diff --git a/db_prep/__filter.py b/db_prep/__filter.py index a8a8da4..cc49a9f 100644 --- a/db_prep/__filter.py +++ b/db_prep/__filter.py @@ -9,15 +9,48 @@ import argparse import pathlib -chemical_elements = ['C', 'H', 'N', 'O', 'P', 'S', 'F', 'Cl', 'Br', 'I', 'Si', 'B', 'Se', 'Na', 'K'] -mass_array = np.array([12.000000, 1.007825, 14.003074, 15.994915, 30.973762, 31.972071, 18.998403, 34.968853, 78.918336, - 126.904473, 27.976927, 10.012937, 73.922476, 22.989769, 38.963707]) +chemical_elements = [ + "C", + "H", + "N", + "O", + "P", + "S", + "F", + "Cl", + "Br", + "I", + "Si", + "B", + "Se", + "Na", + "K", +] +mass_array = np.array( + [ + 12.000000, + 1.007825, + 14.003074, + 15.994915, + 30.973762, + 31.972071, + 18.998403, + 34.968853, + 78.918336, + 126.904473, + 27.976927, + 10.012937, + 73.922476, + 22.989769, + 38.963707, + ] +) def dict_to_chem_formula(input: dict) -> str: - new_formula = '' + new_formula = "" for key in input: - if key != '+' and key != '-': + if key != "+" and key != "-": if int(input[key]) == 0: break elif int(input[key]) == 1: @@ -32,48 +65,54 @@ def charged_formula_coconut(input: str): index = 0 no_chemical_drop_index_list = [] charged_drop_index_list = [] - new_formula_df = pd.DataFrame(columns=['Formula_str', 'Coconut']) + new_formula_df = pd.DataFrame(columns=["Formula_str", "Coconut"]) for formula, times in df.itertuples(index=False): formula_dict = chemparse.parse_formula(formula) keys = list(formula_dict.keys()) if not keys: - new_formula = '' - start_index = formula.find('[') - end_index = formula.find(']') - clean_formula = formula[start_index + 1: end_index] + new_formula = "" + start_index = formula.find("[") + end_index = formula.find("]") + clean_formula = formula[start_index + 1 : end_index] clean_formula_dict = chemparse.parse_formula(clean_formula) - if formula[len(formula) - 1] == '+': - if formula[len(formula) - 2] == ']': + if formula[len(formula) - 1] == "+": + if formula[len(formula) - 2] == "]": loss_h = 1 else: loss_h = int(formula[len(formula) - 2]) - if int(clean_formula_dict['H']) >= loss_h: - clean_formula_dict['H'] = int(clean_formula_dict['H']) - loss_h + if int(clean_formula_dict["H"]) >= loss_h: + clean_formula_dict["H"] = int(clean_formula_dict["H"]) - loss_h for key in clean_formula_dict: if int(clean_formula_dict[key]) == 0: break - new_formula = new_formula + key + str(int(clean_formula_dict[key])) + new_formula = ( + new_formula + key + str(int(clean_formula_dict[key])) + ) new_formula_df.loc[len(new_formula_df)] = [new_formula, times] - if formula[len(formula) - 1] == '-': - if formula[len(formula) - 2] == ']': + if formula[len(formula) - 1] == "-": + if formula[len(formula) - 2] == "]": add_h = 1 else: add_h = int(formula[len(formula) - 2]) - if 'H' in clean_formula_dict.keys(): - clean_formula_dict['H'] = int(clean_formula_dict['H']) + add_h + if "H" in clean_formula_dict.keys(): + clean_formula_dict["H"] = int(clean_formula_dict["H"]) + add_h new_formula = dict_to_chem_formula(clean_formula_dict) else: - new_formula = new_formula + 'C' + str(int(clean_formula_dict['C'])) + new_formula = new_formula + "C" + str(int(clean_formula_dict["C"])) if add_h == 1: - new_formula = new_formula + 'H' + new_formula = new_formula + "H" else: - new_formula = new_formula + 'H' + str(int(add_h)) + new_formula = new_formula + "H" + str(int(add_h)) for key in clean_formula_dict: - if key != 'C': + if key != "C": if int(clean_formula_dict[key]) == 1: new_formula = new_formula + key else: - new_formula = new_formula + key + str(int(clean_formula_dict[key])) + new_formula = ( + new_formula + + key + + str(int(clean_formula_dict[key])) + ) new_formula_df.loc[len(new_formula_df)] = [new_formula, times] charged_drop_index_list.append(index) for key in keys: @@ -84,7 +123,7 @@ def charged_formula_coconut(input: str): total_drop = charged_drop_index_list + no_chemical_drop_index_list df_modified = df.drop(total_drop, axis=0) df_modified = pd.concat([df_modified, new_formula_df]) - df_modified.to_csv('coconut_formula_modified.csv', index=False) + df_modified.to_csv("coconut_formula_modified.csv", index=False) def charged_formula_pub_and_npa(input: str): @@ -92,11 +131,11 @@ def charged_formula_pub_and_npa(input: str): index = 0 no_chemical_drop_index_list = [] charged_drop_index_list = [] - if 'npatlas' in input: - new_formula_df = pd.DataFrame(columns=['Formula_str', 'npatlas']) + if "npatlas" in input: + new_formula_df = pd.DataFrame(columns=["Formula_str", "npatlas"]) else: - new_formula_df = pd.DataFrame(columns=['Formula_str', 'PubChem']) - for formula in df['Formula_str']: + new_formula_df = pd.DataFrame(columns=["Formula_str", "PubChem"]) + for formula in df["Formula_str"]: formula_dict = chemparse.parse_formula(formula) keys = list(formula_dict.keys()) for key in keys: @@ -108,74 +147,100 @@ def charged_formula_pub_and_npa(input: str): for formula, times in df.itertuples(index=False): formula_dict = chemparse.parse_formula(formula) keys = list(formula_dict.keys()) - new_formula = '' - if '+' in keys: - loss_h = formula_dict['+'] - if 'H' in formula_dict.keys(): - if int(formula_dict['H']) >= loss_h: - formula_dict['H'] = int(formula_dict['H']) - loss_h + new_formula = "" + if "+" in keys: + loss_h = formula_dict["+"] + if "H" in formula_dict.keys(): + if int(formula_dict["H"]) >= loss_h: + formula_dict["H"] = int(formula_dict["H"]) - loss_h new_formula = dict_to_chem_formula(formula_dict) new_formula_df.loc[len(new_formula_df)] = [new_formula, times] charged_drop_index_list.append(index) - if '-' in keys: - add_h = formula_dict['-'] - if 'H' in formula_dict.keys(): - formula_dict['H'] = int(formula_dict['H']) + add_h + if "-" in keys: + add_h = formula_dict["-"] + if "H" in formula_dict.keys(): + formula_dict["H"] = int(formula_dict["H"]) + add_h new_formula = dict_to_chem_formula(formula_dict) else: print(formula_dict) - new_formula = new_formula + 'C' + str(int(formula_dict['C'])) + new_formula = new_formula + "C" + str(int(formula_dict["C"])) if add_h == 1: - new_formula = new_formula + 'H' + new_formula = new_formula + "H" else: - new_formula = new_formula + 'H' + str(int(add_h)) + new_formula = new_formula + "H" + str(int(add_h)) for key in formula_dict: - if key != 'C': + if key != "C": if int(formula_dict[key]) == 1: new_formula = new_formula + key else: - new_formula = new_formula + key + str(int(formula_dict[key])) + new_formula = ( + new_formula + key + str(int(formula_dict[key])) + ) new_formula_df.loc[len(new_formula_df)] = [new_formula, times] charged_drop_index_list.append(index) df_modified = df.drop(charged_drop_index_list, axis=0) df_modified = pd.concat([df_modified, new_formula_df]) - if 'npatlas' in input: - df_modified.to_csv('npatlas_formula_modified.csv', index=False) + if "npatlas" in input: + df_modified.to_csv("npatlas_formula_modified.csv", index=False) else: - df_modified.to_csv('pubchem_formula_modified.csv', index=False) + df_modified.to_csv("pubchem_formula_modified.csv", index=False) def merge(): - df_pubchem = pd.read_csv('pubchem_formula_modified.csv') - df_coconut = pd.read_csv('coconut_formula_modified.csv') - df_npatlas = pd.read_csv('npatlas_formula_modified.csv') - df_merged = df_pubchem.merge(df_npatlas, how='outer') - df_merged = df_merged.merge(df_coconut, how='outer') + df_pubchem = pd.read_csv("pubchem_formula_modified.csv") + df_coconut = pd.read_csv("coconut_formula_modified.csv") + df_npatlas = pd.read_csv("npatlas_formula_modified.csv") + df_merged = df_pubchem.merge(df_npatlas, how="outer") + df_merged = df_merged.merge(df_coconut, how="outer") df_merged = df_merged.fillna(0) - df_merged.to_csv('merged_pub_coco_npa.csv', index=False) + df_merged.to_csv("merged_pub_coco_npa.csv", index=False) def test_file(): - df = pd.read_csv('merged_pub_coco_npa.csv') - df.head(10000).to_csv('test_merged.csv', index=False) + df = pd.read_csv("merged_pub_coco_npa.csv") + df.head(10000).to_csv("test_merged.csv", index=False) def split_csv(): # for i, chunk in enumerate(pd.read_csv('merged_pub_coco_npa.csv', chunksize=40000)): # chunk.to_csv('chunk{}.csv'.format(i), index=False) - for i, chunk in enumerate(pd.read_csv('update.csv', chunksize=40000)): - chunk.to_csv('updatesplit{}.csv'.format(i), index=False) + for i, chunk in enumerate(pd.read_csv("update.csv", chunksize=40000)): + chunk.to_csv("updatesplit{}.csv".format(i), index=False) def parse_mass_dbe(): # df = pd.read_csv(pathlib.Path(folder_path) / 'merged_pub_coco_npa.csv') number = 0 - for file in glob.glob(str('/Users/tshyun/Desktop/2022.2.10/COOP/workterm1/pyBUDDY/db/split_ori') + '/*.csv'): + for file in glob.glob( + str("/Users/tshyun/Desktop/2022.2.10/COOP/workterm1/pyBUDDY/db/split_ori") + + "/*.csv" + ): # df = pd.read_csv('test_merged.csv') df = pd.read_csv(file) mass_element_df = pd.DataFrame( - columns=['Formula_str', 'mass', 'C', 'H', 'N', 'O', 'P', 'S', 'F', 'Cl', 'Br', 'I', 'Si', 'B', 'Se', 'Na', - 'K', 'PubChem', 'npatlas', 'Coconut']) + columns=[ + "Formula_str", + "mass", + "C", + "H", + "N", + "O", + "P", + "S", + "F", + "Cl", + "Br", + "I", + "Si", + "B", + "Se", + "Na", + "K", + "PubChem", + "npatlas", + "Coconut", + ] + ) # df.columns = ['Formula_str','mass', 'C', 'H', 'N', 'O', 'P', 'S', 'F', 'Cl', 'Br', 'I', 'Si', 'B', 'Se', 'Na', 'K', # 'PubChem','Npatlas','Coconut'] count = 0 @@ -186,22 +251,56 @@ def parse_mass_dbe(): for i, element in enumerate(chemical_elements): if element in formula_dict.keys(): array[i] = formula_dict[element] - dbe = array[0] + array[10] + 1 - ( - array[1] + array[6] + array[7] + array[8] + array[9] + array[13] + array[14]) / 2 + ( - array[2] + array[4] + array[11]) / 2 + dbe = ( + array[0] + + array[10] + + 1 + - ( + array[1] + + array[6] + + array[7] + + array[8] + + array[9] + + array[13] + + array[14] + ) + / 2 + + (array[2] + array[4] + array[11]) / 2 + ) mass = np.sum(array * mass_array) if not (dbe >= -5 and mass <= 2000): pass else: - mass_element_df.loc[len(mass_element_df)] = [formula, mass, array[0], array[1], array[2], array[3], - array[4], array[5], array[6], array[7], array[8], array[9], - array[10], array[11], array[12], array[13], array[14], - PubChem, npatlas, Coconut] + mass_element_df.loc[len(mass_element_df)] = [ + formula, + mass, + array[0], + array[1], + array[2], + array[3], + array[4], + array[5], + array[6], + array[7], + array[8], + array[9], + array[10], + array[11], + array[12], + array[13], + array[14], + PubChem, + npatlas, + Coconut, + ] count = count + 1 # output_path = pathlib.Path(folder_path) / 'merged_with_formula.csv' mass_element_df.to_csv( - '/Users/tshyun/Desktop/2022.2.10/COOP/workterm1/pyBUDDY/db/split_ori/chunkoutput{}.csv'.format(number), - index=False) + "/Users/tshyun/Desktop/2022.2.10/COOP/workterm1/pyBUDDY/db/split_ori/chunkoutput{}.csv".format( + number + ), + index=False, + ) number = number + 1 # df_modified = df.drop(drop_index_list, axis=0) # print(len(df_modified)) @@ -210,19 +309,22 @@ def parse_mass_dbe(): def concat_csv(): - output = pd.read_csv('chunkoutput0.csv') + output = pd.read_csv("chunkoutput0.csv") count = 0 - for file in glob.glob(str('/Users/tshyun/Desktop/2022.2.10/COOP/workterm1/pyBUDDY/db/split_output') + '/*.csv'): + for file in glob.glob( + str("/Users/tshyun/Desktop/2022.2.10/COOP/workterm1/pyBUDDY/db/split_output") + + "/*.csv" + ): df = pd.read_csv(file) output = pd.concat([output, df], axis=0) print(count) count = count + 1 - output.to_csv('output.csv', index=False) + output.to_csv("output.csv", index=False) def arg_parser(): - parser = argparse.ArgumentParser(description='Merge database') - parser.add_argument('--path', type=str, default='data') + parser = argparse.ArgumentParser(description="Merge database") + parser.add_argument("--path", type=str, default="data") return parser.parse_args() @@ -230,34 +332,153 @@ def update_old(): update = pd.read_csv("output.csv") print(update.shape) update = update.rename( - columns={'Formula_str': 'formula_str', 'C': 'c', 'H': 'h', 'N': 'n', 'O': 'o', 'P': 'p', 'S': 's', - 'F': 'f', 'Cl': 'cl', 'Br': 'br', 'I': 'i', 'Si': 'si', 'B': 'b', 'Se': 'se', 'Na': 'na', - 'K': 'k'}) + columns={ + "Formula_str": "formula_str", + "C": "c", + "H": "h", + "N": "n", + "O": "o", + "P": "p", + "S": "s", + "F": "f", + "Cl": "cl", + "Br": "br", + "I": "i", + "Si": "si", + "B": "b", + "Se": "se", + "Na": "na", + "K": "k", + } + ) old = pd.read_csv("formulaDB_20210622.csv") print(old.shape) - new = old.merge(update, how='outer', - left_on=["formula_str", "c", "h", "b", "br", "cl", "f", "i", "k", "n", "na", "o", "p", "s", "se", - "si"], - right_on=["formula_str", "c", "h", "b", "br", "cl", "f", "i", "k", "n", "na", "o", "p", "s", "se", - "si"]) + new = old.merge( + update, + how="outer", + left_on=[ + "formula_str", + "c", + "h", + "b", + "br", + "cl", + "f", + "i", + "k", + "n", + "na", + "o", + "p", + "s", + "se", + "si", + ], + right_on=[ + "formula_str", + "c", + "h", + "b", + "br", + "cl", + "f", + "i", + "k", + "n", + "na", + "o", + "p", + "s", + "se", + "si", + ], + ) print(new.shape) - new.to_csv('update.csv', index=False) + new.to_csv("update.csv", index=False) def modify(): - df = pd.read_csv('update.csv') - df.mass_x = df.c * 12.000000 + df.h * 1.007825 + df.n * 14.003074 + df.o * 15.994915 + df.p * 30.973762 + df.s * 31.972071 + df.f * 18.998403 + df.cl * 34.968853 + df.br * 78.918336 + df.i * 126.904473 + df.si * 27.976927 + df.b * 10.012937 + df.se * 73.922476 + df.na * 22.989769 + df.k * 38.963707 - df = df.round({'mass_x': 6}) - df.PubChem_x = df[['PubChem_x', 'PubChem_y']].max(axis=1) - df.COCONUT = df[['COCONUT', 'Coconut']].max(axis=1) - df = df[['formula_str', 'mass_x', 'c', 'h', 'b', 'br', 'cl', 'f', 'i', 'k', 'n', 'na', 'o', 'p', 's', 'se', 'si', 'PubChem_x', 'ANPDB', 'BLEXP', 'BMDB', 'ChEBI', 'COCONUT', 'DrugBank', 'DSSTOX', 'ECMDB', 'FooDB', 'HMDB', 'HSDB', 'KEGG', 'LMSD', 'MaConDa', 'MarkerDB', 'MCDB', 'NORMAN', 'NPASS', 'npatlas', 'Plantcyc', 'SMPDB', 'STOFF_IDENT', 'T3DB', 'TTD', 'UNPD', 'YMDB', 'mass_y', 'PubChem_y', 'Coconut']] - df = df.rename(columns={'mass_x': 'mass', 'PubChem_x': 'PubChem', 'npatlas': 'NPAtlas'}) - df = df.drop(columns=['mass_y', 'PubChem_y', 'Coconut']) + df = pd.read_csv("update.csv") + df.mass_x = ( + df.c * 12.000000 + + df.h * 1.007825 + + df.n * 14.003074 + + df.o * 15.994915 + + df.p * 30.973762 + + df.s * 31.972071 + + df.f * 18.998403 + + df.cl * 34.968853 + + df.br * 78.918336 + + df.i * 126.904473 + + df.si * 27.976927 + + df.b * 10.012937 + + df.se * 73.922476 + + df.na * 22.989769 + + df.k * 38.963707 + ) + df = df.round({"mass_x": 6}) + df.PubChem_x = df[["PubChem_x", "PubChem_y"]].max(axis=1) + df.COCONUT = df[["COCONUT", "Coconut"]].max(axis=1) + df = df[ + [ + "formula_str", + "mass_x", + "c", + "h", + "b", + "br", + "cl", + "f", + "i", + "k", + "n", + "na", + "o", + "p", + "s", + "se", + "si", + "PubChem_x", + "ANPDB", + "BLEXP", + "BMDB", + "ChEBI", + "COCONUT", + "DrugBank", + "DSSTOX", + "ECMDB", + "FooDB", + "HMDB", + "HSDB", + "KEGG", + "LMSD", + "MaConDa", + "MarkerDB", + "MCDB", + "NORMAN", + "NPASS", + "npatlas", + "Plantcyc", + "SMPDB", + "STOFF_IDENT", + "T3DB", + "TTD", + "UNPD", + "YMDB", + "mass_y", + "PubChem_y", + "Coconut", + ] + ] + df = df.rename( + columns={"mass_x": "mass", "PubChem_x": "PubChem", "npatlas": "NPAtlas"} + ) + df = df.drop(columns=["mass_y", "PubChem_y", "Coconut"]) df = df.replace(np.nan, 0.0) - df.to_csv('formulaDB_20230316.csv', index=False) + df.to_csv("formulaDB_20230316.csv", index=False) -if __name__ == '__main__': +if __name__ == "__main__": # start_time = time.time() # # charged_formula_pub_and_npa('npatlas_formula.csv') # # charged_formula_pub_and_npa('merged_pubchem.csv') diff --git a/db_prep/__parse_formula_db.py b/db_prep/__parse_formula_db.py index ea9fd5d..f05436e 100644 --- a/db_prep/__parse_formula_db.py +++ b/db_prep/__parse_formula_db.py @@ -7,64 +7,76 @@ # load all sdf files in the folder def load_pubchem_sdf(sdf_file_path: Path, output_path: Path): - for sdf in glob.glob(str(sdf_file_path) + '/*.sdf'): - csv_str = str(sdf).split('Compound_')[1].split('.')[0] + '_formulaCount.csv' + for sdf in glob.glob(str(sdf_file_path) + "/*.sdf"): + csv_str = str(sdf).split("Compound_")[1].split(".")[0] + "_formulaCount.csv" # if csv file exists, skip if (output_path / csv_str).exists(): continue - file = PandasTools.LoadSDF(sdf, smilesName='SMILES', molColName='Molecule', includeFingerprints=False) - result_dup = file[['PUBCHEM_MOLECULAR_FORMULA']] - occur = result_dup.groupby(['PUBCHEM_MOLECULAR_FORMULA']).size() - occur_df = pd.DataFrame({'PUBCHEM_MOLECULAR_FORMULA': occur.index, 'PubChem': occur.values}) - result_no_dup = result_dup.drop_duplicates(subset=['PUBCHEM_MOLECULAR_FORMULA']) - result = pd.merge(result_no_dup, occur_df, on='PUBCHEM_MOLECULAR_FORMULA') - result = result.rename(columns={'PUBCHEM_MOLECULAR_FORMULA': 'Formula_str'}) + file = PandasTools.LoadSDF( + sdf, smilesName="SMILES", molColName="Molecule", includeFingerprints=False + ) + result_dup = file[["PUBCHEM_MOLECULAR_FORMULA"]] + occur = result_dup.groupby(["PUBCHEM_MOLECULAR_FORMULA"]).size() + occur_df = pd.DataFrame( + {"PUBCHEM_MOLECULAR_FORMULA": occur.index, "PubChem": occur.values} + ) + result_no_dup = result_dup.drop_duplicates(subset=["PUBCHEM_MOLECULAR_FORMULA"]) + result = pd.merge(result_no_dup, occur_df, on="PUBCHEM_MOLECULAR_FORMULA") + result = result.rename(columns={"PUBCHEM_MOLECULAR_FORMULA": "Formula_str"}) result.to_csv(output_path / csv_str, index=False) def merge_pubchem_csv(csv_file_path: Path, output_path: Path): list_csv = list() - for csv in glob.glob(str(csv_file_path) + '/*formulaCount.csv'): + for csv in glob.glob(str(csv_file_path) + "/*formulaCount.csv"): file = pd.read_csv(csv) list_csv.append(file) - merged = pd.concat(list_csv).groupby(['Formula_str']).sum().reset_index() - merged.to_csv(output_path / 'merged_pubchem.csv', index=False) + merged = pd.concat(list_csv).groupby(["Formula_str"]).sum().reset_index() + merged.to_csv(output_path / "merged_pubchem.csv", index=False) def load_coconut_sdf(input_path: Path, output_path: Path): - coconut = glob.glob(str(input_path) + '/COCONUT_DB.sdf') - file = PandasTools.LoadSDF(coconut[0], smilesName='SMILES', molColName='Molecule', includeFingerprints=False) - result_dup = file[['molecular_formula']] - occur = result_dup.groupby(['molecular_formula']).size() - occur_df = pd.DataFrame({'molecular_formula': occur.index, 'Coconut': occur.values}) - result_no_dup = result_dup.drop_duplicates(subset=['molecular_formula']) - result = pd.merge(result_no_dup, occur_df, on='molecular_formula') - result = result.rename(columns={'molecular_formula': 'Formula_str'}) - result.to_csv(output_path / 'coconut_formula.csv', index=False) + coconut = glob.glob(str(input_path) + "/COCONUT_DB.sdf") + file = PandasTools.LoadSDF( + coconut[0], + smilesName="SMILES", + molColName="Molecule", + includeFingerprints=False, + ) + result_dup = file[["molecular_formula"]] + occur = result_dup.groupby(["molecular_formula"]).size() + occur_df = pd.DataFrame({"molecular_formula": occur.index, "Coconut": occur.values}) + result_no_dup = result_dup.drop_duplicates(subset=["molecular_formula"]) + result = pd.merge(result_no_dup, occur_df, on="molecular_formula") + result = result.rename(columns={"molecular_formula": "Formula_str"}) + result.to_csv(output_path / "coconut_formula.csv", index=False) def load_npatlas_xlsx(input_path: Path, output_path: Path): - npatlas = glob.glob(str(input_path) + '/NPAtlas_download.xlsx') + npatlas = glob.glob(str(input_path) + "/NPAtlas_download.xlsx") file = pd.read_excel(npatlas[0]) - result_dup = file[['compound_molecular_formula']] - occur = result_dup.groupby(['compound_molecular_formula']).size() - occur_df = pd.DataFrame({'compound_molecular_formula': occur.index, 'npatlas': occur.values}) - result_no_dup = result_dup.drop_duplicates(subset=['compound_molecular_formula']) - result = pd.merge(result_no_dup, occur_df, on='compound_molecular_formula') - result = result.rename(columns={'compound_molecular_formula': 'Formula_str'}) - result.to_csv(output_path / 'npatlas_formula.csv', index=False) + result_dup = file[["compound_molecular_formula"]] + occur = result_dup.groupby(["compound_molecular_formula"]).size() + occur_df = pd.DataFrame( + {"compound_molecular_formula": occur.index, "npatlas": occur.values} + ) + result_no_dup = result_dup.drop_duplicates(subset=["compound_molecular_formula"]) + result = pd.merge(result_no_dup, occur_df, on="compound_molecular_formula") + result = result.rename(columns={"compound_molecular_formula": "Formula_str"}) + result.to_csv(output_path / "npatlas_formula.csv", index=False) + def get_args(): - parser = ArgumentParser(description='Process some integers.') - parser.add_argument('--sdf', type=str, help='sdf file path') - parser.add_argument('--csv', type=str, help='csv file path') - parser.add_argument('--output', type=str, help='output file path') + parser = ArgumentParser(description="Process some integers.") + parser.add_argument("--sdf", type=str, help="sdf file path") + parser.add_argument("--csv", type=str, help="csv file path") + parser.add_argument("--output", type=str, help="output file path") return parser.parse_args() -if __name__ == '__main__': +if __name__ == "__main__": # start_time = time.time() # # # load_pubchem_sdf(Path("/Users/tshyun/Desktop/2022.2.10/COOP/workterm1/pyBUDDY/db"), @@ -78,8 +90,10 @@ def get_args(): args = get_args() # load_coconut_sdf(Path("/Users/tshyun/Desktop/2022.2.10/COOP/workterm1/pyBUDDY/db"), # Path("/Users/tshyun/Desktop/2022.2.10/COOP/workterm1/pyBUDDY/db")) - load_npatlas_xlsx(Path("/Users/tshyun/Desktop/2022.2.10/COOP/workterm1/pyBUDDY/db"), - Path("/Users/tshyun/Desktop/2022.2.10/COOP/workterm1/pyBUDDY/db")) + load_npatlas_xlsx( + Path("/Users/tshyun/Desktop/2022.2.10/COOP/workterm1/pyBUDDY/db"), + Path("/Users/tshyun/Desktop/2022.2.10/COOP/workterm1/pyBUDDY/db"), + ) # load_pubchem_sdf(Path(args.sdf), Path(args.output)) # merge_pubchem_csv(Path(args.csv), Path(args.output)) diff --git a/db_prep/__prepare.py b/db_prep/__prepare.py index ea4d622..827456e 100644 --- a/db_prep/__prepare.py +++ b/db_prep/__prepare.py @@ -6,10 +6,20 @@ # for numba alphabet_np = np.array( - [ord(char) for word in ["C", "H", "Br", "Cl", "F", "I", "K", "N", "Na", "O", "P", "S"] for char in word], - dtype=np.int16) -word_lengths = np.array([len(word) for word in ["C", "H", "Br", "Cl", "F", "I", "K", "N", "Na", "O", "P", "S"]], - dtype=np.int16) + [ + ord(char) + for word in ["C", "H", "Br", "Cl", "F", "I", "K", "N", "Na", "O", "P", "S"] + for char in word + ], + dtype=np.int16, +) +word_lengths = np.array( + [ + len(word) + for word in ["C", "H", "Br", "Cl", "F", "I", "K", "N", "Na", "O", "P", "S"] + ], + dtype=np.int16, +) # @njit @@ -33,13 +43,15 @@ def _form_arr_to_str(form_arr: np.array): idx += word_lengths[i] return formula_list + def _ascii_to_str(ascii_arr) -> str: """ Convert ASCII integer array to string :param ascii_arr: ASCII array :return: string """ - return ''.join(chr(i) for i in ascii_arr) + return "".join(chr(i) for i in ascii_arr) + def form_arr_to_str(form_arr) -> str: """ @@ -49,6 +61,7 @@ def form_arr_to_str(form_arr) -> str: """ return _ascii_to_str(_form_arr_to_str(form_arr)) + def load_frag_table(): """ load common fragment table from file, no carbon @@ -59,12 +72,40 @@ def load_frag_table(): # reorder the element columns into the same order as in the database out_arr = np.zeros((tb.shape[0], 12), dtype=int) for i in range(tb.shape[0]): - out_arr[i, :] = np.array([0, tb.loc[i, 'H'], tb.loc[i, 'Br'], tb.loc[i, 'Cl'], tb.loc[i, 'F'], tb.loc[i, 'I'], - 0, tb.loc[i, 'N'], 0, tb.loc[i, 'O'], tb.loc[i, 'P'], tb.loc[i, 'S']]) + out_arr[i, :] = np.array( + [ + 0, + tb.loc[i, "H"], + tb.loc[i, "Br"], + tb.loc[i, "Cl"], + tb.loc[i, "F"], + tb.loc[i, "I"], + 0, + tb.loc[i, "N"], + 0, + tb.loc[i, "O"], + tb.loc[i, "P"], + tb.loc[i, "S"], + ] + ) # reorder entries by columns of H, then by Br, Cl, F, I, N, O, P, S, in ascending order - indices = np.lexsort((out_arr[:, 11], out_arr[:, 10], out_arr[:, 9], out_arr[:, 8], out_arr[:, 7], out_arr[:, 6], - out_arr[:, 5], out_arr[:, 4], out_arr[:, 3], out_arr[:, 2], out_arr[:, 1], out_arr[:, 0])) + indices = np.lexsort( + ( + out_arr[:, 11], + out_arr[:, 10], + out_arr[:, 9], + out_arr[:, 8], + out_arr[:, 7], + out_arr[:, 6], + out_arr[:, 5], + out_arr[:, 4], + out_arr[:, 3], + out_arr[:, 2], + out_arr[:, 1], + out_arr[:, 0], + ) + ) arr = out_arr[indices, :] # save to file @@ -82,13 +123,39 @@ def load_loss_table(): out_arr = np.zeros((tb.shape[0], 12), dtype=int) for i in range(tb.shape[0]): out_arr[i, :] = np.array( - [tb.loc[i, 'C'], tb.loc[i, 'H'], tb.loc[i, 'Br'], tb.loc[i, 'Cl'], tb.loc[i, 'F'], tb.loc[i, 'I'], - 0, tb.loc[i, 'N'], 0, tb.loc[i, 'O'], tb.loc[i, 'P'], tb.loc[i, 'S']]) + [ + tb.loc[i, "C"], + tb.loc[i, "H"], + tb.loc[i, "Br"], + tb.loc[i, "Cl"], + tb.loc[i, "F"], + tb.loc[i, "I"], + 0, + tb.loc[i, "N"], + 0, + tb.loc[i, "O"], + tb.loc[i, "P"], + tb.loc[i, "S"], + ] + ) # order by C, H, Br, Cl, F, I, N, O, P, S - indices = np.lexsort((out_arr[:, 11], out_arr[:, 10], out_arr[:, 9], out_arr[:, 8], out_arr[:, 7], - out_arr[:, 6], out_arr[:, 5], out_arr[:, 4], out_arr[:, 3], out_arr[:, 2], out_arr[:, 1], - out_arr[:, 0])) + indices = np.lexsort( + ( + out_arr[:, 11], + out_arr[:, 10], + out_arr[:, 9], + out_arr[:, 8], + out_arr[:, 7], + out_arr[:, 6], + out_arr[:, 5], + out_arr[:, 4], + out_arr[:, 3], + out_arr[:, 2], + out_arr[:, 1], + out_arr[:, 0], + ) + ) arr = out_arr[indices, :] # save to file @@ -104,39 +171,86 @@ def formula_db_idx_arr(): db = pd.read_csv("formulaDB_20230316.csv") print("db size before dereplication: ", db.shape[0]) - # rename "PubChem" to "pubchem" - db.rename(columns={'PubChem': 'pubchem'}, inplace=True) + db.rename(columns={"PubChem": "pubchem"}, inplace=True) # create a new column "OtherDB" for non-PubChem databases, fill in the total count of non-PubChem databases - db['other_db'] = db[['ANPDB', 'BLEXP', 'BMDB', 'ChEBI', 'COCONUT', 'DrugBank', 'DSSTOX', 'ECMDB', - 'FooDB', 'HMDB', 'HSDB', 'KEGG', 'LMSD', 'MaConDa', 'MarkerDB', 'MCDB', 'NORMAN', 'NPASS', 'NPAtlas', - 'Plantcyc', 'SMPDB', 'STOFF_IDENT', 'T3DB', 'TTD', 'UNPD', 'YMDB']].sum(axis=1) + db["other_db"] = db[ + [ + "ANPDB", + "BLEXP", + "BMDB", + "ChEBI", + "COCONUT", + "DrugBank", + "DSSTOX", + "ECMDB", + "FooDB", + "HMDB", + "HSDB", + "KEGG", + "LMSD", + "MaConDa", + "MarkerDB", + "MCDB", + "NORMAN", + "NPASS", + "NPAtlas", + "Plantcyc", + "SMPDB", + "STOFF_IDENT", + "T3DB", + "TTD", + "UNPD", + "YMDB", + ] + ].sum(axis=1) # drop rows with se > 0 or si > 0 or b > 0 - db = db.loc[(db['se'] == 0) & (db['si'] == 0) & (db['b'] == 0), :] + db = db.loc[(db["se"] == 0) & (db["si"] == 0) & (db["b"] == 0), :] db.index = range(db.shape[0]) # refill formula_str column - db['form_str'] = db.apply(lambda x: form_arr_to_str(np.array(x[['c', 'h', 'br', 'cl', 'f', 'i', 'k', 'n', 'na', 'o', 'p', 's']].values)), axis=1) - + db["form_str"] = db.apply( + lambda x: form_arr_to_str( + np.array( + x[ + ["c", "h", "br", "cl", "f", "i", "k", "n", "na", "o", "p", "s"] + ].values + ) + ), + axis=1, + ) # dereplicate - db = db.drop_duplicates(subset=['form_str'], inplace=False) + db = db.drop_duplicates(subset=["form_str"], inplace=False) print("db size after dereplication: ", db.shape[0]) # reindex db.index = range(db.shape[0]) # sort by mass - db.sort_values(by=['mass'], inplace=True) + db.sort_values(by=["mass"], inplace=True) db.index = range(db.shape[0]) # c, h, b, br, cl, f, i, k, n, na, o, p, s, se, si # basic db: db with sum of b, k, na, se, si, cl, f, i, br = 0 basic_db = db.loc[ - (db['b'] + db['k'] + db['na'] + db['se'] + db['si'] + db['cl'] + db['f'] + db['i'] + db['br']) == 0, :] + ( + db["b"] + + db["k"] + + db["na"] + + db["se"] + + db["si"] + + db["cl"] + + db["f"] + + db["i"] + + db["br"] + ) + == 0, + :, + ] # halogen db: db with sum of b, k, na, se, si = 0, and sum of cl, f, i, br > 0 - bool_arr1 = (db['b'] + db['k'] + db['na'] + db['se'] + db['si']) == 0 - bool_arr2 = (db['cl'] + db['f'] + db['i'] + db['br']) > 0 + bool_arr1 = (db["b"] + db["k"] + db["na"] + db["se"] + db["si"]) == 0 + bool_arr2 = (db["cl"] + db["f"] + db["i"] + db["br"]) > 0 halogen_db = db.loc[bool_arr1 & bool_arr2, :] # # all db: db with sum of b, k, na, se, si > 0 # all_db = db.loc[(db['b'] + db['k'] + db['na'] + db['se'] + db['si']) > 0, :] @@ -147,27 +261,29 @@ def formula_db_idx_arr(): basic_db_model = [] # combine c, h, b, br, cl, f, i, k, n, na, o, p, s, se, si into formula_arr, np.array - basic_db['formula_arr'] = basic_db[['c', 'h', 'br', 'cl', 'f', 'i', 'k', 'n', 'na', 'o', 'p', 's']].values.tolist() - basic_db_mass_array = basic_db['mass'].values - basic_db_formula_array = np.stack(basic_db['formula_arr'].values) + basic_db["formula_arr"] = basic_db[ + ["c", "h", "br", "cl", "f", "i", "k", "n", "na", "o", "p", "s"] + ].values.tolist() + basic_db_mass_array = basic_db["mass"].values + basic_db_formula_array = np.stack(basic_db["formula_arr"].values) joblib.dump(basic_db_mass_array, "basic_db_mass.joblib") joblib.dump(basic_db_formula_array, "basic_db_formula.joblib") - print("halogen db") halogen_db_model = [] # combine c, h, b, br, cl, f, i, k, n, na, o, p, s, se, si into formula_arr, np.array - halogen_db['formula_arr'] = halogen_db[['c', 'h', 'br', 'cl', 'f', 'i', 'k', 'n', 'na', 'o', 'p', 's']].values.tolist() - halogen_db_mass_array = halogen_db['mass'].values - halogen_db_formula_array = np.stack(halogen_db['formula_arr'].values) + halogen_db["formula_arr"] = halogen_db[ + ["c", "h", "br", "cl", "f", "i", "k", "n", "na", "o", "p", "s"] + ].values.tolist() + halogen_db_mass_array = halogen_db["mass"].values + halogen_db_formula_array = np.stack(halogen_db["formula_arr"].values) joblib.dump(halogen_db_mass_array, "halogen_db_mass.joblib") joblib.dump(halogen_db_formula_array, "halogen_db_formula.joblib") - print("idx array, basic db") # basic db # mass array - mass_arr = np.array(basic_db['mass']) + mass_arr = np.array(basic_db["mass"]) # create idx array, only for mass < 1500 idx_arr = np.zeros(15000, dtype=int) # for each index i, find the index of the first formula with mass >= i/10 @@ -179,7 +295,7 @@ def formula_db_idx_arr(): print("halogen db") # halogen db # mass array - mass_arr = np.array(halogen_db['mass']) + mass_arr = np.array(halogen_db["mass"]) # create idx array, only for mass < 1500 idx_arr = np.zeros(15000, dtype=int) # for each index i, find the index of the first formula with mass >= i/10 @@ -190,7 +306,7 @@ def formula_db_idx_arr(): # test -if __name__ == '__main__': +if __name__ == "__main__": # load_frag_table() # load_loss_table() formula_db_idx_arr() diff --git a/docs/conf.py b/docs/conf.py index e7e71a4..36d9c44 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,7 +1,7 @@ import os import sys -sys.path.insert(0, os.path.abspath('..')) +sys.path.insert(0, os.path.abspath("..")) # Configuration file for the Sphinx documentation builder. # @@ -11,28 +11,24 @@ # -- Project information ----------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information -project = 'msbuddy' -copyright = '2023, Shipei Xing' -author = 'Shipei Xing' -release = '0.1.1' +project = "msbuddy" +copyright = "2023, Shipei Xing" +author = "Shipei Xing" +release = "0.1.1" # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration -extensions = [ - 'sphinx.ext.autodoc', - 'sphinx_rtd_theme' -] +extensions = ["sphinx.ext.autodoc", "sphinx_rtd_theme"] -master_doc = 'index' +master_doc = "index" -templates_path = ['_templates'] -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +templates_path = ["_templates"] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output -html_theme = 'sphinx_rtd_theme' -html_static_path = ['_static'] - +html_theme = "sphinx_rtd_theme" +html_static_path = ["_static"] diff --git a/main_nextflow.py b/main_nextflow.py index bb522f6..97a7f5d 100644 --- a/main_nextflow.py +++ b/main_nextflow.py @@ -23,53 +23,147 @@ def main(): parser = argparse.ArgumentParser(description="msbuddy on Nextflow.") - parser.add_argument('-input', type=str, default=None, help='Path to the input file.') - parser.add_argument('-ms_instr', '-ms', type=str, default=None, - help='MS instrument type. Supported types: orbitrap, qtof, fticr.') - parser.add_argument('-ppm', type=int, default=1, - help='Whether to use ppm for mass tolerance. Default: ppm is enabled.') - parser.add_argument('-ms1_tol', type=float, default=5, help='MS1 tolerance. Default: 5.') - parser.add_argument('-ms2_tol', type=float, default=10, help='MS2 tolerance. Default: 10.') - parser.add_argument('-halogen', '-hal', type=int, default=0, - help='Whether to consider halogen atoms FClBrI. Default: False.') - parser.add_argument('-timeout_secs', '-t', type=int, default=300, help='Timeout in seconds. Default: 300.') - parser.add_argument('-batch_size', '-bs', type=int, default=5000, - help='Batch size. Default: 5000. A larger batch size needs more memory, but is faster.') - parser.add_argument('-c_min', type=int, default=0, help='Minimum number of C atoms. Default: 0.') - parser.add_argument('-c_max', type=int, default=80, help='Maximum number of C atoms. Default: 80.') - parser.add_argument('-h_min', type=int, default=0, help='Minimum number of H atoms. Default: 0.') - parser.add_argument('-h_max', type=int, default=150, help='Maximum number of H atoms. Default: 150.') - parser.add_argument('-n_min', type=int, default=0, help='Minimum number of N atoms. Default: 0.') - parser.add_argument('-n_max', type=int, default=20, help='Maximum number of N atoms. Default: 20.') - parser.add_argument('-o_min', type=int, default=0, help='Minimum number of O atoms. Default: 0.') - parser.add_argument('-o_max', type=int, default=30, help='Maximum number of O atoms. Default: 30.') - parser.add_argument('-p_min', type=int, default=0, help='Minimum number of P atoms. Default: 0.') - parser.add_argument('-p_max', type=int, default=10, help='Maximum number of P atoms. Default: 10.') - parser.add_argument('-s_min', type=int, default=0, help='Minimum number of S atoms. Default: 0.') - parser.add_argument('-s_max', type=int, default=15, help='Maximum number of S atoms. Default: 15.') - parser.add_argument('-f_min', type=int, default=0, help='Minimum number of F atoms. Default: 0.') - parser.add_argument('-f_max', type=int, default=20, help='Maximum number of F atoms. Default: 20.') - parser.add_argument('-cl_min', type=int, default=0, help='Minimum number of Cl atoms. Default: 0.') - parser.add_argument('-cl_max', type=int, default=15, help='Maximum number of Cl atoms. Default: 15.') - parser.add_argument('-br_min', type=int, default=0, help='Minimum number of Br atoms. Default: 0.') - parser.add_argument('-br_max', type=int, default=10, help='Maximum number of Br atoms. Default: 10.') - parser.add_argument('-i_min', type=int, default=0, help='Minimum number of I atoms. Default: 0.') - parser.add_argument('-i_max', type=int, default=10, help='Maximum number of I atoms. Default: 10.') - parser.add_argument('-isotope_bin_mztol', type=float, default=0.02, - help='m/z tolerance for isotope binning, used for MS1 isotope pattern, in Dalton. Default: 0.02.') - parser.add_argument('-max_isotope_cnt', type=int, default=4, - help='Maximum isotope count, used for MS1 isotope pattern. Default: 4.') - parser.add_argument('-rel_int_denoise_cutoff', type=float, default=0.01, - help='Relative intensity cutoff, used for MS2 denoise. Default: 0.01.') - parser.add_argument('-top_n_per_50_da', type=int, default=6, - help='Top N peaks per 50 Da, used for MS2 denoise. Default: 6.') - parser.add_argument('-parallel', '-p', type=int, default=0, - help='Whether to use parallel computing. Default: parallel computing is disabled.') - parser.add_argument('-n_cpu', type=int, default=1, help='Number of CPUs to use. Default: 1.') + parser.add_argument( + "-input", type=str, default=None, help="Path to the input file." + ) + parser.add_argument( + "-ms_instr", + "-ms", + type=str, + default=None, + help="MS instrument type. Supported types: orbitrap, qtof, fticr.", + ) + parser.add_argument( + "-ppm", + type=int, + default=1, + help="Whether to use ppm for mass tolerance. Default: ppm is enabled.", + ) + parser.add_argument( + "-ms1_tol", type=float, default=5, help="MS1 tolerance. Default: 5." + ) + parser.add_argument( + "-ms2_tol", type=float, default=10, help="MS2 tolerance. Default: 10." + ) + parser.add_argument( + "-halogen", + "-hal", + type=int, + default=0, + help="Whether to consider halogen atoms FClBrI. Default: False.", + ) + parser.add_argument( + "-timeout_secs", + "-t", + type=int, + default=300, + help="Timeout in seconds. Default: 300.", + ) + parser.add_argument( + "-batch_size", + "-bs", + type=int, + default=5000, + help="Batch size. Default: 5000. A larger batch size needs more memory, but is faster.", + ) + parser.add_argument( + "-c_min", type=int, default=0, help="Minimum number of C atoms. Default: 0." + ) + parser.add_argument( + "-c_max", type=int, default=80, help="Maximum number of C atoms. Default: 80." + ) + parser.add_argument( + "-h_min", type=int, default=0, help="Minimum number of H atoms. Default: 0." + ) + parser.add_argument( + "-h_max", type=int, default=150, help="Maximum number of H atoms. Default: 150." + ) + parser.add_argument( + "-n_min", type=int, default=0, help="Minimum number of N atoms. Default: 0." + ) + parser.add_argument( + "-n_max", type=int, default=20, help="Maximum number of N atoms. Default: 20." + ) + parser.add_argument( + "-o_min", type=int, default=0, help="Minimum number of O atoms. Default: 0." + ) + parser.add_argument( + "-o_max", type=int, default=30, help="Maximum number of O atoms. Default: 30." + ) + parser.add_argument( + "-p_min", type=int, default=0, help="Minimum number of P atoms. Default: 0." + ) + parser.add_argument( + "-p_max", type=int, default=10, help="Maximum number of P atoms. Default: 10." + ) + parser.add_argument( + "-s_min", type=int, default=0, help="Minimum number of S atoms. Default: 0." + ) + parser.add_argument( + "-s_max", type=int, default=15, help="Maximum number of S atoms. Default: 15." + ) + parser.add_argument( + "-f_min", type=int, default=0, help="Minimum number of F atoms. Default: 0." + ) + parser.add_argument( + "-f_max", type=int, default=20, help="Maximum number of F atoms. Default: 20." + ) + parser.add_argument( + "-cl_min", type=int, default=0, help="Minimum number of Cl atoms. Default: 0." + ) + parser.add_argument( + "-cl_max", type=int, default=15, help="Maximum number of Cl atoms. Default: 15." + ) + parser.add_argument( + "-br_min", type=int, default=0, help="Minimum number of Br atoms. Default: 0." + ) + parser.add_argument( + "-br_max", type=int, default=10, help="Maximum number of Br atoms. Default: 10." + ) + parser.add_argument( + "-i_min", type=int, default=0, help="Minimum number of I atoms. Default: 0." + ) + parser.add_argument( + "-i_max", type=int, default=10, help="Maximum number of I atoms. Default: 10." + ) + parser.add_argument( + "-isotope_bin_mztol", + type=float, + default=0.02, + help="m/z tolerance for isotope binning, used for MS1 isotope pattern, in Dalton. Default: 0.02.", + ) + parser.add_argument( + "-max_isotope_cnt", + type=int, + default=4, + help="Maximum isotope count, used for MS1 isotope pattern. Default: 4.", + ) + parser.add_argument( + "-rel_int_denoise_cutoff", + type=float, + default=0.01, + help="Relative intensity cutoff, used for MS2 denoise. Default: 0.01.", + ) + parser.add_argument( + "-top_n_per_50_da", + type=int, + default=6, + help="Top N peaks per 50 Da, used for MS2 denoise. Default: 6.", + ) + parser.add_argument( + "-parallel", + "-p", + type=int, + default=0, + help="Whether to use parallel computing. Default: parallel computing is disabled.", + ) + parser.add_argument( + "-n_cpu", type=int, default=1, help="Number of CPUs to use. Default: 1." + ) args = parser.parse_args() - ms_instr = None if args.ms_instr == 'None' else args.ms_instr + ms_instr = None if args.ms_instr == "None" else args.ms_instr n_cpu = args.n_cpu if args.n_cpu <= 8 else 8 @@ -78,17 +172,27 @@ def main(): msb_config = MsbuddyConfig( ms_instr=ms_instr, ppm=True if args.ppm == 1 else False, - ms1_tol=args.ms1_tol, ms2_tol=args.ms2_tol, + ms1_tol=args.ms1_tol, + ms2_tol=args.ms2_tol, halogen=True if args.halogen == 1 else False, parallel=True if args.parallel == 1 else False, n_cpu=n_cpu, - timeout_secs=args.timeout_secs, batch_size=args.batch_size, - c_range=(args.c_min, args.c_max), h_range=(args.h_min, args.h_max), n_range=(args.n_min, args.n_max), - o_range=(args.o_min, args.o_max), p_range=(args.p_min, args.p_max), s_range=(args.s_min, args.s_max), - f_range=(args.f_min, args.f_max), cl_range=(args.cl_min, args.cl_max), br_range=(args.br_min, args.br_max), + timeout_secs=args.timeout_secs, + batch_size=args.batch_size, + c_range=(args.c_min, args.c_max), + h_range=(args.h_min, args.h_max), + n_range=(args.n_min, args.n_max), + o_range=(args.o_min, args.o_max), + p_range=(args.p_min, args.p_max), + s_range=(args.s_min, args.s_max), + f_range=(args.f_min, args.f_max), + cl_range=(args.cl_min, args.cl_max), + br_range=(args.br_min, args.br_max), i_range=(args.i_min, args.i_max), - isotope_bin_mztol=args.isotope_bin_mztol, max_isotope_cnt=args.max_isotope_cnt, - rel_int_denoise_cutoff=args.rel_int_denoise_cutoff, top_n_per_50_da=args.top_n_per_50_da + isotope_bin_mztol=args.isotope_bin_mztol, + max_isotope_cnt=args.max_isotope_cnt, + rel_int_denoise_cutoff=args.rel_int_denoise_cutoff, + top_n_per_50_da=args.top_n_per_50_da, ) import os @@ -97,20 +201,20 @@ def main(): if args.input: input_path = pathlib.Path(args.input) else: - raise ValueError('Please specify the input file.') + raise ValueError("Please specify the input file.") # output path, create a folder if not exist - if not os.path.exists('./msbuddy_output'): - os.mkdir('./msbuddy_output') - output_path = pathlib.Path('./msbuddy_output') + if not os.path.exists("./msbuddy_output"): + os.mkdir("./msbuddy_output") + output_path = pathlib.Path("./msbuddy_output") # create a Msbuddy object engine = Msbuddy(msb_config) # load the input file - if input_path.suffix.lower() == '.mgf': + if input_path.suffix.lower() == ".mgf": engine.load_mgf(args.input) - elif input_path.suffix.lower() == '.csv': + elif input_path.suffix.lower() == ".csv": # read and load CSV file df = pd.read_csv(args.input, keep_default_na=False, na_values=None) # if df has >1 columns, treat the 2nd column as adduct strings @@ -119,13 +223,13 @@ def main(): else: engine.load_usi(df.iloc[:, 0].tolist()) else: - raise ValueError('Please input a valid file.') + raise ValueError("Please input a valid file.") # annotate formula engine.annotate_formula_cmd(output_path, write_details=True) - print('Job finished.') + print("Job finished.") -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/msbuddy/base.py b/msbuddy/base.py index 89d39d7..d8c19b3 100644 --- a/msbuddy/base.py +++ b/msbuddy/base.py @@ -29,8 +29,23 @@ class Formula: alphabet = ["C", "H", "Br", "Cl", "F", "I", "K", "N", "Na", "O", "P", "S"] - mass_arr = np.array([12.000000, 1.007825, 78.918336, 34.968853, 18.998403, 126.904473, 38.963707, 14.003074, - 22.989769, 15.994915, 30.973762, 31.972071], dtype=np.float32) + mass_arr = np.array( + [ + 12.000000, + 1.007825, + 78.918336, + 34.968853, + 18.998403, + 126.904473, + 38.963707, + 14.003074, + 22.989769, + 15.994915, + 30.973762, + 31.972071, + ], + dtype=np.float32, + ) # array: np.array(int) # charge: int @@ -38,11 +53,13 @@ class Formula: # dbe: float # isotope: int # M+0, M+1 - def __init__(self, - array: np.array, - charge: int, - mass: Union[float, None] = None, - isotope: int = 0): + def __init__( + self, + array: np.array, + charge: int, + mass: Union[float, None] = None, + isotope: int = 0, + ): self.array = np.int16(array) self.charge = charge self.dbe = calc_formula_dbe(array) @@ -60,7 +77,7 @@ def __bool__(self): def __str__(self) -> str: if not self: - return 'Null' + return "Null" return form_arr_to_str(self.array) @@ -72,7 +89,12 @@ def calc_formula_dbe(arr): """ # ["C", "H", "Br", "Cl", "F", "I", "K", "N", "Na", "O", "P", "S"] # DBE: C + 1 - (H + F + Cl + Br + I + Na + K)/2 + (N + P)/2 - dbe = arr[0] + 1 - (arr[1] + arr[4] + arr[3] + arr[2] + arr[5] + arr[8] + arr[6]) / 2 + (arr[7] + arr[10]) / 2 + dbe = ( + arr[0] + + 1 + - (arr[1] + arr[4] + arr[3] + arr[2] + arr[5] + arr[8] + arr[6]) / 2 + + (arr[7] + arr[10]) / 2 + ) return dbe @@ -82,12 +104,30 @@ def calc_formula_mass(array, charge, isotope): calculate monoisotopic mass of a formula, charge adjusted :return: mass """ - ele_mass_arr = np.array([12.000000, 1.007825, 78.918336, 34.968853, 18.998403, 126.904473, 38.963707, 14.003074, - 22.989769, 15.994915, 30.973762, 31.972071], dtype=np.float32) + ele_mass_arr = np.array( + [ + 12.000000, + 1.007825, + 78.918336, + 34.968853, + 18.998403, + 126.904473, + 38.963707, + 14.003074, + 22.989769, + 15.994915, + 30.973762, + 31.972071, + ], + dtype=np.float32, + ) if charge == 0: mass = float(np.sum(array * ele_mass_arr) + isotope * mass_i) else: - mass = float((np.sum(array * ele_mass_arr) - charge * mass_e + isotope * mass_i) / abs(charge)) + mass = float( + (np.sum(array * ele_mass_arr) - charge * mass_e + isotope * mass_i) + / abs(charge) + ) return mass @@ -98,8 +138,20 @@ def senior_rules(form_arr: np.array) -> bool: :param form_arr: 1D array :return: bool """ - senior_1_1_arr = 6 * form_arr[11] + 5 * form_arr[10] + 4 * form_arr[0] + 3 * form_arr[7] + 2 * form_arr[9] + \ - form_arr[1] + form_arr[4] + form_arr[3] + form_arr[2] + form_arr[5] + form_arr[8] + form_arr[6] + senior_1_1_arr = ( + 6 * form_arr[11] + + 5 * form_arr[10] + + 4 * form_arr[0] + + 3 * form_arr[7] + + 2 * form_arr[9] + + form_arr[1] + + form_arr[4] + + form_arr[3] + + form_arr[2] + + form_arr[5] + + form_arr[8] + + form_arr[6] + ) senior_2_arr = np.sum(form_arr) return senior_1_1_arr >= 2 * (senior_2_arr - 1) @@ -147,10 +199,9 @@ class Adduct: # for example, [M+H-H2O]+ has loss formula H2O; [M+Na]+ has no loss formula; # [M - 2H2O - Cl] - has loss formula H4O2Cl - def __init__(self, - string: Union[str, None], - pos_mode: bool, - report_invalid: bool = False): + def __init__( + self, string: Union[str, None], pos_mode: bool, report_invalid: bool = False + ): """ :param string: adduct string, e.g. [M+H]+ :param pos_mode: True for positive mode, False for negative mode @@ -183,24 +234,44 @@ def _check_valid_style(self) -> bool: check whether the adduct string is valid :return: True for valid, False for invalid """ - if self.string.count(']') == 1 and self.string.count('[') == 1 and 'M' in self.string: - if ("+" in self.string[len(self.string) - 1] and self.pos_mode) \ - or ("-" in self.string[len(self.string) - 1] and not self.pos_mode): + if ( + self.string.count("]") == 1 + and self.string.count("[") == 1 + and "M" in self.string + ): + if ("+" in self.string[len(self.string) - 1] and self.pos_mode) or ( + "-" in self.string[len(self.string) - 1] and not self.pos_mode + ): return True return False # return true for valid characters in the string (except the common abbr.) def _check_valid_character(self) -> bool: - valid_elements = {"-", "+", "C", "H", "Br", "Cl", "F", "I", "K", "N", "Na", "O", "P", "S"} - - m_index = self.string.index('M') - right_index = self.string.index(']') - segment = self.string[m_index + 1: right_index] + valid_elements = { + "-", + "+", + "C", + "H", + "Br", + "Cl", + "F", + "I", + "K", + "N", + "Na", + "O", + "P", + "S", + } + + m_index = self.string.index("M") + right_index = self.string.index("]") + segment = self.string[m_index + 1 : right_index] i = 0 while i < len(segment): # Check for two-character elements - if i < len(segment) - 1 and segment[i:i + 2] in valid_elements: + if i < len(segment) - 1 and segment[i : i + 2] in valid_elements: i += 2 # Check for one-character elements and digits elif segment[i] in valid_elements or segment[i].isdigit(): @@ -215,73 +286,120 @@ def _invalid(self, string, report_invalid: bool = False): raise ValueError("Adduct cannot be parsed: " + string) else: if self.pos_mode: - logging.warning("Adduct cannot be parsed: " + string + ", set to [M+H]+") + logging.warning( + "Adduct cannot be parsed: " + string + ", set to [M+H]+" + ) self.string = "[M+H]+" self.charge = +1 self.loss_formula = None - self.net_formula = Formula(array=[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], charge=0) + self.net_formula = Formula( + array=[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], charge=0 + ) else: - logging.warning("Adduct cannot be parsed: " + string + ", set to [M-H]-") + logging.warning( + "Adduct cannot be parsed: " + string + ", set to [M-H]-" + ) self.string = "[M-H]-" self.charge = -1 - self.loss_formula = Formula(array=[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], charge=0) - self.net_formula = Formula(array=[0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], charge=0) + self.loss_formula = Formula( + array=[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], charge=0 + ) + self.net_formula = Formula( + array=[0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], charge=0 + ) self.m = 1 def _check_common(self) -> bool: - if self.pos_mode and self.string in ['[M+H]+', '[M+NH4]+', '[M+Na]+', '[M+K]+', '[M+H-H2O]+', '[M-H2O+H]+']: + if self.pos_mode and self.string in [ + "[M+H]+", + "[M+NH4]+", + "[M+Na]+", + "[M+K]+", + "[M+H-H2O]+", + "[M-H2O+H]+", + ]: self.charge = +1 self.m = 1 - if self.string == '[M+H]+': + if self.string == "[M+H]+": self.loss_formula = None - self.net_formula = Formula(array=[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], charge=0) - elif self.string == '[M+NH4]+': + self.net_formula = Formula( + array=[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], charge=0 + ) + elif self.string == "[M+NH4]+": self.loss_formula = None - self.net_formula = Formula(array=[0, 4, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0], charge=0) - elif self.string == '[M+Na]+': + self.net_formula = Formula( + array=[0, 4, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0], charge=0 + ) + elif self.string == "[M+Na]+": self.loss_formula = None - self.net_formula = Formula(array=[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0], charge=0) - elif self.string == '[M+K]+': + self.net_formula = Formula( + array=[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0], charge=0 + ) + elif self.string == "[M+K]+": self.loss_formula = None - self.net_formula = Formula(array=[0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0], charge=0) + self.net_formula = Formula( + array=[0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0], charge=0 + ) else: - self.loss_formula = Formula(array=[0, 2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0], charge=0) - self.net_formula = Formula(array=[0, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0], charge=0) + self.loss_formula = Formula( + array=[0, 2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0], charge=0 + ) + self.net_formula = Formula( + array=[0, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0], charge=0 + ) return True - elif (not self.pos_mode) and self.string in ['[M-H]-', '[M+Cl]-', '[M+Br]-', '[M-H2O-H]-', '[M-H-H2O]-']: + elif (not self.pos_mode) and self.string in [ + "[M-H]-", + "[M+Cl]-", + "[M+Br]-", + "[M-H2O-H]-", + "[M-H-H2O]-", + ]: self.charge = -1 self.m = 1 - if self.string == '[M-H]-': - self.loss_formula = Formula(array=[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], charge=0) - self.net_formula = Formula(array=[0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], charge=0) - elif self.string == '[M+Cl]-': + if self.string == "[M-H]-": + self.loss_formula = Formula( + array=[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], charge=0 + ) + self.net_formula = Formula( + array=[0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], charge=0 + ) + elif self.string == "[M+Cl]-": self.loss_formula = None - self.net_formula = Formula(array=[0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0], charge=0) - elif self.string == '[M+Br]-': + self.net_formula = Formula( + array=[0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0], charge=0 + ) + elif self.string == "[M+Br]-": self.loss_formula = None - self.net_formula = Formula(array=[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], charge=0) + self.net_formula = Formula( + array=[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], charge=0 + ) else: - self.loss_formula = Formula(array=[0, 3, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0], charge=0) - self.net_formula = Formula(array=[0, -3, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0], charge=0) + self.loss_formula = Formula( + array=[0, 3, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0], charge=0 + ) + self.net_formula = Formula( + array=[0, -3, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0], charge=0 + ) return True return False def _calc_charge(self): if self.pos_mode: - _substr = self.string.split(']')[1].replace('+', '') - if _substr == '': + _substr = self.string.split("]")[1].replace("+", "") + if _substr == "": self.charge = +1 else: self.charge = int(_substr) else: - _substr = self.string.split(']')[1].replace('-', '') - if _substr == '': + _substr = self.string.split("]")[1].replace("-", "") + if _substr == "": self.charge = -1 else: self.charge = -int(_substr) def _calc_m(self): - idx = self.string.index('M') + idx = self.string.index("M") _substr = self.string[idx - 1] if _substr.isnumeric(): self.m = int(_substr) @@ -308,13 +426,13 @@ def _calc_loss_and_net_formula(self): i = 1 add_index = [] loss_index = [] - add = '' - loss = '' - repeat_add = '' - repeat_loss = '' + add = "" + loss = "" + repeat_add = "" + repeat_loss = "" # get the starting index of add and loss elements - for element in self.string[1: len(self.string) - 2]: + for element in self.string[1 : len(self.string) - 2]: if element == "+": add_index.append(i) elif element == "-": @@ -327,42 +445,42 @@ def _calc_loss_and_net_formula(self): while len(add_index) != 0 and len(loss_index) != 0: if loss_index[0] < add_index[0]: if len(loss_index) == 1: - loss = loss + self.string[loss_index[0]:add_index[0]] + loss = loss + self.string[loss_index[0] : add_index[0]] else: if loss_index[1] > add_index[0]: - loss = loss + self.string[loss_index[0]:add_index[0]] + loss = loss + self.string[loss_index[0] : add_index[0]] else: - loss = loss + self.string[loss_index[0]:loss_index[1]] + loss = loss + self.string[loss_index[0] : loss_index[1]] loss_index.remove(loss_index[0]) else: if len(add_index) == 1: - add = add + self.string[add_index[0]:loss_index[0]] + add = add + self.string[add_index[0] : loss_index[0]] else: if add_index[1] < loss_index[0]: - add = add + self.string[add_index[0]:add_index[1]] + add = add + self.string[add_index[0] : add_index[1]] else: - add = add + self.string[add_index[0]:loss_index[0]] + add = add + self.string[add_index[0] : loss_index[0]] add_index.remove(add_index[0]) if len(add_index) == 0 and len(loss_index) == 0: self._invalid(self.string) return elif len(add_index) == 0: - right_index = self.string.index(']') - loss = loss + self.string[loss_index[0]: right_index] + right_index = self.string.index("]") + loss = loss + self.string[loss_index[0] : right_index] else: - right_index = self.string.index(']') - add = add + self.string[add_index[0]: right_index] + right_index = self.string.index("]") + add = add + self.string[add_index[0] : right_index] # deal with situation like "2H2O" for index, element in enumerate(loss): if element == "-": - if loss[len(loss) - 1] == '-' or loss[index + 1] == '-': + if loss[len(loss) - 1] == "-" or loss[index + 1] == "-": self._invalid(self.string) return elif loss[index + 1].isnumeric(): - repeat_element = '' + repeat_element = "" repeat_time = int(loss[index + 1]) - for e in loss[index + 2:]: + for e in loss[index + 2 :]: if e != "-": repeat_element = repeat_element + e else: @@ -370,22 +488,22 @@ def _calc_loss_and_net_formula(self): if repeat_time > 2: repeat_element = repeat_element * (repeat_time - 1) list1 = list(loss) - list1[index + 1] = ' ' - loss = (''.join(list1)) + list1[index + 1] = " " + loss = "".join(list1) repeat_loss = repeat_loss + repeat_element if len(repeat_loss) != 0: - loss = loss + '-' + repeat_loss + loss = loss + "-" + repeat_loss loss = loss.replace(" ", "") for index2, element2 in enumerate(add): - if element2 == '+': - if add[len(add) - 1] == '+' or add[index2 + 1] == '+': + if element2 == "+": + if add[len(add) - 1] == "+" or add[index2 + 1] == "+": self._invalid(self.string) return elif add[index2 + 1].isnumeric(): - repeat_element2 = '' + repeat_element2 = "" repeat_time2 = int(add[index2 + 1]) - for e2 in add[index2 + 2:]: + for e2 in add[index2 + 2 :]: if e2 != "+": repeat_element2 = repeat_element2 + e2 else: @@ -393,11 +511,11 @@ def _calc_loss_and_net_formula(self): if repeat_time2 > 2: repeat_element2 = repeat_element2 * (repeat_time2 - 1) list2 = list(add) - list2[index2 + 1] = '' - add = (''.join(list2)) + list2[index2 + 1] = "" + add = "".join(list2) repeat_add = repeat_add + repeat_element2 if len(repeat_add) != 0: - add = add + '+' + repeat_add + add = add + "+" + repeat_add add = add.replace(" ", "") loss_array = read_formula(loss.replace("-", "")) @@ -417,14 +535,18 @@ def check_adduct(adduct_str: str) -> Tuple[bool, bool]: # check whether the adduct string is valid adduct_str = adduct_str.replace(" ", "") # check '[' and ']' and 'M' - if adduct_str.count(']') != 1 or adduct_str.count('[') != 1 or 'M' not in adduct_str: + if ( + adduct_str.count("]") != 1 + or adduct_str.count("[") != 1 + or "M" not in adduct_str + ): return False, False # check sign after ']' - if adduct_str[len(adduct_str) - 1] not in ['+', '-']: + if adduct_str[len(adduct_str) - 1] not in ["+", "-"]: return False, False # ion mode - pos_mode = True if adduct_str[len(adduct_str) - 1] == '+' else False + pos_mode = True if adduct_str[len(adduct_str) - 1] == "+" else False return True, pos_mode @@ -438,13 +560,22 @@ class ProcessedMS1: :attr int_array: np.array """ - def __init__(self, mz: float, raw_spec: Spectrum, charge: int, - mz_tol: float, ppm: bool, - isotope_bin_mztol: float, max_isotope_cnt: int): + def __init__( + self, + mz: float, + raw_spec: Spectrum, + charge: int, + mz_tol: float, + ppm: bool, + isotope_bin_mztol: float, + max_isotope_cnt: int, + ): if raw_spec: self.mz_tol = mz_tol self.ppm = ppm - self._find_ms1_isotope(mz, raw_spec, charge, isotope_bin_mztol, max_isotope_cnt) + self._find_ms1_isotope( + mz, raw_spec, charge, isotope_bin_mztol, max_isotope_cnt + ) else: self.idx_array = np.array([], dtype=int) self.mz_array = np.array([], dtype=np.float32) @@ -459,8 +590,14 @@ def __str__(self) -> str: def __len__(self): return self.mz_array.size - def _find_ms1_isotope(self, mz: float, raw_spec: Spectrum, charge: int, - isotope_bin_mztol: float, max_isotope_cnt: int): + def _find_ms1_isotope( + self, + mz: float, + raw_spec: Spectrum, + charge: int, + isotope_bin_mztol: float, + max_isotope_cnt: int, + ): """ find the MS1 isotope pattern from MS1 raw spectrum, record all peaks (could have multiple peaks in one bin) :param mz: precursor mz @@ -487,7 +624,9 @@ def _find_ms1_isotope(self, mz: float, raw_spec: Spectrum, charge: int, self.int_array = np.array([raw_spec.int_array[idx]]) # find other isotope peaks - idx_arr = _find_iso_peaks(mz, raw_spec.mz_array, charge, isotope_bin_mztol, max_isotope_cnt) + idx_arr = _find_iso_peaks( + mz, raw_spec.mz_array, charge, isotope_bin_mztol, max_isotope_cnt + ) self.idx_array = np.concatenate((self.idx_array, idx_arr)) self.mz_array = np.concatenate((self.mz_array, raw_spec.mz_array[idx_arr])) @@ -513,8 +652,13 @@ def _find_m0(mz: float, mz_array: np.array, mz_diff: float) -> Tuple[bool, int]: return m0_found, idx -def _find_iso_peaks(mz: float, mz_arr: np.array, charge: int, - isotope_bin_mztol: float, max_isotope_cnt: int) -> np.array: +def _find_iso_peaks( + mz: float, + mz_arr: np.array, + charge: int, + isotope_bin_mztol: float, + max_isotope_cnt: int, +) -> np.array: """ find ms1 isotope M1, M2, ... peaks (for class ProcessedMS1) :param mz: float @@ -548,10 +692,15 @@ class ProcessedMS2: :attr int_array: np.array """ - def __init__(self, mz: float, raw_spec: Spectrum, - mz_tol: float, ppm: bool, - rel_int_denoise_cutoff: float, - top_n_per_50_da: int): + def __init__( + self, + mz: float, + raw_spec: Spectrum, + mz_tol: float, + ppm: bool, + rel_int_denoise_cutoff: float, + top_n_per_50_da: int, + ): if raw_spec: self.mz_tol = mz_tol self.ppm = ppm @@ -570,8 +719,13 @@ def __str__(self) -> str: def __len__(self): return len(self.mz_array) - def _preprocess(self, mz: float, raw_spec: Spectrum, - rel_int_denoise_cutoff: float, top_n_per_50_da: int): + def _preprocess( + self, + mz: float, + raw_spec: Spectrum, + rel_int_denoise_cutoff: float, + top_n_per_50_da: int, + ): """ preprocess MS2 spectrum, denoise (optional), de-precursor :param mz: precursor mz @@ -647,20 +801,22 @@ def _keep_top_n_per_50_da(self, top_n_per_50_da: int): if top_n_per_50_da <= 1: return - bool_arr = ms2_denoise(mz_arr=self.mz_array, int_arr=self.int_array, top_n=top_n_per_50_da) + bool_arr = ms2_denoise( + mz_arr=self.mz_array, int_arr=self.int_array, top_n=top_n_per_50_da + ) self.idx_array = self.idx_array[bool_arr] self.mz_array = self.mz_array[bool_arr] self.int_array = self.int_array[bool_arr] - def normalize_intensity(self, method: str = 'sum'): + def normalize_intensity(self, method: str = "sum"): """ normalize intensity :param method: 'sum' or 'max' :return: fill self.int_array """ - if method == 'sum': + if method == "sum": self.int_array = self.int_array / np.sum(self.int_array) - elif method == 'max': + elif method == "max": self.int_array = self.int_array / np.max(self.int_array) @@ -697,15 +853,20 @@ class MS2Explanation: MS2Explanation class, used for storing MS2 explanation. """ - def __init__(self, idx_array: np.array, - explanation_list: List[Union[Formula, None]]): + def __init__( + self, idx_array: np.array, explanation_list: List[Union[Formula, None]] + ): self.idx_array = idx_array # indices of peaks in MS2 spectrum - self.explanation_list = explanation_list # List[Formula], isotope peaks are included + self.explanation_list = ( + explanation_list # List[Formula], isotope peaks are included + ) def __str__(self): out_str = "" for i in range(len(self.idx_array)): - out_str += "idx: {}, formula: {}\n".format(self.idx_array[i], str(self.explanation_list[i])) + out_str += "idx: {}, formula: {}\n".format( + self.idx_array[i], str(self.explanation_list[i]) + ) return out_str def __len__(self): @@ -721,31 +882,43 @@ class CandidateFormula: precursor formula in CandidateFormula is a neutral formula """ - def __init__(self, formula: Formula, - charged_formula: Union[Formula, None] = None, - mz_error: Union[float, None] = None, - exp_ms2_sum_int: Union[float, None] = None, - ms2_raw_explanation: Union[MS2Explanation, None] = None, - db_existed: bool = False, - optimal_formula: bool = False, - ms2_refined_explanation: Union[MS2Explanation, None] = None): + def __init__( + self, + formula: Formula, + charged_formula: Union[Formula, None] = None, + mz_error: Union[float, None] = None, + exp_ms2_sum_int: Union[float, None] = None, + ms2_raw_explanation: Union[MS2Explanation, None] = None, + db_existed: bool = False, + optimal_formula: bool = False, + ms2_refined_explanation: Union[MS2Explanation, None] = None, + ): self.formula = formula # neutral formula self.charged_formula = charged_formula # charged formula self.mz_error = mz_error # mz error in ppm or Da, depending on config self.estimated_prob = None # estimated probability (ML score, not normalized) - self.normed_estimated_prob = None # normalized estimated probability considering all candidate formulas + self.normed_estimated_prob = ( + None # normalized estimated probability considering all candidate formulas + ) self.estimated_fdr = None # estimated FDR self.formula_feature_array = None # formula feature array self.ms1_isotope_similarity = None self.exp_ms2_sum_int = exp_ms2_sum_int # sum of MS2 intensity of peaks explained (during bottom-up search) - self.ms2_raw_explanation = ms2_raw_explanation # ms2 explanation during subformula assignment + self.ms2_raw_explanation = ( + ms2_raw_explanation # ms2 explanation during subformula assignment + ) self.db_existed = db_existed # whether this formula is in the formula database # self.optimal_formula = optimal_formula # self.ms2_refined_explanation = ms2_refined_explanation # re-annotate frags using global optim. def __str__(self): - cf_str = form_arr_to_str(self.formula.array) + \ - " est_prob: " + str(self.estimated_prob) + " est_fdr: " + str(self.estimated_fdr) + cf_str = ( + form_arr_to_str(self.formula.array) + + " est_prob: " + + str(self.estimated_prob) + + " est_fdr: " + + str(self.estimated_fdr) + ) if self.ms2_raw_explanation: cf_str += " ms2_raw_exp: " + str(len(self.ms2_raw_explanation)) @@ -757,14 +930,16 @@ class MetaFeature: MetaFeature class, used for storing a metabolic feature. """ - def __init__(self, - identifier: Union[str, int], - mz: float, - charge: int, - rt: Union[float, None] = None, # retention time in seconds - adduct: Union[str, None] = None, - ms1: Union[Spectrum, None] = None, - ms2: Union[Spectrum, None] = None): + def __init__( + self, + identifier: Union[str, int], + mz: float, + charge: int, + rt: Union[float, None] = None, # retention time in seconds + adduct: Union[str, None] = None, + ms1: Union[Spectrum, None] = None, + ms2: Union[Spectrum, None] = None, + ): """ Instantiate a new MetaFeature object. :param identifier: identifier (str or int) @@ -798,10 +973,16 @@ def __str__(self): mf_str += " cf count: 0" return mf_str - def data_preprocess(self, ppm: bool, ms1_tol: float, ms2_tol: float, - isotope_bin_mztol: float, max_isotope_cnt: int, - rel_int_denoise_cutoff: float, - top_n_per_50_da: int): + def data_preprocess( + self, + ppm: bool, + ms1_tol: float, + ms2_tol: float, + isotope_bin_mztol: float, + max_isotope_cnt: int, + rel_int_denoise_cutoff: float, + top_n_per_50_da: int, + ): """ Data preprocessing. :param ppm: whether to use ppm as m/z tolerance @@ -814,31 +995,62 @@ def data_preprocess(self, ppm: bool, ms1_tol: float, ms2_tol: float, :return: fill in ms1_processed and ms2_processed for each metaFeature """ if self.ms1_raw: - self.ms1_processed = ProcessedMS1(self.mz, self.ms1_raw, - self.adduct.charge, - ms1_tol, ppm, isotope_bin_mztol, max_isotope_cnt) + self.ms1_processed = ProcessedMS1( + self.mz, + self.ms1_raw, + self.adduct.charge, + ms1_tol, + ppm, + isotope_bin_mztol, + max_isotope_cnt, + ) if self.ms2_raw: - self.ms2_processed = ProcessedMS2(self.mz, self.ms2_raw, - ms2_tol, ppm, rel_int_denoise_cutoff, top_n_per_50_da) + self.ms2_processed = ProcessedMS2( + self.mz, + self.ms2_raw, + ms2_tol, + ppm, + rel_int_denoise_cutoff, + top_n_per_50_da, + ) def summarize_result(self) -> dict: """ Summarize the annotation result for a MetaFeature. :return: dict """ - result = {'identifier': self.identifier, 'mz': self.mz, 'rt': self.rt, 'adduct': self.adduct.string, - 'formula_rank_1': None, 'estimated_fdr': None, 'formula_rank_2': None, 'formula_rank_3': None, - 'formula_rank_4': None, 'formula_rank_5': None} + result = { + "identifier": self.identifier, + "mz": self.mz, + "rt": self.rt, + "adduct": self.adduct.string, + "formula_rank_1": None, + "estimated_fdr": None, + "formula_rank_2": None, + "formula_rank_3": None, + "formula_rank_4": None, + "formula_rank_5": None, + } if self.candidate_formula_list: - result['formula_rank_1'] = form_arr_to_str(self.candidate_formula_list[0].formula.array) - result['estimated_fdr'] = self.candidate_formula_list[0].estimated_fdr + result["formula_rank_1"] = form_arr_to_str( + self.candidate_formula_list[0].formula.array + ) + result["estimated_fdr"] = self.candidate_formula_list[0].estimated_fdr if len(self.candidate_formula_list) > 1: - result['formula_rank_2'] = form_arr_to_str(self.candidate_formula_list[1].formula.array) + result["formula_rank_2"] = form_arr_to_str( + self.candidate_formula_list[1].formula.array + ) if len(self.candidate_formula_list) > 2: - result['formula_rank_3'] = form_arr_to_str(self.candidate_formula_list[2].formula.array) + result["formula_rank_3"] = form_arr_to_str( + self.candidate_formula_list[2].formula.array + ) if len(self.candidate_formula_list) > 3: - result['formula_rank_4'] = form_arr_to_str(self.candidate_formula_list[3].formula.array) + result["formula_rank_4"] = form_arr_to_str( + self.candidate_formula_list[3].formula.array + ) if len(self.candidate_formula_list) > 4: - result['formula_rank_5'] = form_arr_to_str(self.candidate_formula_list[4].formula.array) + result["formula_rank_5"] = form_arr_to_str( + self.candidate_formula_list[4].formula.array + ) return result diff --git a/msbuddy/cand.py b/msbuddy/cand.py index 90119e8..d130289 100644 --- a/msbuddy/cand.py +++ b/msbuddy/cand.py @@ -19,10 +19,28 @@ from brainpy import isotopic_variants from numba import njit -from msbuddy.base import Formula, CandidateFormula, MS2Explanation, MetaFeature, check_adduct, Adduct +from msbuddy.base import ( + Formula, + CandidateFormula, + MS2Explanation, + MetaFeature, + check_adduct, + Adduct, +) from msbuddy.ml import _calc_log_p_norm -from msbuddy.query import check_common_frag, check_common_nl, query_precursor_mass, query_fragnl_mass -from msbuddy.utils import form_arr_to_str, enumerate_subformula, read_formula, SubformulaResult, FormulaResult +from msbuddy.query import ( + check_common_frag, + check_common_nl, + query_precursor_mass, + query_fragnl_mass, +) +from msbuddy.utils import ( + form_arr_to_str, + enumerate_subformula, + read_formula, + SubformulaResult, + FormulaResult, +) class FragExplanation: @@ -69,8 +87,10 @@ def refine_explanation_v1(self, raw_ms2_mz_arr: np.array, gd): # 2. select the closest to the raw MS2 m/z # either common frag or common nl is True - common_bool = [check_common_frag(frag, gd) or check_common_nl(nl, gd) for frag, nl in - zip(self.frag_list, self.nl_list)] + common_bool = [ + check_common_frag(frag, gd) or check_common_nl(nl, gd) + for frag, nl in zip(self.frag_list, self.nl_list) + ] # if only one common frag/nl, select it if sum(common_bool) == 1: idx = common_bool.index(True) @@ -80,14 +100,29 @@ def refine_explanation_v1(self, raw_ms2_mz_arr: np.array, gd): # if multiple common frag/nl, select the mz closest one elif sum(common_bool) > 1: # get the mass-closest one - idx = np.argmin(np.abs(raw_ms2_mz_arr[self.idx] - np.array([f.mass for i, f in enumerate(self.frag_list) - if common_bool[i]]))) - self.optim_frag = [f for i, f in enumerate(self.frag_list) if common_bool[i]][idx] - self.optim_nl = [f for i, f in enumerate(self.nl_list) if common_bool[i]][idx] + idx = np.argmin( + np.abs( + raw_ms2_mz_arr[self.idx] + - np.array( + [f.mass for i, f in enumerate(self.frag_list) if common_bool[i]] + ) + ) + ) + self.optim_frag = [ + f for i, f in enumerate(self.frag_list) if common_bool[i] + ][idx] + self.optim_nl = [f for i, f in enumerate(self.nl_list) if common_bool[i]][ + idx + ] return # if no common frag/nl, select the closest one else: - idx = np.argmin(np.abs(raw_ms2_mz_arr[self.idx] - np.array([f.mass for f in self.frag_list]))) + idx = np.argmin( + np.abs( + raw_ms2_mz_arr[self.idx] + - np.array([f.mass for f in self.frag_list]) + ) + ) self.optim_frag = self.frag_list[idx] self.optim_nl = self.nl_list[idx] return @@ -104,7 +139,9 @@ def refine_explanation(self, raw_ms2_mz_arr: np.array): return # if multiple explanations, select the closest to the raw MS2 m/z - idx = _find_closest_mass_idx(raw_ms2_mz_arr[self.idx], np.array([f.mass for f in self.frag_list])) + idx = _find_closest_mass_idx( + raw_ms2_mz_arr[self.idx], np.array([f.mass for f in self.frag_list]) + ) # idx = np.argmin(np.abs(raw_ms2_mz_arr[self.idx] - np.array([f.mass for f in self.frag_list]))) self.optim_frag = self.frag_list[idx] self.optim_nl = self.nl_list[idx] @@ -117,24 +154,33 @@ class CandidateSpace: It contains a precursor candidate and a list of FragExplanations. """ - def __init__(self, pre_neutral_array: np.array, pre_charged_array: np.array, - pre_neutral_form_str: Union[str, None] = None, - exp_frag_sum_int: Union[float, None] = None, - exp_frag_idx_ls: Union[List[int], None] = None, - frag_exp_ls: Union[List[FragExplanation], None] = None): + def __init__( + self, + pre_neutral_array: np.array, + pre_charged_array: np.array, + pre_neutral_form_str: Union[str, None] = None, + exp_frag_sum_int: Union[float, None] = None, + exp_frag_idx_ls: Union[List[int], None] = None, + frag_exp_ls: Union[List[FragExplanation], None] = None, + ): self.pre_neutral_array = np.int16(pre_neutral_array) # precursor neutral array - self.pre_charged_array = np.int16(pre_charged_array) # used for ms2 global optim. + self.pre_charged_array = np.int16( + pre_charged_array + ) # used for ms2 global optim. self.pre_neutral_form_str = pre_neutral_form_str self.exp_frag_sum_int = exp_frag_sum_int # during candidate space generation self.exp_frag_idx_ls = exp_frag_idx_ls # during candidate space generation self.neutral_mass = float(np.sum(pre_neutral_array * Formula.mass_arr)) - self.frag_exp_list = frag_exp_ls # during subformula assignment, List[FragExplanation] + self.frag_exp_list = ( + frag_exp_ls # during subformula assignment, List[FragExplanation] + ) def add_frag_exp(self, frag_exp: FragExplanation): self.frag_exp_list.append(frag_exp) - def refine_explanation(self, meta_feature: MetaFeature, - ms2_iso_tol: float) -> CandidateFormula: + def refine_explanation( + self, meta_feature: MetaFeature, ms2_iso_tol: float + ) -> CandidateFormula: """ Refine the MS2 explanation by selecting the most reasonable explanation. Explain other fragments as isotope peaks. @@ -164,13 +210,18 @@ def refine_explanation(self, meta_feature: MetaFeature, if next_exp_idx not in ms2_processed.idx_array: continue # if the next peak is close enough, add it as an isotope peak - if (ms2_raw.mz_array[next_exp_idx] - ms2_raw.mz_array[exp_idx] - 1.003355) <= ms2_iso_tol and \ - ms2_raw.int_array[next_exp_idx] <= ms2_raw.int_array[exp_idx]: + if ( + ms2_raw.mz_array[next_exp_idx] - ms2_raw.mz_array[exp_idx] - 1.003355 + ) <= ms2_iso_tol and ms2_raw.int_array[next_exp_idx] <= ms2_raw.int_array[ + exp_idx + ]: # if the next peak is close enough, add it as an isotope peak this_frag = self.frag_exp_list[m].optim_frag this_nl = self.frag_exp_list[m].optim_nl # add a new FragExplanation - new_frag = Formula(this_frag.array, this_frag.charge, this_frag.mass + 1.003355, 1) + new_frag = Formula( + this_frag.array, this_frag.charge, this_frag.mass + 1.003355, 1 + ) # for iso peak, the neutral loss is actually the same as the previous one (M+0) # but we denote it as '-1' isotope peak, so that frag mass + nl mass is still precursor mass new_nl = Formula(this_nl.array, 0, this_nl.mass - 1.003355, -1) @@ -183,17 +234,29 @@ def refine_explanation(self, meta_feature: MetaFeature, # convert into a CandidateFormula # construct MS2Explanation first - ms2_raw_exp = MS2Explanation(idx_array=np.array([f.idx for f in self.frag_exp_list], dtype=np.int16), - explanation_list=[f.optim_frag for f in self.frag_exp_list]) - - return CandidateFormula(formula=Formula(self.pre_neutral_array, 0, self.neutral_mass), - charged_formula=Formula(self.pre_charged_array, meta_feature.adduct.charge), - ms2_raw_explanation=ms2_raw_exp) - - -def gen_candidate_formula(mf: MetaFeature, ppm: bool, ms1_tol: float, ms2_tol: float, - db_mode: int, ele_lower_limit: np.array, ele_upper_limit: np.array, - max_isotope_cnt: int, gd: dict) -> MetaFeature: + ms2_raw_exp = MS2Explanation( + idx_array=np.array([f.idx for f in self.frag_exp_list], dtype=np.int16), + explanation_list=[f.optim_frag for f in self.frag_exp_list], + ) + + return CandidateFormula( + formula=Formula(self.pre_neutral_array, 0, self.neutral_mass), + charged_formula=Formula(self.pre_charged_array, meta_feature.adduct.charge), + ms2_raw_explanation=ms2_raw_exp, + ) + + +def gen_candidate_formula( + mf: MetaFeature, + ppm: bool, + ms1_tol: float, + ms2_tol: float, + db_mode: int, + ele_lower_limit: np.array, + ele_upper_limit: np.array, + max_isotope_cnt: int, + gd: dict, +) -> MetaFeature: """ Generate candidate formulas for a metabolic feature. :param mf: MetaFeature object @@ -210,21 +273,27 @@ def gen_candidate_formula(mf: MetaFeature, ppm: bool, ms1_tol: float, ms2_tol: f # if MS2 data missing or non-singly charged species, query precursor mass directly if not mf.ms2_processed or abs(mf.adduct.charge) > 1: - cf_list, _ = _gen_candidate_formula_from_mz(mf, ppm, ms1_tol, - ele_lower_limit, ele_upper_limit, db_mode, gd) + cf_list, _ = _gen_candidate_formula_from_mz( + mf, ppm, ms1_tol, ele_lower_limit, ele_upper_limit, db_mode, gd + ) else: # if MS2 data available, generate candidate space with MS2 data - ms2_cand_form_ls, ms2_cand_form_str_ls = _gen_candidate_formula_from_ms2(mf, ppm, ms1_tol, ms2_tol, - ele_lower_limit, - ele_upper_limit, db_mode, gd) + ms2_cand_form_ls, ms2_cand_form_str_ls = _gen_candidate_formula_from_ms2( + mf, ppm, ms1_tol, ms2_tol, ele_lower_limit, ele_upper_limit, db_mode, gd + ) # query precursor mass, for fill in db_existed - ms1_cand_form_ls, ms1_cand_form_str_ls = _gen_candidate_formula_from_mz(mf, ppm, ms1_tol, ele_lower_limit, - ele_upper_limit, db_mode, gd) + ms1_cand_form_ls, ms1_cand_form_str_ls = _gen_candidate_formula_from_mz( + mf, ppm, ms1_tol, ele_lower_limit, ele_upper_limit, db_mode, gd + ) - cf_list = _merge_cand_form_list(ms1_cand_form_ls, ms2_cand_form_ls, - ms1_cand_form_str_ls, ms2_cand_form_str_ls) + cf_list = _merge_cand_form_list( + ms1_cand_form_ls, + ms2_cand_form_ls, + ms1_cand_form_str_ls, + ms2_cand_form_str_ls, + ) # if len(mf.ms2_processed) <= 5: # # merge candidate formulas from ms1 and ms2 @@ -242,17 +311,23 @@ def gen_candidate_formula(mf: MetaFeature, ppm: bool, ms1_tol: float, ms2_tol: f ms1_iso_available = mf.ms1_processed and len(mf.ms1_processed) > 1 if ms1_iso_available: for k, cf in enumerate(cf_list): - cf_list[k].ms1_isotope_similarity = _calc_ms1_iso_sim(cf, mf, max_isotope_cnt) + cf_list[k].ms1_isotope_similarity = _calc_ms1_iso_sim( + cf, mf, max_isotope_cnt + ) ms2_available = mf.ms2_processed # retain top candidate formulas - mf.candidate_formula_list = _retain_top_cand_form(cf_list, ms1_tol, ms1_iso_available, ms2_available, 350) + mf.candidate_formula_list = _retain_top_cand_form( + cf_list, ms1_tol, ms1_iso_available, ms2_available, 350 + ) return mf -def _calc_mz_error(cf_list: List[CandidateFormula], precursor_mz: float, ppm: bool) -> List[CandidateFormula]: +def _calc_mz_error( + cf_list: List[CandidateFormula], precursor_mz: float, ppm: bool +) -> List[CandidateFormula]: """ Calculate mz error for each candidate formula. :param cf_list: list of candidate formulas @@ -262,7 +337,9 @@ def _calc_mz_error(cf_list: List[CandidateFormula], precursor_mz: float, ppm: bo """ if ppm: for k, cf in enumerate(cf_list): - cf_list[k].mz_error = (cf.charged_formula.mass - precursor_mz) / precursor_mz * 1e6 + cf_list[k].mz_error = ( + (cf.charged_formula.mass - precursor_mz) / precursor_mz * 1e6 + ) else: for k, cf in enumerate(cf_list): cf_list[k].mz_error = cf.charged_formula.mass - precursor_mz @@ -314,7 +391,9 @@ def calc_isotope_similarity(int_arr_x, int_arr_y, iso_num: int) -> float: if len(int_arr_y) > min_len: # experimental isotope pattern int_arr_y = int_arr_y[:min_len] if len(int_arr_y) < min_len: - int_arr_y = np.append(int_arr_y, np.zeros(min_len - len(int_arr_y), dtype=np.float32)) + int_arr_y = np.append( + int_arr_y, np.zeros(min_len - len(int_arr_y), dtype=np.float32) + ) # normalize int_arr_x = int_arr_x / np.sum(int_arr_x, dtype=np.float32) @@ -328,7 +407,9 @@ def calc_isotope_similarity(int_arr_x, int_arr_y, iso_num: int) -> float: @njit -def _element_check(form_array: np.array, lower_limit: np.array, upper_limit: np.array) -> bool: +def _element_check( + form_array: np.array, lower_limit: np.array, upper_limit: np.array +) -> bool: """ check whether a formula satisfies the element restriction :param form_array: 12-dim array @@ -353,10 +434,32 @@ def _senior_rules(form: np.array) -> bool: # int senior_1_2 = p + n + h + f + cl + br + i + na + k # int senior_2 = c + h + n + o + p + f + cl + br + i + s + na + k - senior_1_1 = (6 * form[11] + 5 * form[10] + 4 * form[0] + 3 * form[7] + 2 * form[9] + form[1] + form[4] + - form[3] + form[2] + form[5] + form[8] + form[6]) + senior_1_1 = ( + 6 * form[11] + + 5 * form[10] + + 4 * form[0] + + 3 * form[7] + + 2 * form[9] + + form[1] + + form[4] + + form[3] + + form[2] + + form[5] + + form[8] + + form[6] + ) senior_1_1 = np.float32(senior_1_1) - senior_1_2 = form[10] + form[7] + form[1] + form[4] + form[3] + form[2] + form[5] + form[8] + form[6] + senior_1_2 = ( + form[10] + + form[7] + + form[1] + + form[4] + + form[3] + + form[2] + + form[5] + + form[8] + + form[6] + ) senior_1_2 = np.float32(senior_1_2) # The sum of valences or the total number of atoms having odd valences is even @@ -392,8 +495,12 @@ def _dbe_check(form: np.array) -> bool: :param form: 12-dim array :return: True if satisfies, False otherwise """ - dbe = form[0] + 1 - (form[1] + form[4] + form[3] + form[2] + form[5] + form[8] + - form[6]) / 2.0 + (form[7] + form[10]) / 2.0 + dbe = ( + form[0] + + 1 + - (form[1] + form[4] + form[3] + form[2] + form[5] + form[8] + form[6]) / 2.0 + + (form[7] + form[10]) / 2.0 + ) if dbe < 0: return False return True @@ -422,19 +529,27 @@ def _calc_ms1_iso_sim(cand_form, meta_feature, max_isotope_cnt) -> float: :return: ms1 isotope similarity """ # calculate theoretical isotope pattern - theo_isotope_pattern = calc_isotope_pattern(cand_form.charged_formula, max_isotope_cnt) + theo_isotope_pattern = calc_isotope_pattern( + cand_form.charged_formula, max_isotope_cnt + ) # calculate ms1 isotope similarity - ms1_isotope_sim = calc_isotope_similarity(meta_feature.ms1_processed.int_array, theo_isotope_pattern, - max_isotope_cnt) + ms1_isotope_sim = calc_isotope_similarity( + meta_feature.ms1_processed.int_array, theo_isotope_pattern, max_isotope_cnt + ) return ms1_isotope_sim -def _gen_candidate_formula_from_mz(meta_feature: MetaFeature, - ppm: bool, ms1_tol: float, - lower_limit: np.array, upper_limit: np.array, - db_mode: int, gd: dict) -> Tuple[List[CandidateFormula], List[str]]: +def _gen_candidate_formula_from_mz( + meta_feature: MetaFeature, + ppm: bool, + ms1_tol: float, + lower_limit: np.array, + upper_limit: np.array, + db_mode: int, + gd: dict, +) -> Tuple[List[CandidateFormula], List[str]]: """ Generate candidate formulas for a metabolic feature with precursor mz only :param meta_feature: MetaFeature object @@ -447,27 +562,47 @@ def _gen_candidate_formula_from_mz(meta_feature: MetaFeature, :return: list of candidate formulas (CandidateFormula), list of candidate formula strings """ # query precursor mz - neutral_formulas, charged_formulas = query_precursor_mass(meta_feature.mz, meta_feature.adduct, - ms1_tol, ppm, db_mode, gd) + neutral_formulas, charged_formulas = query_precursor_mass( + meta_feature.mz, meta_feature.adduct, ms1_tol, ppm, db_mode, gd + ) # filter out formulas that exceed element limits neutral_forms, charged_forms = [], [] for m, f in enumerate(neutral_formulas): - if _element_check(f.array, lower_limit, upper_limit) and _senior_rules(f.array) and _o_p_check(f.array) \ - and _dbe_check(f.array) and _adduct_loss_check(f.array, meta_feature.adduct.loss_formula): + if ( + _element_check(f.array, lower_limit, upper_limit) + and _senior_rules(f.array) + and _o_p_check(f.array) + and _dbe_check(f.array) + and _adduct_loss_check(f.array, meta_feature.adduct.loss_formula) + ): neutral_forms.append(f) charged_forms.append(charged_formulas[m]) # convert neutral formulas into CandidateFormula objects - cand_form_list = [CandidateFormula(formula=form, charged_formula=charged_form, exp_ms2_sum_int=0.0, - db_existed=True) for form, charged_form in zip(neutral_forms, charged_forms)] + cand_form_list = [ + CandidateFormula( + formula=form, + charged_formula=charged_form, + exp_ms2_sum_int=0.0, + db_existed=True, + ) + for form, charged_form in zip(neutral_forms, charged_forms) + ] cand_form_str_list = [form_arr_to_str(cf.formula.array) for cf in cand_form_list] return cand_form_list, cand_form_str_list -def _gen_candidate_formula_from_ms2(mf: MetaFeature, ppm: bool, ms1_tol: float, ms2_tol: float, - lower_limit: np.array, upper_limit: np.array, - db_mode: int, gd) -> Tuple[List[CandidateFormula], List[str]]: +def _gen_candidate_formula_from_ms2( + mf: MetaFeature, + ppm: bool, + ms1_tol: float, + ms2_tol: float, + lower_limit: np.array, + upper_limit: np.array, + db_mode: int, + gd, +) -> Tuple[List[CandidateFormula], List[str]]: """ Generate candidate formulas for a metabolic feature with MS2 data, then apply element limits :param mf: MetaFeature object @@ -482,7 +617,7 @@ def _gen_candidate_formula_from_ms2(mf: MetaFeature, ppm: bool, ms1_tol: float, """ # normalize MS2 intensity - mf.ms2_processed.normalize_intensity(method='sum') + mf.ms2_processed.normalize_intensity(method="sum") # check whether Na and K are contained in the adduct na_bool = True if mf.adduct.net_formula.array[8] > 0 else False @@ -500,8 +635,17 @@ def _gen_candidate_formula_from_ms2(mf: MetaFeature, ppm: bool, ms1_tol: float, nl_mz = mf.mz - frag_mz # query mass in formula database - frag_form_list, nl_form_list = _query_frag_nl_pair(frag_mz, nl_mz, mf.adduct.pos_mode, na_bool, k_bool, - ms2_tol, ppm, db_mode, gd) + frag_form_list, nl_form_list = _query_frag_nl_pair( + frag_mz, + nl_mz, + mf.adduct.pos_mode, + na_bool, + k_bool, + ms2_tol, + ppm, + db_mode, + gd, + ) if frag_form_list is None or nl_form_list is None: continue @@ -513,7 +657,11 @@ def _gen_candidate_formula_from_ms2(mf: MetaFeature, ppm: bool, ms1_tol: float, for frag in frag_form_list: for nl in nl_form_list: # DBE check, sum of DBE should be a non-integer - if (frag.dbe + nl.dbe) % 1 == 0 or (frag.dbe + nl.dbe) < 0 or frag.dbe < 0: + if ( + (frag.dbe + nl.dbe) % 1 == 0 + or (frag.dbe + nl.dbe) < 0 + or frag.dbe < 0 + ): continue # sum mass check if abs(frag.mass + nl.mass - mf.mz) > ms1_abs_tol: @@ -521,7 +669,9 @@ def _gen_candidate_formula_from_ms2(mf: MetaFeature, ppm: bool, ms1_tol: float, # generate precursor formula & check adduct M # NOTE: pre_form_arr is in neutral form - pre_form_arr = (frag.array + nl.array - mf.adduct.net_formula.array) / mf.adduct.m + pre_form_arr = ( + frag.array + nl.array - mf.adduct.net_formula.array + ) / mf.adduct.m valid_pre_form = _valid_precursor_array(pre_form_arr) if not valid_pre_form: continue @@ -529,34 +679,59 @@ def _gen_candidate_formula_from_ms2(mf: MetaFeature, ppm: bool, ms1_tol: float, pre_form_arr = np.int16(pre_form_arr) # add to candidate space list - candidate_space_list, existing_cand_str_list = _add_to_candidate_space_list(candidate_space_list, - existing_cand_str_list, - pre_form_arr.astype(int), - frag.array, nl.array, - frag_int, i) + candidate_space_list, existing_cand_str_list = ( + _add_to_candidate_space_list( + candidate_space_list, + existing_cand_str_list, + pre_form_arr.astype(int), + frag.array, + nl.array, + frag_int, + i, + ) + ) # element limit check, SENIOR rules, O/P check, DBE check - candidate_list = [cs for cs in candidate_space_list - if _element_check(cs.pre_neutral_array, lower_limit, upper_limit) - and _senior_rules(cs.pre_neutral_array) and _o_p_check(cs.pre_neutral_array) - and _dbe_check(cs.pre_neutral_array) - and _adduct_loss_check(cs.pre_neutral_array, mf.adduct.loss_formula)] + candidate_list = [ + cs + for cs in candidate_space_list + if _element_check(cs.pre_neutral_array, lower_limit, upper_limit) + and _senior_rules(cs.pre_neutral_array) + and _o_p_check(cs.pre_neutral_array) + and _dbe_check(cs.pre_neutral_array) + and _adduct_loss_check(cs.pre_neutral_array, mf.adduct.loss_formula) + ] # remove candidate space variable to save memory del candidate_space_list # generate CandidateFormula object - candidate_formula_list = [CandidateFormula(formula=Formula(cs.pre_neutral_array, 0, cs.neutral_mass), - charged_formula=Formula(cs.pre_charged_array, mf.adduct.charge), - exp_ms2_sum_int=cs.exp_frag_sum_int) for cs in candidate_list] - cand_form_str_list = [form_arr_to_str(cf.formula.array) for cf in candidate_formula_list] + candidate_formula_list = [ + CandidateFormula( + formula=Formula(cs.pre_neutral_array, 0, cs.neutral_mass), + charged_formula=Formula(cs.pre_charged_array, mf.adduct.charge), + exp_ms2_sum_int=cs.exp_frag_sum_int, + ) + for cs in candidate_list + ] + cand_form_str_list = [ + form_arr_to_str(cf.formula.array) for cf in candidate_formula_list + ] return candidate_formula_list, cand_form_str_list -def _query_frag_nl_pair(frag_mz: float, nl_mz: float, pos_mode: bool, na_bool: bool, k_bool: bool, - ms2_tol: float, ppm: bool, - db_mode: int, gd) -> Tuple[Union[List[Formula], None], Union[List[Formula], None]]: +def _query_frag_nl_pair( + frag_mz: float, + nl_mz: float, + pos_mode: bool, + na_bool: bool, + k_bool: bool, + ms2_tol: float, + ppm: bool, + db_mode: int, + gd, +) -> Tuple[Union[List[Formula], None], Union[List[Formula], None]]: """ query fragment and neutral loss formulas from database :param frag_mz: fragment m/z @@ -572,28 +747,38 @@ def _query_frag_nl_pair(frag_mz: float, nl_mz: float, pos_mode: bool, na_bool: b """ if nl_mz < frag_mz: # search neutral loss first, for faster search - nl_form_list = query_fragnl_mass(nl_mz, False, pos_mode, na_bool, k_bool, - ms2_tol, ppm, db_mode, gd) + nl_form_list = query_fragnl_mass( + nl_mz, False, pos_mode, na_bool, k_bool, ms2_tol, ppm, db_mode, gd + ) if nl_form_list: - frag_form_list = query_fragnl_mass(frag_mz, True, pos_mode, - na_bool, k_bool, ms2_tol, ppm, db_mode, gd) + frag_form_list = query_fragnl_mass( + frag_mz, True, pos_mode, na_bool, k_bool, ms2_tol, ppm, db_mode, gd + ) else: return None, None else: - frag_form_list = query_fragnl_mass(frag_mz, True, pos_mode, na_bool, k_bool, - ms2_tol, ppm, db_mode, gd) + frag_form_list = query_fragnl_mass( + frag_mz, True, pos_mode, na_bool, k_bool, ms2_tol, ppm, db_mode, gd + ) if frag_form_list: - nl_form_list = query_fragnl_mass(nl_mz, False, pos_mode, na_bool, k_bool, - ms2_tol, ppm, db_mode, gd) + nl_form_list = query_fragnl_mass( + nl_mz, False, pos_mode, na_bool, k_bool, ms2_tol, ppm, db_mode, gd + ) else: return None, None return frag_form_list, nl_form_list -def _add_to_candidate_space_list(candidate_space_list: List[CandidateSpace], existing_cand_str_list: List[str], - pre_form_arr: np.array, frag_arr: np.array, nl_arr: np.array, - fragment_intensity: float, frag_idx: int) -> Tuple[List[CandidateSpace], List[str]]: +def _add_to_candidate_space_list( + candidate_space_list: List[CandidateSpace], + existing_cand_str_list: List[str], + pre_form_arr: np.array, + frag_arr: np.array, + nl_arr: np.array, + fragment_intensity: float, + frag_idx: int, +) -> Tuple[List[CandidateSpace], List[str]]: """ add a new candidate formula to the candidate space list :param candidate_space_list: candidate space list @@ -610,10 +795,15 @@ def _add_to_candidate_space_list(candidate_space_list: List[CandidateSpace], exi candidate_exist = True if this_pre_str in existing_cand_str_list else False # this precursor formula has not been added to the candidate space list if not candidate_exist: - candidate_space_list.append(CandidateSpace(pre_form_arr, frag_arr + nl_arr, - pre_neutral_form_str=this_pre_str, - exp_frag_sum_int=fragment_intensity, - exp_frag_idx_ls=[frag_idx])) + candidate_space_list.append( + CandidateSpace( + pre_form_arr, + frag_arr + nl_arr, + pre_neutral_form_str=this_pre_str, + exp_frag_sum_int=fragment_intensity, + exp_frag_idx_ls=[frag_idx], + ) + ) existing_cand_str_list.append(this_pre_str) else: # find the index of the existing candidate space @@ -643,8 +833,12 @@ def _valid_precursor_array(pre_arr: np.array) -> bool: return True -def _merge_cand_form_list(ms1_cand_list: List[CandidateFormula], ms2_cand_list: List[CandidateFormula], - ms1_cand_str_list: List[str], ms2_cand_str_list: List[str]) -> List[CandidateFormula]: +def _merge_cand_form_list( + ms1_cand_list: List[CandidateFormula], + ms2_cand_list: List[CandidateFormula], + ms1_cand_str_list: List[str], + ms2_cand_str_list: List[str], +) -> List[CandidateFormula]: """ Merge MS1 and MS2 candidate formula lists. Map MS2 candidate formulas to MS1 candidate formulas (db_existed=True) @@ -667,8 +861,12 @@ def _merge_cand_form_list(ms1_cand_list: List[CandidateFormula], ms2_cand_list: return out_list -def _fill_in_db_existence(ms1_cand_list: List[CandidateFormula], ms2_cand_list: List[CandidateFormula], - ms1_cand_str_list: List[str], ms2_cand_str_list: List[str]) -> List[CandidateFormula]: +def _fill_in_db_existence( + ms1_cand_list: List[CandidateFormula], + ms2_cand_list: List[CandidateFormula], + ms1_cand_str_list: List[str], + ms2_cand_str_list: List[str], +) -> List[CandidateFormula]: """ Fill in DB existence for MS2 candidate formulas. :param ms1_cand_list: candidate formula list from MS1 mz search @@ -689,8 +887,13 @@ def _fill_in_db_existence(ms1_cand_list: List[CandidateFormula], ms2_cand_list: return ms2_cand_list -def _retain_top_cand_form(cf_list: List[CandidateFormula], ms1_tol: float, ms1_iso_available: bool, - ms2_available: bool, top_n: int) -> List[CandidateFormula]: +def _retain_top_cand_form( + cf_list: List[CandidateFormula], + ms1_tol: float, + ms1_iso_available: bool, + ms2_available: bool, + top_n: int, +) -> List[CandidateFormula]: """ Retain top candidate formulas by explained MS2 intensity, then by mz difference. :param cf_list: candidate formula list @@ -705,13 +908,25 @@ def _retain_top_cand_form(cf_list: List[CandidateFormula], ms1_tol: float, ms1_i else: if ms1_iso_available: # sort candidate list by logp mz difference * 0.05 + ms1_iso_sim - cf_list = sorted(cf_list, key=lambda x: (_calc_log_p_norm(x.mz_error, ms1_tol / 3) * 0.05 + - x.ms1_isotope_similarity), reverse=True) + cf_list = sorted( + cf_list, + key=lambda x: ( + _calc_log_p_norm(x.mz_error, ms1_tol / 3) * 0.05 + + x.ms1_isotope_similarity + ), + reverse=True, + ) else: if ms2_available: # sort candidate list by mz_error_log_p + 4 * exp_frag_int_pnt - cf_list = sorted(cf_list, key=lambda x: (_calc_log_p_norm(x.mz_error, ms1_tol / 3) + 4 * x.exp_ms2_sum_int), - reverse=True) + cf_list = sorted( + cf_list, + key=lambda x: ( + _calc_log_p_norm(x.mz_error, ms1_tol / 3) + + 4 * x.exp_ms2_sum_int + ), + reverse=True, + ) else: # sort candidate list by abs mz_error, ascending cf_list = sorted(cf_list, key=lambda x: abs(x.mz_error), reverse=False) @@ -730,7 +945,9 @@ def _form_array_equal(arr1: np.array, arr2: np.array) -> bool: return True if np.equal(arr1, arr2).all() else False -def assign_subformula_cand_form(mf: MetaFeature, ppm: bool, ms2_tol: float) -> MetaFeature: +def assign_subformula_cand_form( + mf: MetaFeature, ppm: bool, ms2_tol: float +) -> MetaFeature: """ Assign subformula to all candidate formulas in a MetaFeature object. :param mf: MetaFeature object @@ -746,8 +963,9 @@ def assign_subformula_cand_form(mf: MetaFeature, ppm: bool, ms2_tol: float) -> M # mono mass mass_arr = _calc_subform_mass(subform_arr, mf.adduct.charge) # assign ms2 explanation - mf.candidate_formula_list[k] = _assign_ms2_explanation(mf, cf, cf.charged_formula.array, subform_arr, mass_arr, - ppm, ms2_tol) + mf.candidate_formula_list[k] = _assign_ms2_explanation( + mf, cf, cf.charged_formula.array, subform_arr, mass_arr, ppm, ms2_tol + ) return mf @@ -761,11 +979,28 @@ def _calc_subform_mass(subform_arr: np.array, adduct_charge: int) -> np.array: :return: 1D array, mass of each subformula """ mass_arr = np.empty(subform_arr.shape[0], dtype=np.float32) - ele_mass_arr = np.array([12.000000, 1.007825, 78.918336, 34.968853, 18.998403, 126.904473, 38.963707, 14.003074, - 22.989769, 15.994915, 30.973762, 31.972071], dtype=np.float32) + ele_mass_arr = np.array( + [ + 12.000000, + 1.007825, + 78.918336, + 34.968853, + 18.998403, + 126.904473, + 38.963707, + 14.003074, + 22.989769, + 15.994915, + 30.973762, + 31.972071, + ], + dtype=np.float32, + ) for i in range(subform_arr.shape[0]): # element wise multiplication - mass_arr[i] = np.sum(subform_arr[i, :] * ele_mass_arr, dtype=np.float32) - np.float32(adduct_charge * 0.0005486) + mass_arr[i] = np.sum( + subform_arr[i, :] * ele_mass_arr, dtype=np.float32 + ) - np.float32(adduct_charge * 0.0005486) return mass_arr @@ -776,9 +1011,21 @@ def _dbe_subform_filter(subform_arr: np.array, cutoff: float) -> np.array: :param subform_arr: 2D array, each row is a subformula array :return: boolean array """ - dbe_arr = subform_arr[:, 0] + 1 - (subform_arr[:, 1] + subform_arr[:, 4] + subform_arr[:, 3] + subform_arr[:, 2] - + subform_arr[:, 5] + subform_arr[:, 8] + subform_arr[:, 6]) / 2 + \ - (subform_arr[:, 7] + subform_arr[:, 10]) / 2 + dbe_arr = ( + subform_arr[:, 0] + + 1 + - ( + subform_arr[:, 1] + + subform_arr[:, 4] + + subform_arr[:, 3] + + subform_arr[:, 2] + + subform_arr[:, 5] + + subform_arr[:, 8] + + subform_arr[:, 6] + ) + / 2 + + (subform_arr[:, 7] + subform_arr[:, 10]) / 2 + ) dbe_bool_arr = dbe_arr >= cutoff return dbe_bool_arr @@ -790,11 +1037,22 @@ def _senior_subform_filter(subform_arr: np.array) -> np.array: :param subform_arr: 2D array, each row is a subformula array :return: boolean array """ - senior_1_1_arr = 6 * subform_arr[:, 11] + 5 * subform_arr[:, 10] + 4 * subform_arr[:, 0] + \ - 3 * subform_arr[:, 7] + 2 * subform_arr[:, 9] + subform_arr[:, 1] + subform_arr[:, 4] + \ - subform_arr[:, 3] + subform_arr[:, 2] + subform_arr[:, 5] + subform_arr[:, 8] + subform_arr[:, 6] + senior_1_1_arr = ( + 6 * subform_arr[:, 11] + + 5 * subform_arr[:, 10] + + 4 * subform_arr[:, 0] + + 3 * subform_arr[:, 7] + + 2 * subform_arr[:, 9] + + subform_arr[:, 1] + + subform_arr[:, 4] + + subform_arr[:, 3] + + subform_arr[:, 2] + + subform_arr[:, 5] + + subform_arr[:, 8] + + subform_arr[:, 6] + ) senior_2_arr = np.sum(subform_arr, axis=1) - senior_bool_arr = (senior_1_1_arr >= 2 * (senior_2_arr - 1)) + senior_bool_arr = senior_1_1_arr >= 2 * (senior_2_arr - 1) return senior_bool_arr @@ -810,22 +1068,39 @@ def _valid_subform_check(subform_arr: np.array, pre_charged_arr: np.array) -> np frag_atom_sum = np.sum(subform_arr, axis=1) loss_form_arr = pre_charged_arr - subform_arr loss_atom_sum = np.sum(loss_form_arr, axis=1) - invalid_bool_arr = (frag_atom_sum == subform_arr[:, 0]) | (frag_atom_sum == subform_arr[:, 7]) | \ - (frag_atom_sum == subform_arr[:, 10]) | (loss_atom_sum == loss_form_arr[:, 0]) | \ - (loss_atom_sum == loss_form_arr[:, 7]) | (loss_atom_sum == loss_form_arr[:, 10]) + invalid_bool_arr = ( + (frag_atom_sum == subform_arr[:, 0]) + | (frag_atom_sum == subform_arr[:, 7]) + | (frag_atom_sum == subform_arr[:, 10]) + | (loss_atom_sum == loss_form_arr[:, 0]) + | (loss_atom_sum == loss_form_arr[:, 7]) + | (loss_atom_sum == loss_form_arr[:, 10]) + ) # O >= 2*P if P > 0 - invalid_o_p_frag_bool_arr = (subform_arr[:, 9] < 2 * subform_arr[:, 10]) & (subform_arr[:, 10] > 0) - invalid_o_p_loss_bool_arr = (loss_form_arr[:, 9] < 2 * loss_form_arr[:, 10]) & (loss_form_arr[:, 10] > 0) + invalid_o_p_frag_bool_arr = (subform_arr[:, 9] < 2 * subform_arr[:, 10]) & ( + subform_arr[:, 10] > 0 + ) + invalid_o_p_loss_bool_arr = (loss_form_arr[:, 9] < 2 * loss_form_arr[:, 10]) & ( + loss_form_arr[:, 10] > 0 + ) - invalid_bool_arr = invalid_bool_arr | invalid_o_p_frag_bool_arr | invalid_o_p_loss_bool_arr + invalid_bool_arr = ( + invalid_bool_arr | invalid_o_p_frag_bool_arr | invalid_o_p_loss_bool_arr + ) return ~invalid_bool_arr -def _assign_ms2_explanation(mf: MetaFeature, cf: CandidateFormula, pre_charged_arr: np.array, - subform_arr: np.array, mass_arr: np.array, - ppm: bool, ms2_tol: float) -> CandidateFormula: +def _assign_ms2_explanation( + mf: MetaFeature, + cf: CandidateFormula, + pre_charged_arr: np.array, + subform_arr: np.array, + mass_arr: np.array, + ppm: bool, + ms2_tol: float, +) -> CandidateFormula: """ Assign MS2 explanation to a candidate formula. :param mf: MetaFeature object @@ -841,8 +1116,12 @@ def _assign_ms2_explanation(mf: MetaFeature, cf: CandidateFormula, pre_charged_a ion_mode_int = 1 if mf.adduct.pos_mode else -1 for i in range(len(mf.ms2_processed.mz_array)): # retrieve all indices of mass within tolerance - this_ms2_tol = ms2_tol if not ppm else ms2_tol * mf.ms2_processed.mz_array[i] * 1e-6 - idx_list = np.where(abs(mf.ms2_processed.mz_array[i] - mass_arr) <= this_ms2_tol)[0] + this_ms2_tol = ( + ms2_tol if not ppm else ms2_tol * mf.ms2_processed.mz_array[i] * 1e-6 + ) + idx_list = np.where( + abs(mf.ms2_processed.mz_array[i] - mass_arr) <= this_ms2_tol + )[0] if len(idx_list) == 0: continue @@ -869,18 +1148,24 @@ def _assign_ms2_explanation(mf: MetaFeature, cf: CandidateFormula, pre_charged_a if this_subform_arr.shape[0] == 0: continue - frag_exp = FragExplanation(mf.ms2_processed.idx_array[i], - Formula(this_subform_arr[0, :], ion_mode_int, this_mass[0]), - Formula(pre_charged_arr - this_subform_arr[0, :], 0)) + frag_exp = FragExplanation( + mf.ms2_processed.idx_array[i], + Formula(this_subform_arr[0, :], ion_mode_int, this_mass[0]), + Formula(pre_charged_arr - this_subform_arr[0, :], 0), + ) # add all subformulas if len(this_mass) > 1: for j in range(1, len(this_mass)): - frag_exp.add_frag_nl(Formula(this_subform_arr[j, :], ion_mode_int, this_mass[j]), - Formula(pre_charged_arr - this_subform_arr[j, :], 0)) + frag_exp.add_frag_nl( + Formula(this_subform_arr[j, :], ion_mode_int, this_mass[j]), + Formula(pre_charged_arr - this_subform_arr[j, :], 0), + ) if candidate_space is None: # create CandidateSpace object - candidate_space = CandidateSpace(cf.formula.array, pre_charged_arr, frag_exp_ls=[frag_exp]) + candidate_space = CandidateSpace( + cf.formula.array, pre_charged_arr, frag_exp_ls=[frag_exp] + ) else: candidate_space.add_frag_exp(frag_exp) @@ -900,9 +1185,14 @@ def _assign_ms2_explanation(mf: MetaFeature, cf: CandidateFormula, pre_charged_a return candidate_form -def assign_subformula(ms2_mz: List, precursor_formula: str, adduct: str, - ms2_tol: float = 10, ppm: bool = True, - dbe_cutoff: float = 0.0) -> Union[List[SubformulaResult], None]: +def assign_subformula( + ms2_mz: List, + precursor_formula: str, + adduct: str, + ms2_tol: float = 10, + ppm: bool = True, + dbe_cutoff: float = 0.0, +) -> Union[List[SubformulaResult], None]: """ Assign subformulas to a given MS2 spectrum with a given precursor formula and adduct. Radical ions are considered. :param ms2_mz: MS2 m/z list diff --git a/msbuddy/export.py b/msbuddy/export.py index 872d2d5..c6fe9ce 100644 --- a/msbuddy/export.py +++ b/msbuddy/export.py @@ -18,8 +18,13 @@ import pandas as pd -def write_batch_results_cmd(buddy_data, output_path: pathlib.Path, write_details: bool, - start_idx: int, end_idx: int) -> pd.DataFrame: +def write_batch_results_cmd( + buddy_data, + output_path: pathlib.Path, + write_details: bool, + start_idx: int, + end_idx: int, +) -> pd.DataFrame: """ write out batch results :param buddy_data: buddy data @@ -36,19 +41,22 @@ def write_batch_results_cmd(buddy_data, output_path: pathlib.Path, write_details # update summary results DataFrame for mf in batch_data: individual_result = mf.summarize_result() - result_df_rows.append({ - 'identifier': mf.identifier, - 'mz': round(mf.mz, 4), - 'rt': round(mf.rt, 4) if mf.rt else 'NA', - 'adduct': mf.adduct.string, - 'formula_rank_1': individual_result['formula_rank_1'], - 'estimated_fdr': individual_result['estimated_fdr'] if individual_result[ - 'estimated_fdr'] is not None else 'NA', - 'formula_rank_2': individual_result['formula_rank_2'], - 'formula_rank_3': individual_result['formula_rank_3'], - 'formula_rank_4': individual_result['formula_rank_4'], - 'formula_rank_5': individual_result['formula_rank_5'] - }) + result_df_rows.append( + { + "identifier": mf.identifier, + "mz": round(mf.mz, 4), + "rt": round(mf.rt, 4) if mf.rt else "NA", + "adduct": mf.adduct.string, + "formula_rank_1": individual_result["formula_rank_1"], + "estimated_fdr": individual_result["estimated_fdr"] + if individual_result["estimated_fdr"] is not None + else "NA", + "formula_rank_2": individual_result["formula_rank_2"], + "formula_rank_3": individual_result["formula_rank_3"], + "formula_rank_4": individual_result["formula_rank_4"], + "formula_rank_5": individual_result["formula_rank_5"], + } + ) result_df = pd.DataFrame(result_df_rows) # write out detailed results @@ -56,28 +64,36 @@ def write_batch_results_cmd(buddy_data, output_path: pathlib.Path, write_details for mf in batch_data: # make a directory for each mf # replace '/' with '_' in the identifier, remove special characters - _id = str(mf.identifier).replace('/', '').replace(':', '').replace(' ', '').strip() - folder_name = _id + '_mz_' + str(round(mf.mz, 4)) + '_rt_' - folder_name += str(round(mf.rt, 2)) if mf.rt else 'NA' + _id = ( + str(mf.identifier) + .replace("/", "") + .replace(":", "") + .replace(" ", "") + .strip() + ) + folder_name = _id + "_mz_" + str(round(mf.mz, 4)) + "_rt_" + folder_name += str(round(mf.rt, 2)) if mf.rt else "NA" mf_path = pathlib.Path(output_path / folder_name) mf_path.mkdir(parents=True, exist_ok=True) # write the tsv file containing all the candidate formulas all_candidates_df_rows = [] if not mf.candidate_formula_list: - all_candidates_df_rows.append({ - 'rank': 'NA', - 'formula': 'NA', - 'ms1_isotope_similarity': 'NA', - 'mz_error_ppm': 'NA', - 'explained_ms2_peak': 'NA', - 'total_valid_ms2_peak': 'NA', - 'estimated_prob': 'NA', - 'normalized_estimated_prob': 'NA', - 'estimated_fdr': 'NA', - 'ms2_explanation_idx': 'NA', - 'ms2_explanation': 'NA' - }) + all_candidates_df_rows.append( + { + "rank": "NA", + "formula": "NA", + "ms1_isotope_similarity": "NA", + "mz_error_ppm": "NA", + "explained_ms2_peak": "NA", + "total_valid_ms2_peak": "NA", + "estimated_prob": "NA", + "normalized_estimated_prob": "NA", + "estimated_fdr": "NA", + "ms2_explanation_idx": "NA", + "ms2_explanation": "NA", + } + ) continue for m, cf in enumerate(mf.candidate_formula_list): @@ -85,59 +101,86 @@ def write_batch_results_cmd(buddy_data, output_path: pathlib.Path, write_details if mf.ms2_processed: if cf.ms2_raw_explanation: exp_ms2_peak = len(cf.ms2_raw_explanation) - ms2_explan_idx = ','.join([str(x) for x in cf.ms2_raw_explanation.idx_array]) - ms2_explan_str = ','.join([x.__str__() for x in cf.ms2_raw_explanation.explanation_list]) + ms2_explan_idx = ",".join( + [str(x) for x in cf.ms2_raw_explanation.idx_array] + ) + ms2_explan_str = ",".join( + [ + x.__str__() + for x in cf.ms2_raw_explanation.explanation_list + ] + ) else: - exp_ms2_peak = '0' - ms2_explan_idx = 'None' - ms2_explan_str = 'None' + exp_ms2_peak = "0" + ms2_explan_idx = "None" + ms2_explan_str = "None" else: - exp_ms2_peak = 'NA' - ms2_explan_idx = 'NA' - ms2_explan_str = 'NA' + exp_ms2_peak = "NA" + ms2_explan_idx = "NA" + ms2_explan_str = "NA" # theoretical mass - theo_mass = (cf.formula.mass * mf.adduct.m + mf.adduct.net_formula.mass - - mf.adduct.charge * 0.0005486) / abs(mf.adduct.charge) + theo_mass = ( + cf.formula.mass * mf.adduct.m + + mf.adduct.net_formula.mass + - mf.adduct.charge * 0.0005486 + ) / abs(mf.adduct.charge) mz_error_ppm = (mf.mz - theo_mass) / theo_mass * 1e6 - all_candidates_df_rows.append({ - 'rank': str(m + 1), - 'formula': cf.formula.__str__(), - 'ms1_isotope_similarity': round(cf.ms1_isotope_similarity, - 5) if cf.ms1_isotope_similarity is not None else 'NA', - 'mz_error_ppm': round(mz_error_ppm, 5), - 'explained_ms2_peak': exp_ms2_peak, - 'total_valid_ms2_peak': len(mf.ms2_processed) if mf.ms2_processed else 'NA', - 'estimated_prob': cf.estimated_prob if cf.estimated_prob is not None else 'NA', - 'normalized_estimated_prob': cf.normed_estimated_prob if cf.normed_estimated_prob is not None else 'NA', - 'estimated_fdr': cf.estimated_fdr if cf.estimated_fdr is not None else 'NA', - 'ms2_explanation_idx': ms2_explan_idx, - 'ms2_explanation': ms2_explan_str - }) + all_candidates_df_rows.append( + { + "rank": str(m + 1), + "formula": cf.formula.__str__(), + "ms1_isotope_similarity": round(cf.ms1_isotope_similarity, 5) + if cf.ms1_isotope_similarity is not None + else "NA", + "mz_error_ppm": round(mz_error_ppm, 5), + "explained_ms2_peak": exp_ms2_peak, + "total_valid_ms2_peak": len(mf.ms2_processed) + if mf.ms2_processed + else "NA", + "estimated_prob": cf.estimated_prob + if cf.estimated_prob is not None + else "NA", + "normalized_estimated_prob": cf.normed_estimated_prob + if cf.normed_estimated_prob is not None + else "NA", + "estimated_fdr": cf.estimated_fdr + if cf.estimated_fdr is not None + else "NA", + "ms2_explanation_idx": ms2_explan_idx, + "ms2_explanation": ms2_explan_str, + } + ) all_candidates_df = pd.DataFrame(all_candidates_df_rows) - all_candidates_df.to_csv(mf_path / 'formula_results.tsv', sep="\t", index=False) + all_candidates_df.to_csv( + mf_path / "formula_results.tsv", sep="\t", index=False + ) # write the tsv file containing preprocessed spectrum if mf.ms1_processed: ms1_df_rows = [] for m in range(len(mf.ms1_processed)): - ms1_df_rows.append({ - 'raw_idx': mf.ms1_processed.idx_array[m], - 'mz': mf.ms1_processed.mz_array[m], - 'intensity': mf.ms1_processed.int_array[m] - }) + ms1_df_rows.append( + { + "raw_idx": mf.ms1_processed.idx_array[m], + "mz": mf.ms1_processed.mz_array[m], + "intensity": mf.ms1_processed.int_array[m], + } + ) ms1_df = pd.DataFrame(ms1_df_rows) - ms1_df.to_csv(mf_path / 'ms1_preprocessed.tsv', sep="\t", index=False) + ms1_df.to_csv(mf_path / "ms1_preprocessed.tsv", sep="\t", index=False) if mf.ms2_processed: ms2_df_rows = [] for m in range(len(mf.ms2_processed)): - ms2_df_rows.append({ - 'raw_idx': mf.ms2_processed.idx_array[m], - 'mz': mf.ms2_processed.mz_array[m], - 'intensity': mf.ms2_processed.int_array[m] - }) + ms2_df_rows.append( + { + "raw_idx": mf.ms2_processed.idx_array[m], + "mz": mf.ms2_processed.mz_array[m], + "intensity": mf.ms2_processed.int_array[m], + } + ) ms2_df = pd.DataFrame(ms2_df_rows) - ms2_df.to_csv(mf_path / 'ms2_preprocessed.tsv', sep="\t", index=False) + ms2_df.to_csv(mf_path / "ms2_preprocessed.tsv", sep="\t", index=False) return result_df diff --git a/msbuddy/load.py b/msbuddy/load.py index 0c8ba46..5457897 100644 --- a/msbuddy/load.py +++ b/msbuddy/load.py @@ -22,13 +22,14 @@ from gdown import download as download from joblib import load as j_load from requests import get +from tqdm.auto import tqdm from msbuddy.base import MetaFeature, Spectrum logging.basicConfig(level=logging.INFO) -current_db_version = 'v0.2.4' -current_model_version = 'v0.3.0' +current_db_version = "v0.2.4" +current_model_version = "v0.3.0" def check_download_joblibload(url: str, path): @@ -38,7 +39,7 @@ def check_download_joblibload(url: str, path): :param path: path to save :return: loaded object """ - if not path.exists() or path.stat().st_size < 10 ** 3: + if not path.exists() or path.stat().st_size < 10**3: download(url, str(path)) return j_load(path) @@ -53,27 +54,37 @@ def init_db() -> dict: root_path = Path(__file__).parent # create data folder if not exists - data_path = root_path / 'data' + data_path = root_path / "data" data_path.mkdir(parents=True, exist_ok=True) global_dict = dict() # load common_loss_db, common_frag_db - db_name = 'common_db_' + current_db_version + '.joblib' - global_dict['common_loss_db'], global_dict['common_frag_db'] = ( + db_name = "common_db_" + current_db_version + ".joblib" + global_dict["common_loss_db"], global_dict["common_frag_db"] = ( check_download_joblibload( - 'https://github.com/Philipbear/msbuddy/releases/download/msbuddy_data_v0.2.4/common_db_v0.2.4.joblib', - data_path / db_name)) + "https://github.com/Philipbear/msbuddy/releases/download/msbuddy_data_v0.2.4/common_db_v0.2.4.joblib", + data_path / db_name, + ) + ) # formula_db - db_name = 'formula_db_' + current_db_version + '.joblib' - basic_db, halogen_db = ( - check_download_joblibload( - 'https://github.com/Philipbear/msbuddy/releases/download/msbuddy_data_v0.2.4/formula_db_v0.2.4.joblib', - data_path / db_name)) - - global_dict['basic_db_mass'], global_dict['basic_db_formula'], global_dict['basic_db_idx'] = basic_db - global_dict['halogen_db_mass'], global_dict['halogen_db_formula'], global_dict['halogen_db_idx'] = halogen_db + db_name = "formula_db_" + current_db_version + ".joblib" + basic_db, halogen_db = check_download_joblibload( + "https://github.com/Philipbear/msbuddy/releases/download/msbuddy_data_v0.2.4/formula_db_v0.2.4.joblib", + data_path / db_name, + ) + + ( + global_dict["basic_db_mass"], + global_dict["basic_db_formula"], + global_dict["basic_db_idx"], + ) = basic_db + ( + global_dict["halogen_db_mass"], + global_dict["halogen_db_formula"], + global_dict["halogen_db_idx"], + ) = halogen_db return global_dict @@ -85,25 +96,35 @@ def init_ml_models(global_dict) -> dict: """ # check if ml models exist - if 'model_ms1_ms2' in global_dict: + if "model_ms1_ms2" in global_dict: return global_dict # get root path root_path = Path(__file__).parent # create data folder if not exists - data_path = root_path / 'data' + data_path = root_path / "data" data_path.mkdir(parents=True, exist_ok=True) # load ml - ml_name = 'ml_' + current_model_version + '.joblib' - (global_dict['model_ms1_ms2'], global_dict['model_noms1_ms2'], global_dict['model_ms1_noms2'], global_dict[ - 'model_noms1_noms2'], global_dict['platt_a_0'], global_dict['platt_b_0'], global_dict['platt_a_1'], - global_dict['platt_b_1'], global_dict['platt_a_2'], global_dict['platt_b_2'], global_dict['platt_a_3'], - global_dict['platt_b_3']) = ( - check_download_joblibload( - 'https://github.com/Philipbear/msbuddy/releases/download/msbuddy_data_v0.3.0/ml_v0.3.0.joblib', - data_path / ml_name)) + ml_name = "ml_" + current_model_version + ".joblib" + ( + global_dict["model_ms1_ms2"], + global_dict["model_noms1_ms2"], + global_dict["model_ms1_noms2"], + global_dict["model_noms1_noms2"], + global_dict["platt_a_0"], + global_dict["platt_b_0"], + global_dict["platt_a_1"], + global_dict["platt_b_1"], + global_dict["platt_a_2"], + global_dict["platt_b_2"], + global_dict["platt_a_3"], + global_dict["platt_b_3"], + ) = check_download_joblibload( + "https://github.com/Philipbear/msbuddy/releases/download/msbuddy_data_v0.3.0/ml_v0.3.0.joblib", + data_path / ml_name, + ) return global_dict @@ -114,16 +135,23 @@ def load_mgf(file_path) -> List[MetaFeature]: :param file_path: path to mgf file :return: list of MetaFeature """ - with open(file_path, 'r') as file: + # Initialize tqdm progress bar + pbar = tqdm( + colour="green", + desc="Loaded spectra", + leave=True, + unit=" spectra", + ) + with open(file_path, "r") as file: # create meta_feature_list - meta_feature_list = [] + meta_feature_dict = dict() cnt = 0 for line in file: # empty line _line = line.strip() # remove leading and trailing whitespace if not _line: continue - elif line.startswith('BEGIN IONS'): + elif line.startswith("BEGIN IONS"): # initialize a new spectrum entry mz_arr = np.array([]) int_arr = np.array([]) @@ -134,10 +162,10 @@ def load_mgf(file_path) -> List[MetaFeature]: ms2_spec = True rt = None adduct_str = None - elif line.startswith('END IONS'): + elif line.startswith("END IONS"): # create a new MetaFeature if precursor_mz is None: - raise ValueError('No precursor mz found.') + raise ValueError("No precursor mz found.") if identifier is None: identifier = cnt if charge is None: @@ -152,77 +180,96 @@ def load_mgf(file_path) -> List[MetaFeature]: # continue # create MetaFeature object if the same identifier does not exist - mf_idx = None - for idx, mf in enumerate(meta_feature_list): - if mf.identifier == identifier: - mf_idx = idx - break # if the same identifier exists, add to the existing MetaFeature - if mf_idx is not None: - if ms2_spec and meta_feature_list[mf_idx].ms2_raw is None: - meta_feature_list[mf_idx].ms2_raw = Spectrum(mz_arr, int_arr) if mz_arr.size > 0 else None - elif ms2_spec is False and meta_feature_list[mf_idx].ms1_raw is None: - meta_feature_list[mf_idx].ms1_raw = Spectrum(mz_arr, int_arr) if mz_arr.size > 0 else None + if identifier in meta_feature_dict: + if ms2_spec and meta_feature_dict[identifier].ms2_raw is None: + meta_feature_dict[identifier].ms2_raw = ( + Spectrum(mz_arr, int_arr) if mz_arr.size > 0 else None + ) + elif ( + ms2_spec is False + and meta_feature_dict[identifier].ms1_raw is None + ): + meta_feature_dict[identifier].ms1_raw = ( + Spectrum(mz_arr, int_arr) if mz_arr.size > 0 else None + ) continue # if the same identifier does not exist, create a new MetaFeature else: if ms2_spec: - mf = MetaFeature(mz=precursor_mz, - charge=charge, - rt=rt, - adduct=adduct_str, - ms2=Spectrum(mz_arr, int_arr) if mz_arr.size > 0 else None, - identifier=identifier) + mf = MetaFeature( + mz=precursor_mz, + charge=charge, + rt=rt, + adduct=adduct_str, + ms2=Spectrum(mz_arr, int_arr) if mz_arr.size > 0 else None, + identifier=identifier, + ) else: - mf = MetaFeature(mz=precursor_mz, - charge=charge, - rt=rt, - adduct=adduct_str, - ms1=Spectrum(mz_arr, int_arr) if mz_arr.size > 0 else None, - identifier=identifier) - meta_feature_list.append(mf) + mf = MetaFeature( + mz=precursor_mz, + charge=charge, + rt=rt, + adduct=adduct_str, + ms1=Spectrum(mz_arr, int_arr) if mz_arr.size > 0 else None, + identifier=identifier, + ) + meta_feature_dict[identifier] = mf cnt += 1 + pbar.update(1) continue else: # if line contains '=', it is a key-value pair - if '=' in _line: + if "=" in _line: # split by first '=', in case of multiple '=' in the line - key, value = _line.split('=', 1) + key, value = _line.split("=", 1) key, value = key.strip(), value.strip() # if key (into all upper case) is 'PEPMASS', it is precursor mz - if key.upper() in ['PEPMASS', 'PRECURSOR_MZ', 'PRECURSORMZ']: + if key.upper() in ["PEPMASS", "PRECURSOR_MZ", "PRECURSORMZ"]: precursor_mz = float(value) # identifier - elif key.upper() in ['TITLE', 'FEATURE_ID', 'SPECTRUMID', 'SPECTRUM_ID']: + elif key.upper() in [ + "TITLE", + "FEATURE_ID", + "SPECTRUMID", + "SPECTRUM_ID", + ]: identifier = value.strip() # if key is 'CHARGE' and charge is not set, it is charge - elif key.upper() == 'CHARGE': - if '-' in value: + elif key.upper() == "CHARGE": + if "-" in value: pos_mode = False - value = value.replace('-', '') + value = value.replace("-", "") charge = -int(value) else: pos_mode = True - value = value.replace('+', '') + value = value.replace("+", "") charge = int(value) # if key is 'ION', it is adduct type - elif key.upper() in ['ION', 'IONTYPE', 'ION_TYPE', 'ADDUCT', 'ADDUCTTYPE', 'ADDUCT_TYPE']: + elif key.upper() in [ + "ION", + "IONTYPE", + "ION_TYPE", + "ADDUCT", + "ADDUCTTYPE", + "ADDUCT_TYPE", + ]: adduct_str = value # if key is 'IONMODE', it is ion mode - elif key.upper() in ['IONMODE', 'ION_MODE']: - if value.upper() in ['POSITIVE', 'POS', 'P']: + elif key.upper() in ["IONMODE", "ION_MODE"]: + if value.upper() in ["POSITIVE", "POS", "P"]: pos_mode = True - elif value.upper() in ['NEGATIVE', 'NEG', 'N']: + elif value.upper() in ["NEGATIVE", "NEG", "N"]: pos_mode = False # if key is 'MSLEVEL', it is ms level - elif key.upper() == 'MSLEVEL': - if value == '1': + elif key.upper() == "MSLEVEL": + if value == "1": ms2_spec = False # if key is 'RTINSECONDS', it is rt - elif key.upper() == 'RTINSECONDS' and value != '': + elif key.upper() == "RTINSECONDS" and value != "": rt = float(value) - elif key.upper() == 'RTINMINUTES' and value != '': + elif key.upper() == "RTINMINUTES" and value != "": rt = float(value) * 60 else: # if no '=', it is a spectrum pair, split by '\t' or ' ' @@ -230,7 +277,8 @@ def load_mgf(file_path) -> List[MetaFeature]: mz_arr = np.append(mz_arr, float(this_mz)) int_arr = np.append(int_arr, float(this_int)) - return meta_feature_list + pbar.close() + return list(meta_feature_dict.values()) def _load_usi(usi: str, adduct: Union[str, None] = None) -> MetaFeature: @@ -244,42 +292,47 @@ def _load_usi(usi: str, adduct: Union[str, None] = None) -> MetaFeature: :return: MetaFeature object """ # get spectrum from USI - url = 'https://api.metabolomics-usi.gnps2.org/json/?usi1=' + usi + url = "https://api.metabolomics-usi.gnps2.org/json/?usi1=" + usi response = get(url, timeout=10) json_data = loads(response.text) # check if the USI is valid - if 'error' in json_data: + if "error" in json_data: raise ValueError # get adduct - if adduct == '': + if adduct == "": adduct = None # valid: dict_keys(['n_peaks', 'peaks', 'precursor_charge', 'precursor_mz', 'splash']) # ion mode - charge = json_data['precursor_charge'] + charge = json_data["precursor_charge"] if charge == 0: if adduct is not None: - pos_mode = str(adduct)[-1] != '-' # use adduct if charge is 0 + pos_mode = str(adduct)[-1] != "-" # use adduct if charge is 0 charge = 1 if pos_mode else -1 else: charge = 1 # default to +1 - logging.warning('Charge is 0 and adduct is not provided. Default charge +1 is used.') - - ms2_mz = np.array(json_data['peaks'])[:, 0] - ms2_int = np.array(json_data['peaks'])[:, 1] - - data = MetaFeature(mz=json_data['precursor_mz'], - charge=charge, - adduct=adduct, - ms2=Spectrum(ms2_mz, ms2_int), - identifier=usi) + logging.warning( + "Charge is 0 and adduct is not provided. Default charge +1 is used." + ) + + ms2_mz = np.array(json_data["peaks"])[:, 0] + ms2_int = np.array(json_data["peaks"])[:, 1] + + data = MetaFeature( + mz=json_data["precursor_mz"], + charge=charge, + adduct=adduct, + ms2=Spectrum(ms2_mz, ms2_int), + identifier=usi, + ) return data -def load_usi(usi_list: Union[str, List[str]], - adduct_list: Union[None, str, List[str]] = None) -> List[MetaFeature]: +def load_usi( + usi_list: Union[str, List[str]], adduct_list: Union[None, str, List[str]] = None +) -> List[MetaFeature]: """ Read from a sequence of USI strings and return a list of MetaFeature objects. Invalid USI strings are discarded. @@ -304,7 +357,9 @@ def load_usi(usi_list: Union[str, List[str]], if adduct_list is None: adduct_list = [None] * len(usi_list) elif len(adduct_list) != len(usi_list): - logging.warning('adduct_list and usi_list must have the same length. Default adducts are used.') + logging.warning( + "adduct_list and usi_list must have the same length. Default adducts are used." + ) usi_list = [usi.strip() for usi in usi_list] @@ -316,7 +371,9 @@ def load_usi(usi_list: Union[str, List[str]], seen[item] = True unique_indices.append(idx) else: - logging.warning('Duplicate USI: ' + item + '. Only the first occurrence is used.') + logging.warning( + "Duplicate USI: " + item + ". Only the first occurrence is used." + ) usi_list_unique = [usi_list[idx] for idx in unique_indices] adduct_list_unique = [adduct_list[idx] for idx in unique_indices] @@ -325,16 +382,15 @@ def load_usi(usi_list: Union[str, List[str]], try: data_list.append(_load_usi(usi, adduct)) except: - logging.warning('Invalid USI: ' + usi) + logging.warning("Invalid USI: " + usi) continue return data_list -if __name__ == '__main__': - - mf = _load_usi('mzspec:TINYMASS:01KG1DBJTAEHQGZRPNZPHF8EYN:scan:1') +if __name__ == "__main__": + mf = _load_usi("mzspec:TINYMASS:01KG1DBJTAEHQGZRPNZPHF8EYN:scan:1") print(mf) - + # init_db() ############# # compile all these databases diff --git a/msbuddy/main.py b/msbuddy/main.py index 90cb425..06c6b7e 100644 --- a/msbuddy/main.py +++ b/msbuddy/main.py @@ -42,29 +42,32 @@ class MsbuddyConfig: msbuddy configuration class """ - def __init__(self, - ms_instr: str = None, - ppm: bool = True, - ms1_tol: float = 5, - ms2_tol: float = 10, - halogen: bool = False, - parallel: bool = False, - n_cpu: int = -1, - timeout_secs: float = 300, - batch_size: int = 1000, - c_range: Tuple[int, int] = (0, 80), - h_range: Tuple[int, int] = (0, 150), - n_range: Tuple[int, int] = (0, 20), - o_range: Tuple[int, int] = (0, 30), - p_range: Tuple[int, int] = (0, 10), - s_range: Tuple[int, int] = (0, 15), - f_range: Tuple[int, int] = (0, 20), - cl_range: Tuple[int, int] = (0, 15), - br_range: Tuple[int, int] = (0, 10), - i_range: Tuple[int, int] = (0, 10), - isotope_bin_mztol: float = 0.02, max_isotope_cnt: int = 4, - rel_int_denoise_cutoff: float = 0.01, - top_n_per_50_da: int = 6): + def __init__( + self, + ms_instr: str = None, + ppm: bool = True, + ms1_tol: float = 5, + ms2_tol: float = 10, + halogen: bool = False, + parallel: bool = False, + n_cpu: int = -1, + timeout_secs: float = 300, + batch_size: int = 1000, + c_range: Tuple[int, int] = (0, 80), + h_range: Tuple[int, int] = (0, 150), + n_range: Tuple[int, int] = (0, 20), + o_range: Tuple[int, int] = (0, 30), + p_range: Tuple[int, int] = (0, 10), + s_range: Tuple[int, int] = (0, 15), + f_range: Tuple[int, int] = (0, 20), + cl_range: Tuple[int, int] = (0, 15), + br_range: Tuple[int, int] = (0, 10), + i_range: Tuple[int, int] = (0, 10), + isotope_bin_mztol: float = 0.02, + max_isotope_cnt: int = 4, + rel_int_denoise_cutoff: float = 0.01, + top_n_per_50_da: int = 6, + ): """ :param ms_instr: mass spectrometry instrument, one of "orbitrap, "fticr", "qtof". :param ppm: whether ppm is used for m/z tolerance @@ -106,7 +109,9 @@ def __init__(self, self.ms1_tol = 10 self.ms2_tol = 20 else: - raise ValueError("Invalid MS instrument. Please choose from 'orbitrap', 'fticr', 'qtof' and None.") + raise ValueError( + "Invalid MS instrument. Please choose from 'orbitrap', 'fticr', 'qtof' and None." + ) self.db_mode = 0 if not halogen else 1 self.parallel = parallel @@ -130,10 +135,40 @@ def __init__(self, else: self.batch_size = int(batch_size) - self.ele_lower = np.array([c_range[0], h_range[0], br_range[0], cl_range[0], f_range[0], i_range[0], - 0, n_range[0], 0, o_range[0], p_range[0], s_range[0]], dtype=np.int16) - self.ele_upper = np.array([c_range[1], h_range[1], br_range[1], cl_range[1], f_range[1], i_range[1], - 0, n_range[1], 0, o_range[1], p_range[1], s_range[1]], dtype=np.int16) + self.ele_lower = np.array( + [ + c_range[0], + h_range[0], + br_range[0], + cl_range[0], + f_range[0], + i_range[0], + 0, + n_range[0], + 0, + o_range[0], + p_range[0], + s_range[0], + ], + dtype=np.int16, + ) + self.ele_upper = np.array( + [ + c_range[1], + h_range[1], + br_range[1], + cl_range[1], + f_range[1], + i_range[1], + 0, + n_range[1], + 0, + o_range[1], + p_range[1], + s_range[1], + ], + dtype=np.int16, + ) if not halogen: self.ele_lower[2:6] = 0 self.ele_upper[2:6] = 0 @@ -147,7 +182,9 @@ def __init__(self, # check valid param set if isotope_bin_mztol <= 0: self.isotope_bin_mztol = 0.02 - logging.warning(f"Isotope bin m/z tolerance is set to {self.isotope_bin_mztol}.") + logging.warning( + f"Isotope bin m/z tolerance is set to {self.isotope_bin_mztol}." + ) else: self.isotope_bin_mztol = isotope_bin_mztol @@ -159,7 +196,9 @@ def __init__(self, if rel_int_denoise_cutoff < 0 or rel_int_denoise_cutoff >= 1: self.rel_int_denoise_cutoff = 0.0 - logging.warning(f"Relative intensity denoise cutoff is set to {self.rel_int_denoise_cutoff}.") + logging.warning( + f"Relative intensity denoise cutoff is set to {self.rel_int_denoise_cutoff}." + ) else: self.rel_int_denoise_cutoff = rel_int_denoise_cutoff @@ -175,6 +214,7 @@ class Msbuddy: msbuddy main class msbuddy data is List[MetaFeature]; MetaFeature is a class defined in base/MetaFeature.py """ + # singleton pattern _instance = None @@ -185,7 +225,9 @@ def __new__(cls, *args, **kwargs): def __init__(self, config: Union[MsbuddyConfig, None] = None): - tqdm.write("msbuddy: molecular formula annotation for MS-based small molecule analysis.") + tqdm.write( + "msbuddy: molecular formula annotation for MS-based small molecule analysis." + ) if config is None: self.config = MsbuddyConfig() # default configuration @@ -202,8 +244,11 @@ def update_config(self, **kwargs): global shared_data_dict # Declare it as a global variable shared_data_dict = init_db() # database initialization - def load_usi(self, usi_list: Union[str, List[str]], - adduct_list: Union[None, str, List[str]] = None): + def load_usi( + self, + usi_list: Union[str, List[str]], + adduct_list: Union[None, str, List[str]] = None, + ): self.data = load_usi(usi_list, adduct_list) def load_mgf(self, file_path): @@ -223,7 +268,9 @@ def clear_data(self): """ self.data = None - def _preprocess_and_generate_candidate_formula(self, batch_start_idx: int = 0, batch_end_idx: int = None): + def _preprocess_and_generate_candidate_formula( + self, batch_start_idx: int = 0, batch_end_idx: int = None + ): """ preprocess data and generate candidate formula space :param batch_start_idx: start index of batch @@ -231,7 +278,9 @@ def _preprocess_and_generate_candidate_formula(self, batch_start_idx: int = 0, b :return: None. Update self.data """ - def _preprocess_and_gen_cand_nonparallel(meta_feature: MetaFeature, ps: MsbuddyConfig) -> MetaFeature: + def _preprocess_and_gen_cand_nonparallel( + meta_feature: MetaFeature, ps: MsbuddyConfig + ) -> MetaFeature: """ a wrapper function for data preprocessing and candidate formula space generation :param meta_feature: MetaFeature object @@ -246,13 +295,24 @@ def _preprocess_and_gen_cand_nonparallel(meta_feature: MetaFeature, ps: MsbuddyC # data preprocessing and candidate space generation if self.config.parallel: - with Pool(processes=int(self.config.n_cpu), initializer=_init_pool, - initargs=(shared_data_dict,)) as pool: - async_results = [pool.apply_async(_preprocess_and_gen_cand_parallel, - (mf, self.config)) for mf in batch_data] + with Pool( + processes=int(self.config.n_cpu), + initializer=_init_pool, + initargs=(shared_data_dict,), + ) as pool: + async_results = [ + pool.apply_async( + _preprocess_and_gen_cand_parallel, (mf, self.config) + ) + for mf in batch_data + ] # Initialize tqdm progress bar - pbar = tqdm(total=len(batch_data), colour="green", desc="Candidate space generation", - file=sys.stdout) + pbar = tqdm( + total=len(batch_data), + colour="green", + desc="Candidate space generation", + file=sys.stdout, + ) for i, async_result in enumerate(async_results): pbar.update(1) # Update tqdm progress bar try: @@ -260,25 +320,36 @@ def _preprocess_and_gen_cand_nonparallel(meta_feature: MetaFeature, ps: MsbuddyC modified_mf_ls.append(modified_mf) except: mf = batch_data[i] - logging.warning(f"Timeout for spectrum {mf.identifier}, mz={mf.mz}, rt={mf.rt}, skipped.") + logging.warning( + f"Timeout for spectrum {mf.identifier}, mz={mf.mz}, rt={mf.rt}, skipped." + ) modified_mf_ls.append(mf) pbar.close() # Close tqdm progress bar del async_results else: # normal loop, timeout implemented using timeout_decorator - for mf in tqdm(batch_data, file=sys.stdout, colour="green", desc="Candidate space generation"): + for mf in tqdm( + batch_data, + file=sys.stdout, + colour="green", + desc="Candidate space generation", + ): try: modified_mf = _preprocess_and_gen_cand_nonparallel(mf, self.config) modified_mf_ls.append(modified_mf) except: - logging.warning(f"Timeout for spectrum {mf.identifier}, mz={mf.mz}, rt={mf.rt}, skipped.") + logging.warning( + f"Timeout for spectrum {mf.identifier}, mz={mf.mz}, rt={mf.rt}, skipped." + ) modified_mf_ls.append(mf) # update data self.data[batch_start_idx:batch_end_idx] = modified_mf_ls del modified_mf_ls - def _assign_subformula_annotation(self, batch_start_idx: int = 0, batch_end_idx: int = None): + def _assign_subformula_annotation( + self, batch_start_idx: int = 0, batch_end_idx: int = None + ): """ assign subformula annotation for loaded data, no timeout implemented :param batch_start_idx: start index of batch @@ -290,10 +361,17 @@ def _assign_subformula_annotation(self, batch_start_idx: int = 0, batch_end_idx: if self.config.parallel: with Pool(processes=int(self.config.n_cpu)) as pool: - async_results = [pool.apply_async(_gen_subformula, - (mf, self.config)) for mf in batch_data] - - pbar = tqdm(total=len(batch_data), colour="green", desc="Subformula assignment: ", file=sys.stdout) + async_results = [ + pool.apply_async(_gen_subformula, (mf, self.config)) + for mf in batch_data + ] + + pbar = tqdm( + total=len(batch_data), + colour="green", + desc="Subformula assignment: ", + file=sys.stdout, + ) for i, async_result in enumerate(async_results): pbar.update(1) # Update tqdm progress bar modified_mf = async_result.get() @@ -302,7 +380,12 @@ def _assign_subformula_annotation(self, batch_start_idx: int = 0, batch_end_idx: del async_results else: # normal loop - for mf in tqdm(batch_data, desc="Subformula assignment: ", file=sys.stdout, colour="green"): + for mf in tqdm( + batch_data, + desc="Subformula assignment: ", + file=sys.stdout, + colour="green", + ): modified_mf = _gen_subformula(mf, self.config) modified_mf_ls.append(modified_mf) @@ -328,7 +411,9 @@ def annotate_formula(self): tqdm.write("Job finished.") - def annotate_formula_cmd(self, output_path: pathlib.Path, write_details: bool = False): + def annotate_formula_cmd( + self, output_path: pathlib.Path, write_details: bool = False + ): """ annotate formula for loaded data, command line version write out summary results to a csv file, clear computed data after annotation to save memory @@ -348,14 +433,19 @@ def annotate_formula_cmd(self, output_path: pathlib.Path, write_details: bool = for n in range(n_batch): start_idx, end_idx = self._annotate_formula_main_batch(n, n_batch) tqdm.write("Writing batch results...") - result_summary_df = write_batch_results_cmd(self.data, output_path, write_details, - start_idx, end_idx) - result_summary_df_all = pd.concat([result_summary_df_all, result_summary_df], ignore_index=True, axis=0) + result_summary_df = write_batch_results_cmd( + self.data, output_path, write_details, start_idx, end_idx + ) + result_summary_df_all = pd.concat( + [result_summary_df_all, result_summary_df], ignore_index=True, axis=0 + ) # clear computed data to save memory, convert to None of the same size self.data[start_idx:end_idx] = [None] * (end_idx - start_idx) tqdm.write("Writing summary results to tsv file...") - result_summary_df_all.to_csv(output_path / 'msbuddy_result_summary.tsv', sep="\t", index=False) + result_summary_df_all.to_csv( + output_path / "msbuddy_result_summary.tsv", sep="\t", index=False + ) def _annotate_formula_prepare(self) -> int: """ @@ -367,11 +457,17 @@ def _annotate_formula_prepare(self) -> int: self.data = [mf for mf in self.data if 1 < mf.mz < 1500] cnt_post = len(self.data) if cnt_pre != cnt_post: - tqdm.write(f"{cnt_pre - cnt_post} spectra with precursor mz > 1500 are removed.") + tqdm.write( + f"{cnt_pre - cnt_post} spectra with precursor mz > 1500 are removed." + ) if not self.data: raise ValueError("No data loaded.") - query_str = f"{len(self.data)} queries loaded." if len(self.data) > 1 else "1 query loaded." + query_str = ( + f"{len(self.data)} queries loaded." + if len(self.data) > 1 + else "1 query loaded." + ) tqdm.write(query_str) if self.config.parallel: @@ -380,7 +476,9 @@ def _annotate_formula_prepare(self) -> int: # batches n_batch = int(np.ceil(len(self.data) / self.config.batch_size)) - batch_str = f"{n_batch} batches in total." if n_batch > 1 else "1 batch in total." + batch_str = ( + f"{n_batch} batches in total." if n_batch > 1 else "1 batch in total." + ) tqdm.write(batch_str) return n_batch @@ -403,7 +501,9 @@ def _annotate_formula_main_batch(self, n: int, n_batch: int) -> Tuple[int, int]: self._assign_subformula_annotation(start_idx, end_idx) tqdm.write("Candidate formula ranking...") - predict_formula_probability(self.data, start_idx, end_idx, self.config, shared_data_dict) + predict_formula_probability( + self.data, start_idx, end_idx, self.config, shared_data_dict + ) # FDR calculation calc_fdr(self.data, start_idx, end_idx) @@ -424,9 +524,15 @@ def get_summary(self) -> List[dict]: return result_summary_list - def mass_to_formula(self, mass: float, mass_tol: float = 10., ppm: bool = True, - halogen: bool = False, dbe_cutoff: float = 0.0, - integer_dbe: bool = True) -> List[FormulaResult]: + def mass_to_formula( + self, + mass: float, + mass_tol: float = 10.0, + ppm: bool = True, + halogen: bool = False, + dbe_cutoff: float = 0.0, + integer_dbe: bool = True, + ) -> List[FormulaResult]: """ convert mass to formula, return list of formula strings :param mass: target mass, should be <1500 Da @@ -440,20 +546,33 @@ def mass_to_formula(self, mass: float, mass_tol: float = 10., ppm: bool = True, formulas = query_neutral_mass(mass, mass_tol, ppm, halogen, shared_data_dict) if integer_dbe: - out = [FormulaResult(form_arr_to_str(f.array), f.mass, mass) for f in formulas - if f.dbe >= dbe_cutoff and f.dbe % 1 == 0 and senior_rules(f.array)] + out = [ + FormulaResult(form_arr_to_str(f.array), f.mass, mass) + for f in formulas + if f.dbe >= dbe_cutoff and f.dbe % 1 == 0 and senior_rules(f.array) + ] else: - out = [FormulaResult(form_arr_to_str(f.array), f.mass, mass) for f in formulas - if f.dbe >= dbe_cutoff and senior_rules(f.array)] + out = [ + FormulaResult(form_arr_to_str(f.array), f.mass, mass) + for f in formulas + if f.dbe >= dbe_cutoff and senior_rules(f.array) + ] # sort by absolute mass error, ascending out.sort(key=lambda x: abs(x.mass_error)) return out - def mz_to_formula(self, mz: float, adduct: str = '[M+H]+', - mz_tol: float = 10., ppm: bool = True, halogen: bool = False, - dbe_cutoff: float = 0.0, integer_dbe: bool = True) -> List[FormulaResult]: + def mz_to_formula( + self, + mz: float, + adduct: str = "[M+H]+", + mz_tol: float = 10.0, + ppm: bool = True, + halogen: bool = False, + dbe_cutoff: float = 0.0, + integer_dbe: bool = True, + ) -> List[FormulaResult]: """ convert mz to formula, return list of formula strings :param mz: target mz, should be <1500 Da @@ -473,20 +592,38 @@ def mz_to_formula(self, mz: float, adduct: str = '[M+H]+', ion = Adduct(adduct, pos_mode) if halogen: - formulas, _ = query_precursor_mass(mz, ion, mz_tol, ppm, 1, shared_data_dict) + formulas, _ = query_precursor_mass( + mz, ion, mz_tol, ppm, 1, shared_data_dict + ) else: - formulas, _ = query_precursor_mass(mz, ion, mz_tol, ppm, 0, shared_data_dict) + formulas, _ = query_precursor_mass( + mz, ion, mz_tol, ppm, 0, shared_data_dict + ) ion_int = 1 if pos_mode else -1 if integer_dbe: - out = [FormulaResult(form_arr_to_str(f.array), - (f.mass * ion.m + ion.net_formula.mass - ion_int * 0.00054858) / abs(ion.charge), - mz) for f in formulas if f.dbe >= dbe_cutoff and f.dbe % 1 == 0 and senior_rules(f.array)] + out = [ + FormulaResult( + form_arr_to_str(f.array), + (f.mass * ion.m + ion.net_formula.mass - ion_int * 0.00054858) + / abs(ion.charge), + mz, + ) + for f in formulas + if f.dbe >= dbe_cutoff and f.dbe % 1 == 0 and senior_rules(f.array) + ] else: - out = [FormulaResult(form_arr_to_str(f.array), - (f.mass * ion.m + ion.net_formula.mass - ion_int * 0.00054858) / abs(ion.charge), - mz) for f in formulas if f.dbe >= dbe_cutoff and senior_rules(f.array)] + out = [ + FormulaResult( + form_arr_to_str(f.array), + (f.mass * ion.m + ion.net_formula.mass - ion_int * 0.00054858) + / abs(ion.charge), + mz, + ) + for f in formulas + if f.dbe >= dbe_cutoff and senior_rules(f.array) + ] # sort by absolute mass error, ascending out.sort(key=lambda x: abs(x.mass_error)) @@ -516,7 +653,9 @@ def _init_pool(the_dict): shared_data_dict = the_dict -def _preprocess_and_gen_cand_parallel(meta_feature: MetaFeature, ps: MsbuddyConfig) -> MetaFeature: +def _preprocess_and_gen_cand_parallel( + meta_feature: MetaFeature, ps: MsbuddyConfig +) -> MetaFeature: """ a wrapper function for data preprocessing and candidate formula space generation :param meta_feature: MetaFeature object @@ -551,12 +690,16 @@ def _gen_subformula(mf: MetaFeature, ps: MsbuddyConfig) -> MetaFeature: else: cand_form_exp_ms2_peak_list.append(0) if min(cand_form_exp_ms2_peak_list) > 0: - mf.candidate_formula_list = [cf for cf in mf.candidate_formula_list if len(cf.ms2_raw_explanation) > 0] + mf.candidate_formula_list = [ + cf for cf in mf.candidate_formula_list if len(cf.ms2_raw_explanation) > 0 + ] return mf -def _generate_candidate_formula(mf: MetaFeature, ps: MsbuddyConfig, global_dict) -> MetaFeature: +def _generate_candidate_formula( + mf: MetaFeature, ps: MsbuddyConfig, global_dict +) -> MetaFeature: """ preprocess data and generate candidate formula space :param mf: MetaFeature object @@ -565,25 +708,45 @@ def _generate_candidate_formula(mf: MetaFeature, ps: MsbuddyConfig, global_dict) :return: MetaFeature object """ # data preprocessing - mf.data_preprocess(ps.ppm, ps.ms1_tol, ps.ms2_tol, ps.isotope_bin_mztol, ps.max_isotope_cnt, - ps.rel_int_denoise_cutoff, ps.top_n_per_50_da) + mf.data_preprocess( + ps.ppm, + ps.ms1_tol, + ps.ms2_tol, + ps.isotope_bin_mztol, + ps.max_isotope_cnt, + ps.rel_int_denoise_cutoff, + ps.top_n_per_50_da, + ) # generate candidate formula space - gen_candidate_formula(mf, ps.ppm, ps.ms1_tol, ps.ms2_tol, ps.db_mode, ps.ele_lower, ps.ele_upper, - ps.max_isotope_cnt, global_dict) + gen_candidate_formula( + mf, + ps.ppm, + ps.ms1_tol, + ps.ms2_tol, + ps.db_mode, + ps.ele_lower, + ps.ele_upper, + ps.max_isotope_cnt, + global_dict, + ) return mf -if __name__ == '__main__': +if __name__ == "__main__": import time start = time.time() - msb_config = MsbuddyConfig(ms_instr='orbitrap', # supported: "qtof", "orbitrap" and "fticr" - halogen=True, parallel=True, n_cpu=10) + msb_config = MsbuddyConfig( + ms_instr="orbitrap", # supported: "qtof", "orbitrap" and "fticr" + halogen=True, + parallel=True, + n_cpu=10, + ) msb_engine = Msbuddy(msb_config) - msb_engine.load_mgf('/Users/shipei/Documents/projects/msbuddy/demo/input_file.mgf') + msb_engine.load_mgf("/Users/shipei/Documents/projects/msbuddy/demo/input_file.mgf") # mgf_folder = '/Users/shipei/Documents/projects/msbuddy/results/lcms_datasets/MSV000085143_chagas_neg_orbi' # 83 # mgf_folder = '/Users/shipei/Documents/projects/msbuddy/results/lcms_datasets/MSV000081463_tomato_pos' # 194 @@ -604,7 +767,7 @@ def _generate_candidate_formula(mf: MetaFeature, ps: MsbuddyConfig, global_dict) result = msb_engine.get_summary() print(result) - form_top1 = [r['formula_rank_1'] for r in result] - form_est_fdr = [r['estimated_fdr'] for r in result] + form_top1 = [r["formula_rank_1"] for r in result] + form_est_fdr = [r["estimated_fdr"] for r in result] print(form_top1) print(form_est_fdr) diff --git a/msbuddy/main_cmd.py b/msbuddy/main_cmd.py index cb06f44..ff201e2 100644 --- a/msbuddy/main_cmd.py +++ b/msbuddy/main_cmd.py @@ -22,56 +22,157 @@ def main(): - parser = argparse.ArgumentParser(description="msbuddy command line interface (version 0.3.14)") - parser.add_argument('-mgf', type=str, help='Path to the MGF file.') - parser.add_argument('-usi', type=str, help='A single USI string.') - parser.add_argument('-csv', type=str, - help='Path to the CSV file containing USI strings in the first column (no header row).') - parser.add_argument('-output', '-o', type=str, help='The output file path.') - parser.add_argument('-details', '-d', action='store_true', - help='Store true. Whether to write detailed results. Default: detailed results are not written.') - parser.add_argument('-ms_instr', '-ms', type=str, default=None, - help='MS instrument type. Supported types: orbitrap, qtof, fticr.') - parser.add_argument('-use_Da', action='store_true', - help='Store true. Whether to use Dalton for mass tolerance. Default: ppm is used.') - parser.add_argument('-ms1_tol', type=float, default=5, help='MS1 tolerance. Default: 5.') - parser.add_argument('-ms2_tol', type=float, default=10, help='MS2 tolerance. Default: 10.') - parser.add_argument('-halogen', '-hal', action='store_true', - help='Store true. Whether to consider halogen atoms FClBrI. Default: halogen atoms are not considered.') - parser.add_argument('-parallel', '-p', action='store_true', - help='Store true. Whether to use parallel computing. Default: parallel computing is disabled.') - parser.add_argument('-n_cpu', type=int, default=-1, help='Number of CPUs to use. Default: -1, use all CPUs.') - parser.add_argument('-timeout_secs', '-t', type=int, default=300, help='Timeout in seconds. Default: 300.') - parser.add_argument('-batch_size', '-bs', type=int, default=5000, - help='Batch size. Default: 5000. A larger batch size needs more memory, but is faster.') - parser.add_argument('-c_min', type=int, default=0, help='Minimum number of C atoms. Default: 0.') - parser.add_argument('-c_max', type=int, default=80, help='Maximum number of C atoms. Default: 80.') - parser.add_argument('-h_min', type=int, default=0, help='Minimum number of H atoms. Default: 0.') - parser.add_argument('-h_max', type=int, default=150, help='Maximum number of H atoms. Default: 150.') - parser.add_argument('-n_min', type=int, default=0, help='Minimum number of N atoms. Default: 0.') - parser.add_argument('-n_max', type=int, default=20, help='Maximum number of N atoms. Default: 20.') - parser.add_argument('-o_min', type=int, default=0, help='Minimum number of O atoms. Default: 0.') - parser.add_argument('-o_max', type=int, default=30, help='Maximum number of O atoms. Default: 30.') - parser.add_argument('-p_min', type=int, default=0, help='Minimum number of P atoms. Default: 0.') - parser.add_argument('-p_max', type=int, default=10, help='Maximum number of P atoms. Default: 10.') - parser.add_argument('-s_min', type=int, default=0, help='Minimum number of S atoms. Default: 0.') - parser.add_argument('-s_max', type=int, default=15, help='Maximum number of S atoms. Default: 15.') - parser.add_argument('-f_min', type=int, default=0, help='Minimum number of F atoms. Default: 0.') - parser.add_argument('-f_max', type=int, default=20, help='Maximum number of F atoms. Default: 20.') - parser.add_argument('-cl_min', type=int, default=0, help='Minimum number of Cl atoms. Default: 0.') - parser.add_argument('-cl_max', type=int, default=15, help='Maximum number of Cl atoms. Default: 15.') - parser.add_argument('-br_min', type=int, default=0, help='Minimum number of Br atoms. Default: 0.') - parser.add_argument('-br_max', type=int, default=10, help='Maximum number of Br atoms. Default: 10.') - parser.add_argument('-i_min', type=int, default=0, help='Minimum number of I atoms. Default: 0.') - parser.add_argument('-i_max', type=int, default=10, help='Maximum number of I atoms. Default: 10.') - parser.add_argument('-isotope_bin_mztol', type=float, default=0.02, - help='m/z tolerance for isotope binning, used for MS1 isotope pattern, in Dalton. Default: 0.02.') - parser.add_argument('-max_isotope_cnt', type=int, default=4, - help='Maximum isotope count, used for MS1 isotope pattern. Default: 4.') - parser.add_argument('-rel_int_denoise_cutoff', type=float, default=0.01, - help='Relative intensity cutoff, used for MS2 denoise. Default: 0.01.') - parser.add_argument('-top_n_per_50_da', type=int, default=6, - help='Top n peaks per 50 Da, used for MS2 denoise. Default: 6.') + parser = argparse.ArgumentParser( + description="msbuddy command line interface (version 0.3.14)" + ) + parser.add_argument("-mgf", type=str, help="Path to the MGF file.") + parser.add_argument("-usi", type=str, help="A single USI string.") + parser.add_argument( + "-csv", + type=str, + help="Path to the CSV file containing USI strings in the first column (no header row).", + ) + parser.add_argument("-output", "-o", type=str, help="The output file path.") + parser.add_argument( + "-details", + "-d", + action="store_true", + help="Store true. Whether to write detailed results. Default: detailed results are not written.", + ) + parser.add_argument( + "-ms_instr", + "-ms", + type=str, + default=None, + help="MS instrument type. Supported types: orbitrap, qtof, fticr.", + ) + parser.add_argument( + "-use_Da", + action="store_true", + help="Store true. Whether to use Dalton for mass tolerance. Default: ppm is used.", + ) + parser.add_argument( + "-ms1_tol", type=float, default=5, help="MS1 tolerance. Default: 5." + ) + parser.add_argument( + "-ms2_tol", type=float, default=10, help="MS2 tolerance. Default: 10." + ) + parser.add_argument( + "-halogen", + "-hal", + action="store_true", + help="Store true. Whether to consider halogen atoms FClBrI. Default: halogen atoms are not considered.", + ) + parser.add_argument( + "-parallel", + "-p", + action="store_true", + help="Store true. Whether to use parallel computing. Default: parallel computing is disabled.", + ) + parser.add_argument( + "-n_cpu", + type=int, + default=-1, + help="Number of CPUs to use. Default: -1, use all CPUs.", + ) + parser.add_argument( + "-timeout_secs", + "-t", + type=int, + default=300, + help="Timeout in seconds. Default: 300.", + ) + parser.add_argument( + "-batch_size", + "-bs", + type=int, + default=5000, + help="Batch size. Default: 5000. A larger batch size needs more memory, but is faster.", + ) + parser.add_argument( + "-c_min", type=int, default=0, help="Minimum number of C atoms. Default: 0." + ) + parser.add_argument( + "-c_max", type=int, default=80, help="Maximum number of C atoms. Default: 80." + ) + parser.add_argument( + "-h_min", type=int, default=0, help="Minimum number of H atoms. Default: 0." + ) + parser.add_argument( + "-h_max", type=int, default=150, help="Maximum number of H atoms. Default: 150." + ) + parser.add_argument( + "-n_min", type=int, default=0, help="Minimum number of N atoms. Default: 0." + ) + parser.add_argument( + "-n_max", type=int, default=20, help="Maximum number of N atoms. Default: 20." + ) + parser.add_argument( + "-o_min", type=int, default=0, help="Minimum number of O atoms. Default: 0." + ) + parser.add_argument( + "-o_max", type=int, default=30, help="Maximum number of O atoms. Default: 30." + ) + parser.add_argument( + "-p_min", type=int, default=0, help="Minimum number of P atoms. Default: 0." + ) + parser.add_argument( + "-p_max", type=int, default=10, help="Maximum number of P atoms. Default: 10." + ) + parser.add_argument( + "-s_min", type=int, default=0, help="Minimum number of S atoms. Default: 0." + ) + parser.add_argument( + "-s_max", type=int, default=15, help="Maximum number of S atoms. Default: 15." + ) + parser.add_argument( + "-f_min", type=int, default=0, help="Minimum number of F atoms. Default: 0." + ) + parser.add_argument( + "-f_max", type=int, default=20, help="Maximum number of F atoms. Default: 20." + ) + parser.add_argument( + "-cl_min", type=int, default=0, help="Minimum number of Cl atoms. Default: 0." + ) + parser.add_argument( + "-cl_max", type=int, default=15, help="Maximum number of Cl atoms. Default: 15." + ) + parser.add_argument( + "-br_min", type=int, default=0, help="Minimum number of Br atoms. Default: 0." + ) + parser.add_argument( + "-br_max", type=int, default=10, help="Maximum number of Br atoms. Default: 10." + ) + parser.add_argument( + "-i_min", type=int, default=0, help="Minimum number of I atoms. Default: 0." + ) + parser.add_argument( + "-i_max", type=int, default=10, help="Maximum number of I atoms. Default: 10." + ) + parser.add_argument( + "-isotope_bin_mztol", + type=float, + default=0.02, + help="m/z tolerance for isotope binning, used for MS1 isotope pattern, in Dalton. Default: 0.02.", + ) + parser.add_argument( + "-max_isotope_cnt", + type=int, + default=4, + help="Maximum isotope count, used for MS1 isotope pattern. Default: 4.", + ) + parser.add_argument( + "-rel_int_denoise_cutoff", + type=float, + default=0.01, + help="Relative intensity cutoff, used for MS2 denoise. Default: 0.01.", + ) + parser.add_argument( + "-top_n_per_50_da", + type=int, + default=6, + help="Top n peaks per 50 Da, used for MS2 denoise. Default: 6.", + ) args = parser.parse_args() @@ -80,24 +181,38 @@ def main(): msb_config = MsbuddyConfig( ms_instr=args.ms_instr, ppm=~args.use_Da, - ms1_tol=args.ms1_tol, ms2_tol=args.ms2_tol, halogen=args.halogen, - parallel=args.parallel, n_cpu=args.n_cpu, - timeout_secs=args.timeout_secs, batch_size=args.batch_size, - c_range=(args.c_min, args.c_max), h_range=(args.h_min, args.h_max), n_range=(args.n_min, args.n_max), - o_range=(args.o_min, args.o_max), p_range=(args.p_min, args.p_max), s_range=(args.s_min, args.s_max), - f_range=(args.f_min, args.f_max), cl_range=(args.cl_min, args.cl_max), br_range=(args.br_min, args.br_max), + ms1_tol=args.ms1_tol, + ms2_tol=args.ms2_tol, + halogen=args.halogen, + parallel=args.parallel, + n_cpu=args.n_cpu, + timeout_secs=args.timeout_secs, + batch_size=args.batch_size, + c_range=(args.c_min, args.c_max), + h_range=(args.h_min, args.h_max), + n_range=(args.n_min, args.n_max), + o_range=(args.o_min, args.o_max), + p_range=(args.p_min, args.p_max), + s_range=(args.s_min, args.s_max), + f_range=(args.f_min, args.f_max), + cl_range=(args.cl_min, args.cl_max), + br_range=(args.br_min, args.br_max), i_range=(args.i_min, args.i_max), - isotope_bin_mztol=args.isotope_bin_mztol, max_isotope_cnt=args.max_isotope_cnt, - rel_int_denoise_cutoff=args.rel_int_denoise_cutoff, top_n_per_50_da=args.top_n_per_50_da + isotope_bin_mztol=args.isotope_bin_mztol, + max_isotope_cnt=args.max_isotope_cnt, + rel_int_denoise_cutoff=args.rel_int_denoise_cutoff, + top_n_per_50_da=args.top_n_per_50_da, ) if args.output: output_path = pathlib.Path(args.output) elif args.mgf or args.csv: # use the parent directory of the input file as the output directory - output_path = pathlib.Path(args.mgf if args.mgf else args.csv).parent / 'msbuddy_output' + output_path = ( + pathlib.Path(args.mgf if args.mgf else args.csv).parent / "msbuddy_output" + ) else: - raise ValueError('Please specify the output path.') + raise ValueError("Please specify the output path.") engine = Msbuddy(msb_config) @@ -110,17 +225,18 @@ def main(): df = pd.read_csv(args.csv, keep_default_na=False, na_values=None) # if df has >1 columns, treat the 2nd column as adduct strings if df.shape[1] > 1: - engine.load_usi(usi_list=df.iloc[:, 0].tolist(), - adduct_list=df.iloc[:, 1].tolist()) + engine.load_usi( + usi_list=df.iloc[:, 0].tolist(), adduct_list=df.iloc[:, 1].tolist() + ) else: engine.load_usi(df.iloc[:, 0].tolist()) else: - raise ValueError('Please specify the input data source.') + raise ValueError("Please specify the input data source.") engine.annotate_formula_cmd(output_path, write_details=args.details) - print('Job finished.') + print("Job finished.") -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/msbuddy/ml.py b/msbuddy/ml.py index dac81f3..4a3201c 100644 --- a/msbuddy/ml.py +++ b/msbuddy/ml.py @@ -13,6 +13,7 @@ Description: machine learning functions: feature generation, prediction, etc. False discovery rate (FDR) estimation """ + import math import sys import warnings @@ -25,7 +26,7 @@ from msbuddy.query import common_nl_from_array, check_formula_existence # ignore warnings -warnings.filterwarnings('ignore') +warnings.filterwarnings("ignore") def _gen_arr_from_buddy_data(buddy_data) -> (np.array, np.array, np.array): @@ -61,11 +62,21 @@ def _gen_form_feature(all_cf_arr, dbe_arr, mass_arr) -> np.array: :return: numpy array of ML features """ # calculate ML features - ele_sum_1_arr = all_cf_arr[:, 2] + all_cf_arr[:, 3] + all_cf_arr[:, 4] + all_cf_arr[:, 5] + \ - all_cf_arr[:, 6] + all_cf_arr[:, 8] + ele_sum_1_arr = ( + all_cf_arr[:, 2] + + all_cf_arr[:, 3] + + all_cf_arr[:, 4] + + all_cf_arr[:, 5] + + all_cf_arr[:, 6] + + all_cf_arr[:, 8] + ) ele_sum_2_arr = ele_sum_1_arr + all_cf_arr[:, 10] + all_cf_arr[:, 11] - chon_arr = np.clip(ele_sum_2_arr, 0, 1) # whether other eles other than C, H, O, N exist - chonps_arr = np.clip(ele_sum_1_arr, 0, 1) # whether other eles other than C, H, O, N, P, S exist + chon_arr = np.clip( + ele_sum_2_arr, 0, 1 + ) # whether other eles other than C, H, O, N exist + chonps_arr = np.clip( + ele_sum_1_arr, 0, 1 + ) # whether other eles other than C, H, O, N, P, S exist hal_arr = np.sum(all_cf_arr[:, 2:6], axis=1) # sum of halogen atoms ta_arr = np.sum(all_cf_arr, axis=1) # total number of atoms @@ -73,11 +84,24 @@ def _gen_form_feature(all_cf_arr, dbe_arr, mass_arr) -> np.array: cl_exist_arr = np.clip(all_cf_arr[:, 3], 0, 1) # whether Cl exists br_exist_arr = np.clip(all_cf_arr[:, 2], 0, 1) # whether Br exists i_exist_arr = np.clip(all_cf_arr[:, 5], 0, 1) # whether I exists - hal_ele_type_arr = f_exist_arr + cl_exist_arr + br_exist_arr + i_exist_arr # number of halogen elements - hal_two = np.clip(hal_ele_type_arr - 1, 0, 1) # whether more than one halogen element exists - hal_three = np.clip(hal_ele_type_arr - 2, 0, 1) # whether more than two halogen elements exist - senior_1_1_arr = (6 * all_cf_arr[:, 11] + 5 * all_cf_arr[:, 10] + 4 * all_cf_arr[:, 0] + 3 * all_cf_arr[:, 7] + - 2 * all_cf_arr[:, 9] + all_cf_arr[:, 1] + hal_arr) + hal_ele_type_arr = ( + f_exist_arr + cl_exist_arr + br_exist_arr + i_exist_arr + ) # number of halogen elements + hal_two = np.clip( + hal_ele_type_arr - 1, 0, 1 + ) # whether more than one halogen element exists + hal_three = np.clip( + hal_ele_type_arr - 2, 0, 1 + ) # whether more than two halogen elements exist + senior_1_1_arr = ( + 6 * all_cf_arr[:, 11] + + 5 * all_cf_arr[:, 10] + + 4 * all_cf_arr[:, 0] + + 3 * all_cf_arr[:, 7] + + 2 * all_cf_arr[:, 9] + + all_cf_arr[:, 1] + + hal_arr + ) senior_1_2_arr = all_cf_arr[:, 7] + all_cf_arr[:, 10] + all_cf_arr[:, 1] + hal_arr # halogen to H ratio, fill 0 if H = 0 @@ -100,30 +124,63 @@ def _gen_form_feature(all_cf_arr, dbe_arr, mass_arr) -> np.array: chno = all_cf_arr[i, 0] + all_cf_arr[i, 1] + all_cf_arr[i, 7] + all_cf_arr[i, 9] # if C > 0 if all_cf_arr[i, 0] > 0: - out[i, :] = [1 - chon_arr[i], 1 - chonps_arr[i], - all_cf_arr[i, 0] / ta, all_cf_arr[i, 1] / ta, - all_cf_arr[i, 7] / ta, - all_cf_arr[i, 9] / ta, all_cf_arr[i, 10] / ta, - all_cf_arr[i, 11] / ta, chno / ta, - hal_arr[i] / ta, senior_1_1_arr[i], senior_1_2_arr[i], 2 * ta - 1, dbe_arr[i], - np.sqrt(dbe_arr[i] / mass_arr[i]), dbe_arr[i] / np.power(mass_arr[i] / 100, 2 / 3), - all_cf_arr[i, 1] / all_cf_arr[i, 0], - all_cf_arr[i, 7] / all_cf_arr[i, 0], - all_cf_arr[i, 9] / all_cf_arr[i, 0], - all_cf_arr[i, 10] / all_cf_arr[i, 0], - all_cf_arr[i, 11] / all_cf_arr[i, 0], - hal_arr[i] / all_cf_arr[i, 0], - hal_h_arr[i], o_p_arr[i], hal_two[i], hal_three[i]] + out[i, :] = [ + 1 - chon_arr[i], + 1 - chonps_arr[i], + all_cf_arr[i, 0] / ta, + all_cf_arr[i, 1] / ta, + all_cf_arr[i, 7] / ta, + all_cf_arr[i, 9] / ta, + all_cf_arr[i, 10] / ta, + all_cf_arr[i, 11] / ta, + chno / ta, + hal_arr[i] / ta, + senior_1_1_arr[i], + senior_1_2_arr[i], + 2 * ta - 1, + dbe_arr[i], + np.sqrt(dbe_arr[i] / mass_arr[i]), + dbe_arr[i] / np.power(mass_arr[i] / 100, 2 / 3), + all_cf_arr[i, 1] / all_cf_arr[i, 0], + all_cf_arr[i, 7] / all_cf_arr[i, 0], + all_cf_arr[i, 9] / all_cf_arr[i, 0], + all_cf_arr[i, 10] / all_cf_arr[i, 0], + all_cf_arr[i, 11] / all_cf_arr[i, 0], + hal_arr[i] / all_cf_arr[i, 0], + hal_h_arr[i], + o_p_arr[i], + hal_two[i], + hal_three[i], + ] else: - out[i, :] = [1 - chon_arr[i], 1 - chonps_arr[i], - all_cf_arr[i, 0] / ta, all_cf_arr[i, 1] / ta, - all_cf_arr[i, 7] / ta, - all_cf_arr[i, 9] / ta, all_cf_arr[i, 10] / ta, - all_cf_arr[i, 11] / ta, chno / ta, - hal_arr[i] / ta, senior_1_1_arr[i], senior_1_2_arr[i], 2 * ta - 1, dbe_arr[i], - np.sqrt(dbe_arr[i] / mass_arr[i]), dbe_arr[i] / np.power(mass_arr[i] / 100, 2 / 3), - 0, 0, 0, 0, 0, 0, - hal_h_arr[i], o_p_arr[i], hal_two[i], hal_three[i]] + out[i, :] = [ + 1 - chon_arr[i], + 1 - chonps_arr[i], + all_cf_arr[i, 0] / ta, + all_cf_arr[i, 1] / ta, + all_cf_arr[i, 7] / ta, + all_cf_arr[i, 9] / ta, + all_cf_arr[i, 10] / ta, + all_cf_arr[i, 11] / ta, + chno / ta, + hal_arr[i] / ta, + senior_1_1_arr[i], + senior_1_2_arr[i], + 2 * ta - 1, + dbe_arr[i], + np.sqrt(dbe_arr[i] / mass_arr[i]), + dbe_arr[i] / np.power(mass_arr[i] / 100, 2 / 3), + 0, + 0, + 0, + 0, + 0, + 0, + hal_h_arr[i], + o_p_arr[i], + hal_two[i], + hal_three[i], + ] return out @@ -146,7 +203,9 @@ def _fill_form_feature_arr_in_batch_data(batch_data, feature_arr) -> None: return -def gen_ml_feature(meta_feature_list, ppm: bool, ms1_tol: float, ms2_tol: float, gd) -> np.array: +def gen_ml_feature( + meta_feature_list, ppm: bool, ms1_tol: float, ms2_tol: float, gd +) -> np.array: """ generate ML features for all metabolic features :param meta_feature_list: List of MetaFeature objects @@ -181,7 +240,9 @@ def gen_ml_feature(meta_feature_list, ppm: bool, ms1_tol: float, ms2_tol: float, return total_X_arr -def gen_ml_feature_single(meta_feature, cand_form, ppm: bool, ms1_tol: float, ms2_tol: float, gd) -> np.array: +def gen_ml_feature_single( + meta_feature, cand_form, ppm: bool, ms1_tol: float, ms2_tol: float, gd +) -> np.array: """ generate ML features for a single candidate formula :param meta_feature: MetaFeature object @@ -197,32 +258,56 @@ def gen_ml_feature_single(meta_feature, cand_form, ppm: bool, ms1_tol: float, ms # mz error in ppm theo_mass = cand_form.charged_formula.mass / abs(this_adduct.charge) - mz_error = (meta_feature.mz - theo_mass) / theo_mass * 1e6 if ppm else meta_feature.mz - theo_mass + mz_error = ( + (meta_feature.mz - theo_mass) / theo_mass * 1e6 + if ppm + else meta_feature.mz - theo_mass + ) mz_error_log_p = _calc_log_p_norm(mz_error, ms1_tol / 3) # precursor charged formula pre_charged_arr = cand_form.charged_formula.array pre_dbe = cand_form.charged_formula.dbe - pre_h2c = pre_charged_arr[1] / pre_charged_arr[0] if pre_charged_arr[0] > 0 else 0 # no carbon, assign 0 + pre_h2c = ( + pre_charged_arr[1] / pre_charged_arr[0] if pre_charged_arr[0] > 0 else 0 + ) # no carbon, assign 0 # MS1 isotope similarity - ms1_iso_sim = cand_form.ms1_isotope_similarity if cand_form.ms1_isotope_similarity else 0 + ms1_iso_sim = ( + cand_form.ms1_isotope_similarity if cand_form.ms1_isotope_similarity else 0 + ) # MS/MS-related features - ms2_feature_arr = _gen_ms2_feature(meta_feature, cand_form, pre_dbe, pre_h2c, ppm, ms2_tol, gd) + ms2_feature_arr = _gen_ms2_feature( + meta_feature, cand_form, pre_dbe, pre_h2c, ppm, ms2_tol, gd + ) # pos mode bool pos_mode = 1 if this_adduct.charge > 0 else 0 # generate output array - out = np.concatenate((np.array([ms1_iso_sim]), np.array([mz_error_log_p]), np.array([pos_mode]), # 3 - cand_form.formula_feature_array, ms2_feature_arr)) # 26 + 24 + out = np.concatenate( + ( + np.array([ms1_iso_sim]), + np.array([mz_error_log_p]), + np.array([pos_mode]), # 3 + cand_form.formula_feature_array, + ms2_feature_arr, + ) + ) # 26 + 24 return out -def _gen_ms2_feature(meta_feature, cand_form, pre_dbe: float, pre_h2c: float, - ppm: bool, ms2_tol: float, gd) -> np.array: +def _gen_ms2_feature( + meta_feature, + cand_form, + pre_dbe: float, + pre_h2c: float, + ppm: bool, + ms2_tol: float, + gd, +) -> np.array: """ generate MS/MS-related features for a single candidate formula :param meta_feature: MetaFeature object @@ -253,19 +338,27 @@ def _gen_ms2_feature(meta_feature, cand_form, pre_dbe: float, pre_h2c: float, # explained fragment ion intensity percentage exp_frag_int_pct = np.sum(exp_int_arr) / np.sum(valid_int_arr) - frag_form_list = ms2_explanation.explanation_list # list of fragment formulas, Formula objects + frag_form_list = ( + ms2_explanation.explanation_list + ) # list of fragment formulas, Formula objects pos_mode = meta_feature.adduct.pos_mode # check db existence of all explained fragments and neutral losses - frag_db_existed, frag_common = np.array([], dtype=bool), np.array([], dtype=bool) + frag_db_existed, frag_common = ( + np.array([], dtype=bool), + np.array([], dtype=bool), + ) nl_db_existed, nl_common = np.array([], dtype=bool), np.array([], dtype=bool) for f in frag_form_list: - frag_db_bool, frag_common_bool = check_formula_existence(f.array, pos_mode, True, gd) + frag_db_bool, frag_common_bool = check_formula_existence( + f.array, pos_mode, True, gd + ) frag_db_existed = np.append(frag_db_existed, frag_db_bool) frag_common = np.append(frag_common, frag_common_bool) - nl_db_bool, nl_common_bool = check_formula_existence(cand_form.charged_formula.array - f.array, - pos_mode, False, gd) + nl_db_bool, nl_common_bool = check_formula_existence( + cand_form.charged_formula.array - f.array, pos_mode, False, gd + ) nl_db_existed = np.append(nl_db_existed, nl_db_bool) nl_common = np.append(nl_common, nl_common_bool) @@ -279,9 +372,13 @@ def _gen_ms2_feature(meta_feature, cand_form, pre_dbe: float, pre_h2c: float, exp_db_fragnl_cnt_pct = np.sum(fragnl_db_existed) / len(valid_idx_arr) # explained and db existed fragment/nl ion intensity percentage - exp_db_frag_int_pct = np.sum(exp_int_arr[frag_db_existed]) / np.sum(valid_int_arr) + exp_db_frag_int_pct = np.sum(exp_int_arr[frag_db_existed]) / np.sum( + valid_int_arr + ) exp_db_nl_int_pct = np.sum(exp_int_arr[nl_db_existed]) / np.sum(valid_int_arr) - exp_db_fragnl_int_pct = np.sum(exp_int_arr[fragnl_db_existed]) / np.sum(valid_int_arr) + exp_db_fragnl_int_pct = np.sum(exp_int_arr[fragnl_db_existed]) / np.sum( + valid_int_arr + ) # common fragment/nl ion count percentage exp_common_frag_cnt_pct = np.sum(frag_common) / len(valid_idx_arr) @@ -289,48 +386,109 @@ def _gen_ms2_feature(meta_feature, cand_form, pre_dbe: float, pre_h2c: float, exp_common_fragnl_cnt_pct = np.sum(fragnl_common) / len(valid_idx_arr) # common fragment/nl ion intensity percentage - exp_common_frag_int_pct = np.sum(exp_int_arr[frag_common]) / np.sum(valid_int_arr) + exp_common_frag_int_pct = np.sum(exp_int_arr[frag_common]) / np.sum( + valid_int_arr + ) exp_common_nl_int_pct = np.sum(exp_int_arr[nl_common]) / np.sum(valid_int_arr) - exp_common_fragnl_int_pct = np.sum(exp_int_arr[fragnl_common]) / np.sum(valid_int_arr) + exp_common_fragnl_int_pct = np.sum(exp_int_arr[fragnl_common]) / np.sum( + valid_int_arr + ) # subformula count: how many frags are subformula of other frags - subform_score, subform_common_loss_score = _calc_subformula_score(frag_form_list, gd) + subform_score, subform_common_loss_score = _calc_subformula_score( + frag_form_list, gd + ) # radical ion count percentage (out of all explained fragment ions) - radical_cnt_pct = np.sum([1 for frag_form in frag_form_list if frag_form.dbe % 1 == 0]) / len(frag_form_list) + radical_cnt_pct = np.sum( + [1 for frag_form in frag_form_list if frag_form.dbe % 1 == 0] + ) / len(frag_form_list) # normalized explained intensity array normed_exp_int_arr = exp_int_arr / np.sum(exp_int_arr) # weighted average of fragment DBEs - frag_dbe_wavg = np.sum(np.array([frag_form.dbe for frag_form in frag_form_list]) * normed_exp_int_arr) + frag_dbe_wavg = np.sum( + np.array([frag_form.dbe for frag_form in frag_form_list]) + * normed_exp_int_arr + ) # weighted average of fragment H/C ratios - frag_h2c_wavg = np.sum(np.array([frag_form.array[1] / frag_form.array[0] if frag_form.array[0] > 0 else pre_h2c - for frag_form in frag_form_list]) * normed_exp_int_arr) + frag_h2c_wavg = np.sum( + np.array( + [ + frag_form.array[1] / frag_form.array[0] + if frag_form.array[0] > 0 + else pre_h2c + for frag_form in frag_form_list + ] + ) + * normed_exp_int_arr + ) # weighted average of fragment m/z ppm errors if ppm: - frag_mz_err_wavg = np.sum(np.array([_calc_log_p_norm((frag_form.mass - mz) / frag_form.mass * 1e6, - ms2_tol / 3) - for frag_form, mz in - zip(frag_form_list, exp_mz_arr)]) * normed_exp_int_arr) + frag_mz_err_wavg = np.sum( + np.array( + [ + _calc_log_p_norm( + (frag_form.mass - mz) / frag_form.mass * 1e6, ms2_tol / 3 + ) + for frag_form, mz in zip(frag_form_list, exp_mz_arr) + ] + ) + * normed_exp_int_arr + ) else: - frag_mz_err_wavg = np.sum(np.array([_calc_log_p_norm(frag_form.mass - mz, ms2_tol / 3) - for frag_form, mz in - zip(frag_form_list, exp_mz_arr)]) * normed_exp_int_arr) + frag_mz_err_wavg = np.sum( + np.array( + [ + _calc_log_p_norm(frag_form.mass - mz, ms2_tol / 3) + for frag_form, mz in zip(frag_form_list, exp_mz_arr) + ] + ) + * normed_exp_int_arr + ) # weighted average of fragment-nl DBE difference - frag_nl_dbe_diff_wavg = np.sum(np.array([frag_form.dbe - (pre_dbe - frag_form.dbe + 1) - for frag_form in frag_form_list]) * normed_exp_int_arr) - - out_arr = np.array([exp_frag_cnt_pct, exp_frag_int_pct, exp_db_frag_cnt_pct, exp_db_nl_cnt_pct, - exp_db_fragnl_cnt_pct, exp_db_frag_int_pct, exp_db_nl_int_pct, exp_db_fragnl_int_pct, - exp_common_frag_cnt_pct, exp_common_nl_cnt_pct, exp_common_fragnl_cnt_pct, - exp_common_frag_int_pct, exp_common_nl_int_pct, exp_common_fragnl_int_pct, - subform_score, subform_common_loss_score, - radical_cnt_pct, frag_dbe_wavg, frag_h2c_wavg, frag_mz_err_wavg, frag_nl_dbe_diff_wavg, - len(valid_idx_arr), math.sqrt(exp_frag_cnt_pct), math.sqrt(exp_frag_int_pct)]) + frag_nl_dbe_diff_wavg = np.sum( + np.array( + [ + frag_form.dbe - (pre_dbe - frag_form.dbe + 1) + for frag_form in frag_form_list + ] + ) + * normed_exp_int_arr + ) + + out_arr = np.array( + [ + exp_frag_cnt_pct, + exp_frag_int_pct, + exp_db_frag_cnt_pct, + exp_db_nl_cnt_pct, + exp_db_fragnl_cnt_pct, + exp_db_frag_int_pct, + exp_db_nl_int_pct, + exp_db_fragnl_int_pct, + exp_common_frag_cnt_pct, + exp_common_nl_cnt_pct, + exp_common_fragnl_cnt_pct, + exp_common_frag_int_pct, + exp_common_nl_int_pct, + exp_common_fragnl_int_pct, + subform_score, + subform_common_loss_score, + radical_cnt_pct, + frag_dbe_wavg, + frag_h2c_wavg, + frag_mz_err_wavg, + frag_nl_dbe_diff_wavg, + len(valid_idx_arr), + math.sqrt(exp_frag_cnt_pct), + math.sqrt(exp_frag_int_pct), + ] + ) else: out_arr = np.array([0] * 24) @@ -360,11 +518,15 @@ def _calc_subformula_score(frag_form_arr, gd) -> (float, float): return 0, 0 # subformula check & subformula common loss check - subform_cnt, subform_common_loss_cnt = _subformula_check(all_frag_arr, gd['common_loss_db']) + subform_cnt, subform_common_loss_cnt = _subformula_check( + all_frag_arr, gd["common_loss_db"] + ) # generate scores, normalized by the number of all possible combinations subform_score = 2 * subform_cnt / (exp_frag_cnt * (exp_frag_cnt - 1)) - subform_common_loss_score = 2 * subform_common_loss_cnt / (exp_frag_cnt * (exp_frag_cnt - 1)) + subform_common_loss_score = ( + 2 * subform_common_loss_cnt / (exp_frag_cnt * (exp_frag_cnt - 1)) + ) return subform_score, subform_common_loss_score @@ -384,7 +546,11 @@ def _subformula_check(all_frag_arr: np.array, nl_db: np.array): for j in range(i + 1, len(all_frag_arr)): delta_arr = all_frag_arr[j] - all_frag_arr[i] # check if subformula; check only C > 0, N > 0 - if np.all(delta_arr >= 0) and np.sum(delta_arr[1:]) > 0 and (np.sum(delta_arr) - delta_arr[7]) > 0: + if ( + np.all(delta_arr >= 0) + and np.sum(delta_arr[1:]) > 0 + and (np.sum(delta_arr) - delta_arr[7]) > 0 + ): subform_cnt += 1 # check if there is common loss if common_nl_from_array(delta_arr, nl_db): @@ -419,7 +585,9 @@ def _calc_log_p_norm_helper(arr_norm_p) -> np.array: return log_p -def _predict_ml(meta_feature_list, group_no: int, ppm: bool, ms1_tol: float, ms2_tol: float, gd) -> np.array: +def _predict_ml( + meta_feature_list, group_no: int, ppm: bool, ms1_tol: float, ms2_tol: float, gd +) -> np.array: """ ml prediction :param meta_feature_list: List of MetaFeature objects @@ -438,15 +606,15 @@ def _predict_ml(meta_feature_list, group_no: int, ppm: bool, ms1_tol: float, ms2 # load model if group_no == 0: - model = gd['model_ms1_ms2'] + model = gd["model_ms1_ms2"] elif group_no == 1: - model = gd['model_ms1_noms2'] + model = gd["model_ms1_noms2"] X_arr = X_arr[:, :-24] # remove MS2-related features elif group_no == 2: - model = gd['model_noms1_ms2'] + model = gd["model_noms1_ms2"] X_arr = X_arr[:, 1:] # remove MS1 isotope similarity else: - model = gd['model_noms1_noms2'] + model = gd["model_noms1_noms2"] X_arr = X_arr[:, 1:] # remove MS1 isotope similarity X_arr = X_arr[:, :-24] # remove MS2-related features @@ -455,7 +623,9 @@ def _predict_ml(meta_feature_list, group_no: int, ppm: bool, ms1_tol: float, ms2 return score_arr -def predict_formula_probability(buddy_data, batch_start_idx: int, batch_end_idx: int, config, gd): +def predict_formula_probability( + buddy_data, batch_start_idx: int, batch_end_idx: int, config, gd +): """ predict formula probability :param buddy_data: buddy data @@ -509,9 +679,13 @@ def predict_formula_probability(buddy_data, batch_start_idx: int, batch_end_idx: if not group_dict[i]: continue # predict formula probability - prob_arr = _predict_ml([batch_data[j] for j in group_dict[i]], i, ppm, ms1_tol, ms2_tol, gd) + prob_arr = _predict_ml( + [batch_data[j] for j in group_dict[i]], i, ppm, ms1_tol, ms2_tol, gd + ) # Platt calibration - prob_arr = _platt_calibrated_probability(prob_arr, gd['platt_a_' + str(i)], gd['platt_b_' + str(i)]) + prob_arr = _platt_calibrated_probability( + prob_arr, gd["platt_a_" + str(i)], gd["platt_b_" + str(i)] + ) # add prediction results to candidate formula objects in the list cnt = 0 for j in group_dict[i]: @@ -547,14 +721,23 @@ def calc_fdr(buddy_data, batch_start_idx: int, batch_end_idx: int): batch_data = buddy_data[batch_start_idx:batch_end_idx] # sort candidate formula list for each metabolic feature - for meta_feature in tqdm(batch_data, desc="FDR calculation: ", file=sys.stdout, colour="green"): + for meta_feature in tqdm( + batch_data, desc="FDR calculation: ", file=sys.stdout, colour="green" + ): if not meta_feature.candidate_formula_list: continue # sort candidate formula list by estimated probability, in descending order - meta_feature.candidate_formula_list.sort(key=lambda x: x.estimated_prob, reverse=True) + meta_feature.candidate_formula_list.sort( + key=lambda x: x.estimated_prob, reverse=True + ) # sum of estimated probabilities - prob_sum = np.sum([cand_form.estimated_prob for cand_form in meta_feature.candidate_formula_list]) + prob_sum = np.sum( + [ + cand_form.estimated_prob + for cand_form in meta_feature.candidate_formula_list + ] + ) # calculate normed estimated prob and FDR considering all candidate formulas sum_normed_estimated_prob = 0 diff --git a/msbuddy/query.py b/msbuddy/query.py index af6e1e1..54ec464 100644 --- a/msbuddy/query.py +++ b/msbuddy/query.py @@ -37,27 +37,29 @@ def _get_formula_db_idx(start_idx, end_idx, db_mode: int, gd) -> Tuple[int, int] """ if db_mode == 0: if start_idx >= 15000: - db_start_idx = gd['basic_db_idx'][-1] + db_start_idx = gd["basic_db_idx"][-1] else: - db_start_idx = gd['basic_db_idx'][start_idx] + db_start_idx = gd["basic_db_idx"][start_idx] if end_idx >= 15000: - db_end_idx = len(gd['basic_db_idx']) - 1 + db_end_idx = len(gd["basic_db_idx"]) - 1 else: - db_end_idx = gd['basic_db_idx'][end_idx] + db_end_idx = gd["basic_db_idx"][end_idx] else: if start_idx >= 15000: - db_start_idx = gd['halogen_db_idx'][-1] + db_start_idx = gd["halogen_db_idx"][-1] else: - db_start_idx = gd['halogen_db_idx'][start_idx] + db_start_idx = gd["halogen_db_idx"][start_idx] if end_idx >= 15000: - db_end_idx = len(gd['halogen_db_idx']) - 1 + db_end_idx = len(gd["halogen_db_idx"]) - 1 else: - db_end_idx = gd['halogen_db_idx'][end_idx] + db_end_idx = gd["halogen_db_idx"][end_idx] return int(db_start_idx), int(db_end_idx) -def query_neutral_mass(mass: float, mz_tol: float, ppm: bool, halogen: bool, gd) -> List[Formula]: +def query_neutral_mass( + mass: float, mz_tol: float, ppm: bool, halogen: bool, gd +) -> List[Formula]: """ search neutral mass in neutral formula database :param mass: mass to search @@ -80,22 +82,28 @@ def query_neutral_mass(mass: float, mz_tol: float, ppm: bool, halogen: bool, gd) end_idx = ceil((target_mass + mass_tol) * 10) db_start_idx, db_end_idx = _get_formula_db_idx(start_idx, end_idx, 0, gd) - results_basic_mass = gd['basic_db_mass'][db_start_idx:db_end_idx] - results_basic_formula = gd['basic_db_formula'][db_start_idx:db_end_idx] - forms_basic = _func_a(results_basic_mass, results_basic_formula, target_mass, mass_tol, None) + results_basic_mass = gd["basic_db_mass"][db_start_idx:db_end_idx] + results_basic_formula = gd["basic_db_formula"][db_start_idx:db_end_idx] + forms_basic = _func_a( + results_basic_mass, results_basic_formula, target_mass, mass_tol, None + ) formulas.extend(forms_basic) if halogen: db_start_idx, db_end_idx = _get_formula_db_idx(start_idx, end_idx, 1, gd) - results_halogen_mass = gd['halogen_db_mass'][db_start_idx:db_end_idx] - results_halogen_formula = gd['halogen_db_formula'][db_start_idx:db_end_idx] - forms_halogen = _func_a(results_halogen_mass, results_halogen_formula, target_mass, mass_tol, None) + results_halogen_mass = gd["halogen_db_mass"][db_start_idx:db_end_idx] + results_halogen_formula = gd["halogen_db_formula"][db_start_idx:db_end_idx] + forms_halogen = _func_a( + results_halogen_mass, results_halogen_formula, target_mass, mass_tol, None + ) formulas.extend(forms_halogen) return formulas -def check_formula_existence(form_arr, pos_mode: bool, frag: bool, gd) -> Tuple[bool, bool]: +def check_formula_existence( + form_arr, pos_mode: bool, frag: bool, gd +) -> Tuple[bool, bool]: """ check whether this formula exists in the database :param form_arr: 12-dim array @@ -126,25 +134,26 @@ def check_formula_existence(form_arr, pos_mode: bool, frag: bool, gd) -> Tuple[b db_start_idx, db_end_idx = _get_formula_db_idx(start_idx, end_idx, db_mode, gd) if db_mode == 0: - results_mass = gd['basic_db_mass'][db_start_idx:db_end_idx] - results_formula = gd['basic_db_formula'][db_start_idx:db_end_idx] + results_mass = gd["basic_db_mass"][db_start_idx:db_end_idx] + results_formula = gd["basic_db_formula"][db_start_idx:db_end_idx] else: - results_mass = gd['halogen_db_mass'][db_start_idx:db_end_idx] - results_formula = gd['halogen_db_formula'][db_start_idx:db_end_idx] + results_mass = gd["halogen_db_mass"][db_start_idx:db_end_idx] + results_formula = gd["halogen_db_formula"][db_start_idx:db_end_idx] forms = _func_a(results_mass, results_formula, target_mass, mass_tol, None) db_existed_bool = len(forms) > 0 if frag and form_arr[0] == 0: - common_bool = common_frag_from_array(form_arr, gd['common_frag_db']) + common_bool = common_frag_from_array(form_arr, gd["common_frag_db"]) else: - common_bool = common_nl_from_array(form_arr, gd['common_loss_db']) + common_bool = common_nl_from_array(form_arr, gd["common_loss_db"]) return db_existed_bool, common_bool -def query_precursor_mass(mass: float, adduct: Adduct, mz_tol: float, - ppm: bool, db_mode: int, gd) -> Tuple[List[Formula], List[Formula]]: +def query_precursor_mass( + mass: float, adduct: Adduct, mz_tol: float, ppm: bool, db_mode: int, gd +) -> Tuple[List[Formula], List[Formula]]: """ search precursor mass in neutral database :param mass: mass to search @@ -159,7 +168,9 @@ def query_precursor_mass(mass: float, adduct: Adduct, mz_tol: float, mass_tol = mass * mz_tol / 1e6 if ppm else mz_tol # search in database ion_mode_int = 1 if adduct.pos_mode else -1 - target_mass = (mass * abs(adduct.charge) + ion_mode_int * 0.00054858 - adduct.net_formula.mass) / adduct.m + target_mass = ( + mass * abs(adduct.charge) + ion_mode_int * 0.00054858 - adduct.net_formula.mass + ) / adduct.m # formulas to return neutral_formulas = [] @@ -171,28 +182,58 @@ def query_precursor_mass(mass: float, adduct: Adduct, mz_tol: float, end_idx = ceil((target_mass + mass_tol) * 10) db_start_idx, db_end_idx = _get_formula_db_idx(start_idx, end_idx, 0, gd) - results_basic_mass = gd['basic_db_mass'][db_start_idx:db_end_idx] - results_basic_formula = gd['basic_db_formula'][db_start_idx:db_end_idx] - forms_basic = _func_a(results_basic_mass, results_basic_formula, target_mass, mass_tol, adduct.loss_formula) + results_basic_mass = gd["basic_db_mass"][db_start_idx:db_end_idx] + results_basic_formula = gd["basic_db_formula"][db_start_idx:db_end_idx] + forms_basic = _func_a( + results_basic_mass, + results_basic_formula, + target_mass, + mass_tol, + adduct.loss_formula, + ) neutral_formulas.extend(forms_basic) - charged_formulas.extend([Formula(adduct.m * f.array + adduct.net_formula.array, - charge=adduct.charge) for f in forms_basic]) + charged_formulas.extend( + [ + Formula(adduct.m * f.array + adduct.net_formula.array, charge=adduct.charge) + for f in forms_basic + ] + ) if db_mode > 0: db_start_idx, db_end_idx = _get_formula_db_idx(start_idx, end_idx, 1, gd) - results_halogen_mass = gd['halogen_db_mass'][db_start_idx:db_end_idx] - results_halogen_formula = gd['halogen_db_formula'][db_start_idx:db_end_idx] - forms_halogen = _func_a(results_halogen_mass, results_halogen_formula, - target_mass, mass_tol, adduct.loss_formula) + results_halogen_mass = gd["halogen_db_mass"][db_start_idx:db_end_idx] + results_halogen_formula = gd["halogen_db_formula"][db_start_idx:db_end_idx] + forms_halogen = _func_a( + results_halogen_mass, + results_halogen_formula, + target_mass, + mass_tol, + adduct.loss_formula, + ) neutral_formulas.extend(forms_halogen) - charged_formulas.extend([Formula(adduct.m * f.array + adduct.net_formula.array, - charge=adduct.charge) for f in forms_halogen]) + charged_formulas.extend( + [ + Formula( + adduct.m * f.array + adduct.net_formula.array, charge=adduct.charge + ) + for f in forms_halogen + ] + ) return neutral_formulas, charged_formulas -def query_fragnl_mass(mass: float, fragment: bool, pos_mode: bool, na_contain: bool, k_contain: bool, - mz_tol: float, ppm: bool, db_mode: int, gd) -> List[Formula]: +def query_fragnl_mass( + mass: float, + fragment: bool, + pos_mode: bool, + na_contain: bool, + k_contain: bool, + mz_tol: float, + ppm: bool, + db_mode: int, + gd, +) -> List[Formula]: """ search fragment or neutral loss mass in neutral database by default, both radical and non-radical formulas are searched @@ -216,33 +257,123 @@ def query_fragnl_mass(mass: float, fragment: bool, pos_mode: bool, na_contain: b # search in database # consider non-radical ions (even-electron) - t_mass, start_idx, end_idx = _calc_t_mass_db_idx(mass, fragment, False, 0, pos_mode, mass_tol) - formulas.extend(_func_b(t_mass, mass_tol, start_idx, end_idx, fragment, False, - False, False, pos_mode, db_mode, gd)) + t_mass, start_idx, end_idx = _calc_t_mass_db_idx( + mass, fragment, False, 0, pos_mode, mass_tol + ) + formulas.extend( + _func_b( + t_mass, + mass_tol, + start_idx, + end_idx, + fragment, + False, + False, + False, + pos_mode, + db_mode, + gd, + ) + ) # consider radical ions (odd-electron) - t_mass, start_idx, end_idx = _calc_t_mass_db_idx(mass, fragment, True, 0, pos_mode, mass_tol) - formulas.extend(_func_b(t_mass, mass_tol, start_idx, end_idx, fragment, True, - False, False, pos_mode, db_mode, gd)) + t_mass, start_idx, end_idx = _calc_t_mass_db_idx( + mass, fragment, True, 0, pos_mode, mass_tol + ) + formulas.extend( + _func_b( + t_mass, + mass_tol, + start_idx, + end_idx, + fragment, + True, + False, + False, + pos_mode, + db_mode, + gd, + ) + ) # consider Na and K if na_contain: - t_mass, start_idx, end_idx = _calc_t_mass_db_idx(mass, fragment, False, na_h_delta, pos_mode, mass_tol) - formulas.extend(_func_b(t_mass, mass_tol, start_idx, end_idx, fragment, False, - True, False, pos_mode, db_mode, gd)) - - t_mass, start_idx, end_idx = _calc_t_mass_db_idx(mass, fragment, True, na_h_delta, pos_mode, mass_tol) - formulas.extend(_func_b(t_mass, mass_tol, start_idx, end_idx, fragment, True, - True, False, pos_mode, db_mode, gd)) + t_mass, start_idx, end_idx = _calc_t_mass_db_idx( + mass, fragment, False, na_h_delta, pos_mode, mass_tol + ) + formulas.extend( + _func_b( + t_mass, + mass_tol, + start_idx, + end_idx, + fragment, + False, + True, + False, + pos_mode, + db_mode, + gd, + ) + ) + + t_mass, start_idx, end_idx = _calc_t_mass_db_idx( + mass, fragment, True, na_h_delta, pos_mode, mass_tol + ) + formulas.extend( + _func_b( + t_mass, + mass_tol, + start_idx, + end_idx, + fragment, + True, + True, + False, + pos_mode, + db_mode, + gd, + ) + ) if k_contain: - t_mass, start_idx, end_idx = _calc_t_mass_db_idx(mass, fragment, False, k_h_delta, pos_mode, mass_tol) - formulas.extend(_func_b(t_mass, mass_tol, start_idx, end_idx, fragment, False, - False, True, pos_mode, db_mode, gd)) - - t_mass, start_idx, end_idx = _calc_t_mass_db_idx(mass, fragment, True, k_h_delta, pos_mode, mass_tol) - formulas.extend(_func_b(t_mass, mass_tol, start_idx, end_idx, fragment, True, - False, True, pos_mode, db_mode, gd)) + t_mass, start_idx, end_idx = _calc_t_mass_db_idx( + mass, fragment, False, k_h_delta, pos_mode, mass_tol + ) + formulas.extend( + _func_b( + t_mass, + mass_tol, + start_idx, + end_idx, + fragment, + False, + False, + True, + pos_mode, + db_mode, + gd, + ) + ) + + t_mass, start_idx, end_idx = _calc_t_mass_db_idx( + mass, fragment, True, k_h_delta, pos_mode, mass_tol + ) + formulas.extend( + _func_b( + t_mass, + mass_tol, + start_idx, + end_idx, + fragment, + True, + False, + True, + pos_mode, + db_mode, + gd, + ) + ) return formulas @@ -261,7 +392,7 @@ def check_common_frag(formula: Formula, gd) -> bool: # Na, K => H form_arr = convert_na_k(form_arr) - return common_frag_from_array(form_arr, gd['common_frag_db']) + return common_frag_from_array(form_arr, gd["common_frag_db"]) def check_common_nl(formula: Formula, gd) -> bool: @@ -275,11 +406,17 @@ def check_common_nl(formula: Formula, gd) -> bool: # Na, K => H form_arr = convert_na_k(form_arr) - return common_nl_from_array(form_arr, gd['common_loss_db']) + return common_nl_from_array(form_arr, gd["common_loss_db"]) -def _calc_t_mass_db_idx(mass: float, fragment: bool, radical: bool, convert_mass: float, pos_mode: bool, - mass_tol: float) -> Tuple[float, int, int]: +def _calc_t_mass_db_idx( + mass: float, + fragment: bool, + radical: bool, + convert_mass: float, + pos_mode: bool, + mass_tol: float, +) -> Tuple[float, int, int]: """ calculate target mass and calculate database index :param mass: mass to search @@ -292,22 +429,39 @@ def _calc_t_mass_db_idx(mass: float, fragment: bool, radical: bool, convert_mass """ if fragment: if not radical: - t_mass = mass - convert_mass - 1.007276 if pos_mode else mass - convert_mass + 1.007276 + t_mass = ( + mass - convert_mass - 1.007276 + if pos_mode + else mass - convert_mass + 1.007276 + ) else: - t_mass = mass - convert_mass + 0.00054858 if pos_mode else mass - convert_mass - 0.00054858 + t_mass = ( + mass - convert_mass + 0.00054858 + if pos_mode + else mass - convert_mass - 0.00054858 + ) else: if not radical: t_mass = mass - convert_mass else: - t_mass = mass - convert_mass - 1.007825 if pos_mode else mass - convert_mass + 1.007825 + t_mass = ( + mass - convert_mass - 1.007825 + if pos_mode + else mass - convert_mass + 1.007825 + ) start_idx = int((t_mass - mass_tol) * 10) end_idx = ceil((t_mass + mass_tol) * 10) return t_mass, start_idx, end_idx -def _func_a(results_mass, results_formula, target_mass: float, mass_tol: float, - adduct_loss_form: Union[Formula, None]) -> List[Formula]: +def _func_a( + results_mass, + results_formula, + target_mass: float, + mass_tol: float, + adduct_loss_form: Union[Formula, None], +) -> List[Formula]: """ a helper function for query_precursor_mass filter and convert the sql query results to Formula objects @@ -331,16 +485,31 @@ def _func_a(results_mass, results_formula, target_mass: float, mass_tol: float, l_arr = adduct_loss_form.array for idx in all_idx: if np.all(results_formula[idx] >= l_arr): - formulas.append(Formula(results_formula[idx], charge=0, mass=results_mass[idx])) + formulas.append( + Formula(results_formula[idx], charge=0, mass=results_mass[idx]) + ) # if adduct has no loss formula else: for idx in all_idx: - formulas.append(Formula(results_formula[idx], charge=0, mass=results_mass[idx])) + formulas.append( + Formula(results_formula[idx], charge=0, mass=results_mass[idx]) + ) return formulas -def _func_b(target_mass, mass_tol, start_idx, end_idx, fragment: bool, radical: bool, - na_contain: bool, k_contain: bool, pos_mode: bool, db_mode: int, gd) -> List[Formula]: +def _func_b( + target_mass, + mass_tol, + start_idx, + end_idx, + fragment: bool, + radical: bool, + na_contain: bool, + k_contain: bool, + pos_mode: bool, + db_mode: int, + gd, +) -> List[Formula]: """ a helper function for "query_fragnl_mass" first query the database, then convert the results to Formula objects @@ -359,18 +528,51 @@ def _func_b(target_mass, mass_tol, start_idx, end_idx, fragment: bool, radical: """ forms = [] - forms_basic = _func_c(target_mass, mass_tol, start_idx, end_idx, fragment, radical, na_contain, - k_contain, pos_mode, 0, gd) + forms_basic = _func_c( + target_mass, + mass_tol, + start_idx, + end_idx, + fragment, + radical, + na_contain, + k_contain, + pos_mode, + 0, + gd, + ) forms.extend(forms_basic) if db_mode > 0: - forms_halogen = _func_c(target_mass, mass_tol, start_idx, end_idx, fragment, radical, na_contain, - k_contain, pos_mode, 1, gd) + forms_halogen = _func_c( + target_mass, + mass_tol, + start_idx, + end_idx, + fragment, + radical, + na_contain, + k_contain, + pos_mode, + 1, + gd, + ) forms.extend(forms_halogen) return forms -def _func_c(target_mass, mass_tol, start_idx, end_idx, fragment: bool, radical: bool, - na_contain: bool, k_contain: bool, pos_mode: bool, db_mode: int, gd) -> List[Formula]: +def _func_c( + target_mass, + mass_tol, + start_idx, + end_idx, + fragment: bool, + radical: bool, + na_contain: bool, + k_contain: bool, + pos_mode: bool, + db_mode: int, + gd, +) -> List[Formula]: """ a helper function for _func_b query the database @@ -389,11 +591,11 @@ def _func_c(target_mass, mass_tol, start_idx, end_idx, fragment: bool, radical: """ db_start_idx, db_end_idx = _get_formula_db_idx(start_idx, end_idx, db_mode, gd) if db_mode == 0: - results_mass = gd['basic_db_mass'][db_start_idx:db_end_idx] - results_formula = gd['basic_db_formula'][db_start_idx:db_end_idx] + results_mass = gd["basic_db_mass"][db_start_idx:db_end_idx] + results_formula = gd["basic_db_formula"][db_start_idx:db_end_idx] else: - results_mass = gd['halogen_db_mass'][db_start_idx:db_end_idx] - results_formula = gd['halogen_db_formula'][db_start_idx:db_end_idx] + results_mass = gd["halogen_db_mass"][db_start_idx:db_end_idx] + results_formula = gd["halogen_db_formula"][db_start_idx:db_end_idx] all_idx = np.where(np.abs(results_mass - target_mass) <= mass_tol)[0] @@ -410,40 +612,79 @@ def _func_c(target_mass, mass_tol, start_idx, end_idx, fragment: bool, radical: arr = results_formula[idx].copy() arr[8] += 1 arr[1] -= 1 - forms.append(Formula(arr, charge=ion_mode_int, - mass=results_mass[idx] - ion_mode_int * 0.00054858 + na_h_delta)) + forms.append( + Formula( + arr, + charge=ion_mode_int, + mass=results_mass[idx] + - ion_mode_int * 0.00054858 + + na_h_delta, + ) + ) else: for idx in all_idx: arr = results_formula[idx].copy() arr[8] += 1 arr[1] = arr[1] - 1 + ion_mode_int - forms.append(Formula(arr, charge=ion_mode_int, - mass=results_mass[idx] + ion_mode_int * 1.007276 + na_h_delta)) + forms.append( + Formula( + arr, + charge=ion_mode_int, + mass=results_mass[idx] + + ion_mode_int * 1.007276 + + na_h_delta, + ) + ) elif k_contain: if radical: for idx in all_idx: arr = results_formula[idx].copy() arr[6] += 1 arr[1] -= 1 - forms.append(Formula(arr, charge=ion_mode_int, - mass=results_mass[idx] - ion_mode_int * 0.00054858 + k_h_delta)) + forms.append( + Formula( + arr, + charge=ion_mode_int, + mass=results_mass[idx] + - ion_mode_int * 0.00054858 + + k_h_delta, + ) + ) else: for idx in all_idx: arr = results_formula[idx].copy() arr[6] += 1 arr[1] = arr[1] - 1 + ion_mode_int - forms.append(Formula(arr, charge=ion_mode_int, - mass=results_mass[idx] + ion_mode_int * 1.007276 + k_h_delta)) + forms.append( + Formula( + arr, + charge=ion_mode_int, + mass=results_mass[idx] + + ion_mode_int * 1.007276 + + k_h_delta, + ) + ) else: if radical: for idx in all_idx: - forms.append(Formula(results_formula[idx], charge=ion_mode_int, - mass=results_mass[idx] - ion_mode_int * 0.00054858)) + forms.append( + Formula( + results_formula[idx], + charge=ion_mode_int, + mass=results_mass[idx] - ion_mode_int * 0.00054858, + ) + ) else: for idx in all_idx: arr = results_formula[idx].copy() arr[1] += ion_mode_int - forms.append(Formula(arr, charge=ion_mode_int, mass=results_mass[idx] + ion_mode_int * 1.007276)) + forms.append( + Formula( + arr, + charge=ion_mode_int, + mass=results_mass[idx] + ion_mode_int * 1.007276, + ) + ) else: # neutral loss if na_contain: if radical: @@ -451,37 +692,63 @@ def _func_c(target_mass, mass_tol, start_idx, end_idx, fragment: bool, radical: arr = results_formula[idx].copy() arr[8] += 1 arr[1] = arr[1] - 1 + ion_mode_int - forms.append(Formula(arr, charge=0, - mass=results_mass[idx] + ion_mode_int * 1.007825 + na_h_delta)) + forms.append( + Formula( + arr, + charge=0, + mass=results_mass[idx] + + ion_mode_int * 1.007825 + + na_h_delta, + ) + ) else: for idx in all_idx: arr = results_formula[idx].copy() arr[8] += 1 arr[1] -= 1 - forms.append(Formula(arr, charge=0, mass=results_mass[idx] + na_h_delta)) + forms.append( + Formula(arr, charge=0, mass=results_mass[idx] + na_h_delta) + ) elif k_contain: if radical: for idx in all_idx: arr = results_formula[idx].copy() arr[6] += 1 arr[1] = arr[1] - 1 + ion_mode_int - forms.append(Formula(arr, charge=0, - mass=results_mass[idx] + ion_mode_int * 1.007825 + k_h_delta)) + forms.append( + Formula( + arr, + charge=0, + mass=results_mass[idx] + + ion_mode_int * 1.007825 + + k_h_delta, + ) + ) else: for idx in all_idx: arr = results_formula[idx].copy() arr[6] += 1 arr[1] -= 1 - forms.append(Formula(arr, charge=0, mass=results_mass[idx] + k_h_delta)) + forms.append( + Formula(arr, charge=0, mass=results_mass[idx] + k_h_delta) + ) else: if radical: for idx in all_idx: arr = results_formula[idx].copy() arr[1] += ion_mode_int - forms.append(Formula(arr, charge=0, mass=results_mass[idx] + ion_mode_int * 1.007825)) + forms.append( + Formula( + arr, + charge=0, + mass=results_mass[idx] + ion_mode_int * 1.007825, + ) + ) else: for idx in all_idx: - forms.append(Formula(results_formula[idx], charge=0, mass=results_mass[idx])) + forms.append( + Formula(results_formula[idx], charge=0, mass=results_mass[idx]) + ) return forms diff --git a/msbuddy/utils.py b/msbuddy/utils.py index 82e929f..2d454b4 100644 --- a/msbuddy/utils.py +++ b/msbuddy/utils.py @@ -110,13 +110,13 @@ def enumerate_subformula(pre_charged_arr: np.array) -> np.array: for i in range(n): count = pre_charged_arr[i] repeatSize = tempSize - tempSize *= (count + 1) + tempSize *= count + 1 pattern = np.arange(count + 1) repeated_pattern = np.empty(repeatSize * len(pattern), dtype=np.int16) for j in range(len(pattern)): - repeated_pattern[j * repeatSize: (j + 1) * repeatSize] = pattern[j] + repeated_pattern[j * repeatSize : (j + 1) * repeatSize] = pattern[j] full_repeats = total_subform_cnt // len(repeated_pattern) @@ -130,10 +130,20 @@ def enumerate_subformula(pre_charged_arr: np.array) -> np.array: # for numba alphabet_np = np.array( - [ord(char) for word in ["C", "H", "Br", "Cl", "F", "I", "K", "N", "Na", "O", "P", "S"] for char in word], - dtype=np.int16) -word_lengths = np.array([len(word) for word in ["C", "H", "Br", "Cl", "F", "I", "K", "N", "Na", "O", "P", "S"]], - dtype=np.int16) + [ + ord(char) + for word in ["C", "H", "Br", "Cl", "F", "I", "K", "N", "Na", "O", "P", "S"] + for char in word + ], + dtype=np.int16, +) +word_lengths = np.array( + [ + len(word) + for word in ["C", "H", "Br", "Cl", "F", "I", "K", "N", "Na", "O", "P", "S"] + ], + dtype=np.int16, +) @njit @@ -164,7 +174,7 @@ def _ascii_to_str(ascii_arr) -> str: :param ascii_arr: ASCII array :return: string """ - return ''.join(chr(i) for i in ascii_arr) + return "".join(chr(i) for i in ascii_arr) def form_arr_to_str(form_arr) -> str: @@ -205,9 +215,9 @@ def read_formula_str(x: str) -> dict: :return: formula dictionary """ # if x contains '.', read two parts - if '.' in x: - x1 = x.split('.')[0] - x2 = x.split('.')[1] + if "." in x: + x1 = x.split(".")[0] + x2 = x.split(".")[1] form1 = _read_formula_str(x1) # this is a dict form2 = _read_formula_str(x2) # merge two dicts, add values with same keys diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..854adde --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,43 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "msbuddy" +version = "0.3.14" +description = "Molecular formula annotation for MS-based small molecule analysis." +readme = "README.md" +requires-python = ">=3.8" +license = "Apache-2.0" +authors = [ + { name = "Shipei Xing", email = "philipxsp@hotmail.com" } +] +classifiers = [ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: Apache Software License", + "Operating System :: OS Independent", +] +dependencies = [ + "brain-isotopic-distribution", + "numpy", + "numba", + "requests", + "joblib", + "gdown", + "chemparse", + "scipy", + "tqdm", + "pandas", + "lightgbm>=4.6.0", +] + +[project.urls] +Homepage = "https://github.com/Philipbear/msbuddy" +"Bug Tracker" = "https://github.com/Philipbear/msbuddy/issues" +Documentation = "https://msbuddy.readthedocs.io/en/latest/" + +[project.scripts] +msbuddy = "msbuddy.main_cmd:main" + +[tool.hatch.build.targets.wheel] +packages = ["msbuddy"] diff --git a/setup.py b/setup.py deleted file mode 100644 index f5f0dbd..0000000 --- a/setup.py +++ /dev/null @@ -1,44 +0,0 @@ -import setuptools - -with open("README.md", "r", encoding="utf-8") as fh: - long_description = fh.read() - -setuptools.setup( - name="msbuddy", - version="0.3.14", - author="Shipei Xing", - author_email="philipxsp@hotmail.com", - description="Molecular formula annotation for MS-based small molecule analysis.", - long_description=long_description, - long_description_content_type="text/markdown", - url="https://github.com/Philipbear/msbuddy", - project_urls={ - "Bug Tracker": "https://github.com/Philipbear/msbuddy/issues", - "Documentation": "https://msbuddy.readthedocs.io/en/latest/" - }, - scripts=['msbuddy/main_cmd.py'], - entry_points={ - 'console_scripts': [ - 'msbuddy=msbuddy.main_cmd:main', - ], - }, - classifiers=[ - "Programming Language :: Python :: 3", - "License :: OSI Approved :: Apache Software License", - "Operating System :: OS Independent", - ], - packages=["msbuddy"], - install_requires=[ - "brain-isotopic-distribution", - "numpy", - "numba", - "requests", - "joblib", - "gdown", - "chemparse", - "scipy", - "tqdm", - "pandas" - ], - python_requires=">=3.8" -) diff --git a/test.py b/test.py index 03e0174..b813944 100644 --- a/test.py +++ b/test.py @@ -1,16 +1,18 @@ # test msbuddy # ver 0.3.0 + def test_main(): from msbuddy import Msbuddy, MsbuddyConfig + # instantiate a MsbuddyConfig object - msb_config = MsbuddyConfig(ms_instr='orbitrap', halogen=False) + msb_config = MsbuddyConfig(ms_instr="orbitrap", halogen=False) # instantiate a Msbuddy object msb_engine = Msbuddy(msb_config) # you can load multiple USIs at once - msb_engine.load_mgf('/Users/shipei/Documents/projects/msbuddy/demo/input_file.mgf') + msb_engine.load_mgf("./demo/input_file.mgf") # annotate molecular formula msb_engine.annotate_formula() @@ -63,13 +65,18 @@ def test_mass_formula(): def test_subformula(): from msbuddy import assign_subformula - subformla_list = assign_subformula([107.05, 149.02, 209.04, 221.04, 230.96], - precursor_formula="C15H16O5", adduct="[M+H]+", - ms2_tol=0.02, ppm=False, dbe_cutoff=-1.0) + subformla_list = assign_subformula( + [107.05, 149.02, 209.04, 221.04, 230.96], + precursor_formula="C15H16O5", + adduct="[M+H]+", + ms2_tol=0.02, + ppm=False, + dbe_cutoff=-1.0, + ) print(len(subformla_list)) -if __name__ == '__main__': +if __name__ == "__main__": test_main() test_formula() test_mass_formula() diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..1655d08 --- /dev/null +++ b/uv.lock @@ -0,0 +1,1522 @@ +version = 1 +revision = 3 +requires-python = ">=3.8" +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", + "python_full_version < '3.9'", +] + +[[package]] +name = "beautifulsoup4" +version = "4.14.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve", version = "2.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "soupsieve", version = "2.8.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" }, +] + +[[package]] +name = "brain-isotopic-distribution" +version = "1.5.19" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/40/2d/c5de0f5eeb0745192ffc731e78397825c490d4e999bed2bb4e29b57e0ebd/brain_isotopic_distribution-1.5.19.tar.gz", hash = "sha256:cad44fcb1ddbd26787dafb65345558df5119a7928b6e773fbb9dd5fd4f3334de", size = 591771, upload-time = "2025-05-22T01:19:50.293Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/bb/4074978a2d98055958b4927d31da85b5097b0c87ff33fe5245c05c0a4f6c/brain_isotopic_distribution-1.5.19-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9cb8a5009177eacb43c8323f88ecd0b58385db4293a38593ccfb6caf52fe2e56", size = 850199, upload-time = "2025-05-22T01:19:22.769Z" }, + { url = "https://files.pythonhosted.org/packages/d4/98/8f081bcc49565f8a803cfae3226ed12b3500f65c54a2029c8497131cc6f8/brain_isotopic_distribution-1.5.19-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd6675cdc9d4d815df8b1571c209d35dccb02fc95492e55ca3ab49825073dfdb", size = 2157835, upload-time = "2025-05-22T01:19:27.937Z" }, + { url = "https://files.pythonhosted.org/packages/3a/35/f14869ff6a4c3386c47686d478b01f0db218ba24c1333f8bac63c1773cea/brain_isotopic_distribution-1.5.19-cp310-cp310-win_amd64.whl", hash = "sha256:855c040f3ee967202e0c55c88c2bfca0050c7ca7b99c5001487be68155eac8c8", size = 842315, upload-time = "2025-05-22T01:19:29.796Z" }, + { url = "https://files.pythonhosted.org/packages/86/28/8323a24560d2ddac17fe356c4eefc545eeae66f93dd12a552d9f7aa15593/brain_isotopic_distribution-1.5.19-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:417faed1d00ed8a973241f992895d728ede46bd8778e4bce7398fdb6b9467f5b", size = 853468, upload-time = "2025-05-22T01:19:31.481Z" }, + { url = "https://files.pythonhosted.org/packages/d1/05/9106e918f5db7a7c0e01922c2e31bb430f80721decc6e252d7cd46279d7f/brain_isotopic_distribution-1.5.19-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f4b9f6ac714e958ebf263276a484a8368778e3e1ade8fdf4a87e0576e1384cf", size = 2262401, upload-time = "2025-05-22T01:19:33.288Z" }, + { url = "https://files.pythonhosted.org/packages/e2/da/9f63a844dff3c38b315964828edc5e8a9f97f02d1f57b8cc4014f6a53e8f/brain_isotopic_distribution-1.5.19-cp311-cp311-win_amd64.whl", hash = "sha256:42b19145326fc15362a9ce943d69448b766b4c1f6cf046fa84cb40d428d9c698", size = 843565, upload-time = "2025-05-22T01:19:34.459Z" }, + { url = "https://files.pythonhosted.org/packages/ca/3f/eb6adbe67e877d4fc6792cf51473f19c1522ba019a46c56f7a68b2dc4fbc/brain_isotopic_distribution-1.5.19-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7e59fe63c4a57f2db17b65d5aab98ae470ae7adeaef6a8c6c7dc52116b9d7925", size = 852024, upload-time = "2025-05-22T01:19:35.591Z" }, + { url = "https://files.pythonhosted.org/packages/64/23/e80f8e519fcdea4bdf483c6363db5629a10fd6be8e179bb47bdc07707bec/brain_isotopic_distribution-1.5.19-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0651e9490880c5d699f573277705b2ef0972d32a2b752ec3e49b768a7f75b597", size = 2246630, upload-time = "2025-05-22T01:19:37.11Z" }, + { url = "https://files.pythonhosted.org/packages/ec/4e/402b9fc1a958767b7390046ce3f72f876eddbc3fade1681b4baa29f1fb98/brain_isotopic_distribution-1.5.19-cp312-cp312-win_amd64.whl", hash = "sha256:5bca2d1dd1e7bdbe760456583cf158444686566f30025630fc685308af57cea6", size = 844597, upload-time = "2025-05-22T01:19:38.383Z" }, + { url = "https://files.pythonhosted.org/packages/0b/2a/013cfb6d1639473ca2dc58a408d4c66f73094ad2ad57e9a284b092e96e2d/brain_isotopic_distribution-1.5.19-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c894611c3756226ca7bc962fbca8154dbe430501ed8c191d995ddb7d2d935994", size = 856414, upload-time = "2025-05-22T01:19:40.123Z" }, + { url = "https://files.pythonhosted.org/packages/f7/e8/5109fe9247d65f86c64b183a250ec73a40c3d8e574590f0c3192a3e1e1e8/brain_isotopic_distribution-1.5.19-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13926c14efcc7eb13d7d8a61d30985f7eaa866b8d58dc847da724d48d560ca60", size = 2190945, upload-time = "2025-05-22T01:19:41.41Z" }, + { url = "https://files.pythonhosted.org/packages/de/b2/a6e3a2cf98fbf240c2aceb5513f6f27a13c90ffdc7ced2d860462112a8ae/brain_isotopic_distribution-1.5.19-cp38-cp38-win_amd64.whl", hash = "sha256:6005068203fda8438724d20c01cb1b717095211939f56ea1bac09753106ff1a9", size = 845947, upload-time = "2025-05-22T01:19:43.09Z" }, + { url = "https://files.pythonhosted.org/packages/af/71/2a47e84eaa9a58dd8054299e575e03357eda5bc4a580ff57e3f0026be4ea/brain_isotopic_distribution-1.5.19-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fa751b98ef59981308d42ea40102dc8c3cddad038b7c93b791adbceddb9f081e", size = 852012, upload-time = "2025-05-22T01:19:45.643Z" }, + { url = "https://files.pythonhosted.org/packages/a5/95/c0721b370992c9fbff2c3cba6f416f4873ae6b6f116e58417ffcf6c2eefb/brain_isotopic_distribution-1.5.19-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb622c81e8d5633bf334fe151bba54abf76889251ccb19e43660d5c3db3544af", size = 2156886, upload-time = "2025-05-22T01:19:47.058Z" }, + { url = "https://files.pythonhosted.org/packages/52/d5/b46f1286e09a3bea678d5b1701b5ca7909db67015d2cdc196075e974f4c9/brain_isotopic_distribution-1.5.19-cp39-cp39-win_amd64.whl", hash = "sha256:a4bc7da61d21a8d3e7a5769665b1edbf4e20c7ed3a7da604d26c478e4296309a", size = 843680, upload-time = "2025-05-22T01:19:48.995Z" }, +] + +[[package]] +name = "certifi" +version = "2026.4.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/25/ee/6caf7a40c36a1220410afe15a1cc64993a1f864871f698c0f93acb72842a/certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580", size = 137077, upload-time = "2026-04-22T11:26:11.191Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/30/7cd8fdcdfbc5b869528b079bfb76dcdf6056b1a2097a662e5e8c04f42965/certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a", size = 135707, upload-time = "2026-04-22T11:26:09.372Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/08/0f303cb0b529e456bb116f2d50565a482694fbb94340bf56d44677e7ed03/charset_normalizer-3.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cdd68a1fb318e290a2077696b7eb7a21a49163c455979c639bf5a5dcdc46617d", size = 315182, upload-time = "2026-04-02T09:25:40.673Z" }, + { url = "https://files.pythonhosted.org/packages/24/47/b192933e94b546f1b1fe4df9cc1f84fcdbf2359f8d1081d46dd029b50207/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e17b8d5d6a8c47c85e68ca8379def1303fd360c3e22093a807cd34a71cd082b8", size = 209329, upload-time = "2026-04-02T09:25:42.354Z" }, + { url = "https://files.pythonhosted.org/packages/c2/b4/01fa81c5ca6141024d89a8fc15968002b71da7f825dd14113207113fabbd/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:511ef87c8aec0783e08ac18565a16d435372bc1ac25a91e6ac7f5ef2b0bff790", size = 231230, upload-time = "2026-04-02T09:25:44.281Z" }, + { url = "https://files.pythonhosted.org/packages/20/f7/7b991776844dfa058017e600e6e55ff01984a063290ca5622c0b63162f68/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:007d05ec7321d12a40227aae9e2bc6dca73f3cb21058999a1df9e193555a9dcc", size = 225890, upload-time = "2026-04-02T09:25:45.475Z" }, + { url = "https://files.pythonhosted.org/packages/20/e7/bed0024a0f4ab0c8a9c64d4445f39b30c99bd1acd228291959e3de664247/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf29836da5119f3c8a8a70667b0ef5fdca3bb12f80fd06487cfa575b3909b393", size = 216930, upload-time = "2026-04-02T09:25:46.58Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ab/b18f0ab31cdd7b3ddb8bb76c4a414aeb8160c9810fdf1bc62f269a539d87/charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:12d8baf840cc7889b37c7c770f478adea7adce3dcb3944d02ec87508e2dcf153", size = 202109, upload-time = "2026-04-02T09:25:48.031Z" }, + { url = "https://files.pythonhosted.org/packages/82/e5/7e9440768a06dfb3075936490cb82dbf0ee20a133bf0dd8551fa096914ec/charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d560742f3c0d62afaccf9f41fe485ed69bd7661a241f86a3ef0f0fb8b1a397af", size = 214684, upload-time = "2026-04-02T09:25:49.245Z" }, + { url = "https://files.pythonhosted.org/packages/71/94/8c61d8da9f062fdf457c80acfa25060ec22bf1d34bbeaca4350f13bcfd07/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b14b2d9dac08e28bb8046a1a0434b1750eb221c8f5b87a68f4fa11a6f97b5e34", size = 212785, upload-time = "2026-04-02T09:25:50.671Z" }, + { url = "https://files.pythonhosted.org/packages/66/cd/6e9889c648e72c0ab2e5967528bb83508f354d706637bc7097190c874e13/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:bc17a677b21b3502a21f66a8cc64f5bfad4df8a0b8434d661666f8ce90ac3af1", size = 203055, upload-time = "2026-04-02T09:25:51.802Z" }, + { url = "https://files.pythonhosted.org/packages/92/2e/7a951d6a08aefb7eb8e1b54cdfb580b1365afdd9dd484dc4bee9e5d8f258/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:750e02e074872a3fad7f233b47734166440af3cdea0add3e95163110816d6752", size = 232502, upload-time = "2026-04-02T09:25:53.388Z" }, + { url = "https://files.pythonhosted.org/packages/58/d5/abcf2d83bf8e0a1286df55cd0dc1d49af0da4282aa77e986df343e7de124/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:4e5163c14bffd570ef2affbfdd77bba66383890797df43dc8b4cc7d6f500bf53", size = 214295, upload-time = "2026-04-02T09:25:54.765Z" }, + { url = "https://files.pythonhosted.org/packages/47/3a/7d4cd7ed54be99973a0dc176032cba5cb1f258082c31fa6df35cff46acfc/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6ed74185b2db44f41ef35fd1617c5888e59792da9bbc9190d6c7300617182616", size = 227145, upload-time = "2026-04-02T09:25:55.904Z" }, + { url = "https://files.pythonhosted.org/packages/1d/98/3a45bf8247889cf28262ebd3d0872edff11565b2a1e3064ccb132db3fbb0/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:94e1885b270625a9a828c9793b4d52a64445299baa1fea5a173bf1d3dd9a1a5a", size = 218884, upload-time = "2026-04-02T09:25:57.074Z" }, + { url = "https://files.pythonhosted.org/packages/ad/80/2e8b7f8915ed5c9ef13aa828d82738e33888c485b65ebf744d615040c7ea/charset_normalizer-3.4.7-cp310-cp310-win32.whl", hash = "sha256:6785f414ae0f3c733c437e0f3929197934f526d19dfaa75e18fdb4f94c6fb374", size = 148343, upload-time = "2026-04-02T09:25:58.199Z" }, + { url = "https://files.pythonhosted.org/packages/35/1b/3b8c8c77184af465ee9ad88b5aea46ea6b2e1f7b9dc9502891e37af21e30/charset_normalizer-3.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:6696b7688f54f5af4462118f0bfa7c1621eeb87154f77fa04b9295ce7a8f2943", size = 159174, upload-time = "2026-04-02T09:25:59.322Z" }, + { url = "https://files.pythonhosted.org/packages/be/c1/feb40dca40dbb21e0a908801782d9288c64fc8d8e562c2098e9994c8c21b/charset_normalizer-3.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:66671f93accb62ed07da56613636f3641f1a12c13046ce91ffc923721f23c008", size = 147805, upload-time = "2026-04-02T09:26:00.756Z" }, + { url = "https://files.pythonhosted.org/packages/c2/d7/b5b7020a0565c2e9fa8c09f4b5fa6232feb326b8c20081ccded47ea368fd/charset_normalizer-3.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7", size = 309705, upload-time = "2026-04-02T09:26:02.191Z" }, + { url = "https://files.pythonhosted.org/packages/5a/53/58c29116c340e5456724ecd2fff4196d236b98f3da97b404bc5e51ac3493/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7", size = 206419, upload-time = "2026-04-02T09:26:03.583Z" }, + { url = "https://files.pythonhosted.org/packages/b2/02/e8146dc6591a37a00e5144c63f29fb7c97a734ea8a111190783c0e60ab63/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e", size = 227901, upload-time = "2026-04-02T09:26:04.738Z" }, + { url = "https://files.pythonhosted.org/packages/fb/73/77486c4cd58f1267bf17db420e930c9afa1b3be3fe8c8b8ebbebc9624359/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c", size = 222742, upload-time = "2026-04-02T09:26:06.36Z" }, + { url = "https://files.pythonhosted.org/packages/a1/fa/f74eb381a7d94ded44739e9d94de18dc5edc9c17fb8c11f0a6890696c0a9/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df", size = 214061, upload-time = "2026-04-02T09:26:08.347Z" }, + { url = "https://files.pythonhosted.org/packages/dc/92/42bd3cefcf7687253fb86694b45f37b733c97f59af3724f356fa92b8c344/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265", size = 199239, upload-time = "2026-04-02T09:26:09.823Z" }, + { url = "https://files.pythonhosted.org/packages/4c/3d/069e7184e2aa3b3cddc700e3dd267413dc259854adc3380421c805c6a17d/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4", size = 210173, upload-time = "2026-04-02T09:26:10.953Z" }, + { url = "https://files.pythonhosted.org/packages/62/51/9d56feb5f2e7074c46f93e0ebdbe61f0848ee246e2f0d89f8e20b89ebb8f/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e", size = 209841, upload-time = "2026-04-02T09:26:12.142Z" }, + { url = "https://files.pythonhosted.org/packages/d2/59/893d8f99cc4c837dda1fe2f1139079703deb9f321aabcb032355de13b6c7/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38", size = 200304, upload-time = "2026-04-02T09:26:13.711Z" }, + { url = "https://files.pythonhosted.org/packages/7d/1d/ee6f3be3464247578d1ed5c46de545ccc3d3ff933695395c402c21fa6b77/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c", size = 229455, upload-time = "2026-04-02T09:26:14.941Z" }, + { url = "https://files.pythonhosted.org/packages/54/bb/8fb0a946296ea96a488928bdce8ef99023998c48e4713af533e9bb98ef07/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b", size = 210036, upload-time = "2026-04-02T09:26:16.478Z" }, + { url = "https://files.pythonhosted.org/packages/9a/bc/015b2387f913749f82afd4fcba07846d05b6d784dd16123cb66860e0237d/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c", size = 224739, upload-time = "2026-04-02T09:26:17.751Z" }, + { url = "https://files.pythonhosted.org/packages/17/ab/63133691f56baae417493cba6b7c641571a2130eb7bceba6773367ab9ec5/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d", size = 216277, upload-time = "2026-04-02T09:26:18.981Z" }, + { url = "https://files.pythonhosted.org/packages/06/6d/3be70e827977f20db77c12a97e6a9f973631a45b8d186c084527e53e77a4/charset_normalizer-3.4.7-cp311-cp311-win32.whl", hash = "sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad", size = 147819, upload-time = "2026-04-02T09:26:20.295Z" }, + { url = "https://files.pythonhosted.org/packages/20/d9/5f67790f06b735d7c7637171bbfd89882ad67201891b7275e51116ed8207/charset_normalizer-3.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00", size = 159281, upload-time = "2026-04-02T09:26:21.74Z" }, + { url = "https://files.pythonhosted.org/packages/ca/83/6413f36c5a34afead88ce6f66684d943d91f233d76dd083798f9602b75ae/charset_normalizer-3.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1", size = 147843, upload-time = "2026-04-02T09:26:22.901Z" }, + { url = "https://files.pythonhosted.org/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", size = 311328, upload-time = "2026-04-02T09:26:24.331Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e3/0fadc706008ac9d7b9b5be6dc767c05f9d3e5df51744ce4cc9605de7b9f4/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2", size = 208061, upload-time = "2026-04-02T09:26:25.568Z" }, + { url = "https://files.pythonhosted.org/packages/42/f0/3dd1045c47f4a4604df85ec18ad093912ae1344ac706993aff91d38773a2/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b", size = 229031, upload-time = "2026-04-02T09:26:26.865Z" }, + { url = "https://files.pythonhosted.org/packages/dc/67/675a46eb016118a2fbde5a277a5d15f4f69d5f3f5f338e5ee2f8948fcf43/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a", size = 225239, upload-time = "2026-04-02T09:26:28.044Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f8/d0118a2f5f23b02cd166fa385c60f9b0d4f9194f574e2b31cef350ad7223/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116", size = 216589, upload-time = "2026-04-02T09:26:29.239Z" }, + { url = "https://files.pythonhosted.org/packages/b1/f1/6d2b0b261b6c4ceef0fcb0d17a01cc5bc53586c2d4796fa04b5c540bc13d/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb", size = 202733, upload-time = "2026-04-02T09:26:30.5Z" }, + { url = "https://files.pythonhosted.org/packages/6f/c0/7b1f943f7e87cc3db9626ba17807d042c38645f0a1d4415c7a14afb5591f/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1", size = 212652, upload-time = "2026-04-02T09:26:31.709Z" }, + { url = "https://files.pythonhosted.org/packages/38/dd/5a9ab159fe45c6e72079398f277b7d2b523e7f716acc489726115a910097/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15", size = 211229, upload-time = "2026-04-02T09:26:33.282Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ff/531a1cad5ca855d1c1a8b69cb71abfd6d85c0291580146fda7c82857caa1/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5", size = 203552, upload-time = "2026-04-02T09:26:34.845Z" }, + { url = "https://files.pythonhosted.org/packages/c1/4c/a5fb52d528a8ca41f7598cb619409ece30a169fbdf9cdce592e53b46c3a6/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d", size = 230806, upload-time = "2026-04-02T09:26:36.152Z" }, + { url = "https://files.pythonhosted.org/packages/59/7a/071feed8124111a32b316b33ae4de83d36923039ef8cf48120266844285b/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7", size = 212316, upload-time = "2026-04-02T09:26:37.672Z" }, + { url = "https://files.pythonhosted.org/packages/fd/35/f7dba3994312d7ba508e041eaac39a36b120f32d4c8662b8814dab876431/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464", size = 227274, upload-time = "2026-04-02T09:26:38.93Z" }, + { url = "https://files.pythonhosted.org/packages/8a/2d/a572df5c9204ab7688ec1edc895a73ebded3b023bb07364710b05dd1c9be/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49", size = 218468, upload-time = "2026-04-02T09:26:40.17Z" }, + { url = "https://files.pythonhosted.org/packages/86/eb/890922a8b03a568ca2f336c36585a4713c55d4d67bf0f0c78924be6315ca/charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c", size = 148460, upload-time = "2026-04-02T09:26:41.416Z" }, + { url = "https://files.pythonhosted.org/packages/35/d9/0e7dffa06c5ab081f75b1b786f0aefc88365825dfcd0ac544bdb7b2b6853/charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6", size = 159330, upload-time = "2026-04-02T09:26:42.554Z" }, + { url = "https://files.pythonhosted.org/packages/9e/5d/481bcc2a7c88ea6b0878c299547843b2521ccbc40980cb406267088bc701/charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d", size = 147828, upload-time = "2026-04-02T09:26:44.075Z" }, + { url = "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627, upload-time = "2026-04-02T09:26:45.198Z" }, + { url = "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008, upload-time = "2026-04-02T09:26:46.824Z" }, + { url = "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303, upload-time = "2026-04-02T09:26:48.397Z" }, + { url = "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282, upload-time = "2026-04-02T09:26:49.684Z" }, + { url = "https://files.pythonhosted.org/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595, upload-time = "2026-04-02T09:26:50.915Z" }, + { url = "https://files.pythonhosted.org/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986, upload-time = "2026-04-02T09:26:52.197Z" }, + { url = "https://files.pythonhosted.org/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711, upload-time = "2026-04-02T09:26:53.49Z" }, + { url = "https://files.pythonhosted.org/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036, upload-time = "2026-04-02T09:26:54.975Z" }, + { url = "https://files.pythonhosted.org/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998, upload-time = "2026-04-02T09:26:56.303Z" }, + { url = "https://files.pythonhosted.org/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056, upload-time = "2026-04-02T09:26:57.554Z" }, + { url = "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537, upload-time = "2026-04-02T09:26:58.843Z" }, + { url = "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176, upload-time = "2026-04-02T09:27:00.437Z" }, + { url = "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723, upload-time = "2026-04-02T09:27:02.021Z" }, + { url = "https://files.pythonhosted.org/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085, upload-time = "2026-04-02T09:27:03.192Z" }, + { url = "https://files.pythonhosted.org/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819, upload-time = "2026-04-02T09:27:04.454Z" }, + { url = "https://files.pythonhosted.org/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915, upload-time = "2026-04-02T09:27:05.971Z" }, + { url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" }, + { url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" }, + { url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" }, + { url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" }, + { url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" }, + { url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" }, + { url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" }, + { url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" }, + { url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" }, + { url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" }, + { url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" }, + { url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" }, + { url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" }, + { url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" }, + { url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" }, + { url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" }, + { url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" }, + { url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" }, + { url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" }, + { url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" }, + { url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" }, + { url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" }, + { url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" }, + { url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" }, + { url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" }, + { url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/12/46/fce169ad09419b8e8a5a81db61e08cd7b9fd31332221b84bd176fe0a3136/charset_normalizer-3.4.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e5f4d355f0a2b1a31bc3edec6795b46324349c9cb25eed068049e4f472fb4259", size = 283148, upload-time = "2026-04-02T09:27:52.419Z" }, + { url = "https://files.pythonhosted.org/packages/81/76/14ab25789e14f83124c4318f0edbbf15a6ed535bd3d88720c42001a954df/charset_normalizer-3.4.7-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:16d971e29578a5e97d7117866d15889a4a07befe0e87e703ed63cd90cb348c01", size = 192457, upload-time = "2026-04-02T09:27:53.681Z" }, + { url = "https://files.pythonhosted.org/packages/6c/0e/0f722c41d983dd204b3142606fbfcdbb0a33c34b9b031ef3c1fe9e8187ad/charset_normalizer-3.4.7-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dca4bbc466a95ba9c0234ef56d7dd9509f63da22274589ebd4ed7f1f4d4c54e3", size = 209614, upload-time = "2026-04-02T09:27:55.01Z" }, + { url = "https://files.pythonhosted.org/packages/ef/ec/e7961eea9977a4d5ac920627e78938784272cb9b752cf1209da91e93d006/charset_normalizer-3.4.7-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e80c8378d8f3d83cd3164da1ad2df9e37a666cdde7b1cb2298ed0b558064be30", size = 205833, upload-time = "2026-04-02T09:27:56.648Z" }, + { url = "https://files.pythonhosted.org/packages/17/85/cacf6d45cff52be431468ee4cfa6f625eb622ab8f23a892218af8c77094d/charset_normalizer-3.4.7-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:36836d6ff945a00b88ba1e4572d721e60b5b8c98c155d465f56ad19d68f23734", size = 199240, upload-time = "2026-04-02T09:27:57.95Z" }, + { url = "https://files.pythonhosted.org/packages/0e/f1/40a59aae52edc5275e85813cbc49621c10758f481deeb27f71c97406cda0/charset_normalizer-3.4.7-cp38-cp38-manylinux_2_31_armv7l.whl", hash = "sha256:bd9b23791fe793e4968dba0c447e12f78e425c59fc0e3b97f6450f4781f3ee60", size = 188301, upload-time = "2026-04-02T09:27:59.351Z" }, + { url = "https://files.pythonhosted.org/packages/96/53/6ea2906da0fd3773d57398e7cee5628d004d844b0c4903ea3038ae8488cd/charset_normalizer-3.4.7-cp38-cp38-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aef65cd602a6d0e0ff6f9930fcb1c8fec60dd2cfcb6facaf4bdb0e5873042db0", size = 197431, upload-time = "2026-04-02T09:28:00.634Z" }, + { url = "https://files.pythonhosted.org/packages/52/f9/47a52cbcce0140f612ef7a37797b2929244bcaaf2f83ade3775429457252/charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:82b271f5137d07749f7bf32f70b17ab6eaabedd297e75dce75081a24f76eb545", size = 195156, upload-time = "2026-04-02T09:28:02.312Z" }, + { url = "https://files.pythonhosted.org/packages/76/79/0e09d2169b7ba38a04e9660669d124ea688605f66189030e4c2be44d8e27/charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:1efde3cae86c8c273f1eb3b287be7d8499420cf2fe7585c41d370d3e790054a5", size = 188708, upload-time = "2026-04-02T09:28:03.949Z" }, + { url = "https://files.pythonhosted.org/packages/75/20/8b3cefb78df39d40272d7831dda07b51875d89af1f390f97a801eaedec78/charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:c593052c465475e64bbfe5dbd81680f64a67fdc752c56d7a0ae205dc8aeefe0f", size = 211495, upload-time = "2026-04-02T09:28:05.301Z" }, + { url = "https://files.pythonhosted.org/packages/47/3f/9bb0864a92b4abf0ec0d1f40546297f45afd73851795e3216c899b360aa0/charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_riscv64.whl", hash = "sha256:af21eb4409a119e365397b2adbaca4c9ccab56543a65d5dbd9f920d6ac29f686", size = 197309, upload-time = "2026-04-02T09:28:07.03Z" }, + { url = "https://files.pythonhosted.org/packages/ec/5e/0739d2975ae6fd42505fdb80881ab5e99b4edfbff1d581f4cd5aa94f2d94/charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:84c018e49c3bf790f9c2771c45e9313a08c2c2a6342b162cd650258b57817706", size = 207388, upload-time = "2026-04-02T09:28:08.381Z" }, + { url = "https://files.pythonhosted.org/packages/d3/5e/8161a7bbf4a7f88d0409091ab5a5762c014913c9ef80a48b50f806140918/charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dd915403e231e6b1809fe9b6d9fc55cf8fb5e02765ac625d9cd623342a7905d7", size = 201139, upload-time = "2026-04-02T09:28:09.73Z" }, + { url = "https://files.pythonhosted.org/packages/04/2a/c1f1f791467d865b48b749842c895668229e553dd79b71ad80498a0b646f/charset_normalizer-3.4.7-cp38-cp38-win32.whl", hash = "sha256:320ade88cfb846b8cd6b4ddf5ee9e80ee0c1f52401f2456b84ae1ae6a1a5f207", size = 142063, upload-time = "2026-04-02T09:28:11.394Z" }, + { url = "https://files.pythonhosted.org/packages/6f/5e/9e74560659e3f8a7650e09dac978749d408917c8e9764af13f5f81ceec53/charset_normalizer-3.4.7-cp38-cp38-win_amd64.whl", hash = "sha256:1dc8b0ea451d6e69735094606991f32867807881400f808a106ee1d963c46a83", size = 151922, upload-time = "2026-04-02T09:28:12.77Z" }, + { url = "https://files.pythonhosted.org/packages/01/1b/ef725f8eb19b5a261b30f78efa9252ef9d017985cb499102f6f49834cd12/charset_normalizer-3.4.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:177a0ba5f0211d488e295aaf82707237e331c24788d8d76c96c5a41594723217", size = 299121, upload-time = "2026-04-02T09:28:14.372Z" }, + { url = "https://files.pythonhosted.org/packages/a3/22/2f12878fbc680fbbb52386cd39a379801f62eaca74fc8b323381325f0f04/charset_normalizer-3.4.7-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e0d51f618228538a3e8f46bd246f87a6cd030565e015803691603f55e12afb5", size = 200612, upload-time = "2026-04-02T09:28:16.162Z" }, + { url = "https://files.pythonhosted.org/packages/bc/b6/10c84e789126ca97d4a7228863a30481e786980a8b8cfcbf4f30658ca63c/charset_normalizer-3.4.7-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:14265bfe1f09498b9d8ec91e9ec9fa52775edf90fcbde092b25f4a33d444fea9", size = 221041, upload-time = "2026-04-02T09:28:17.554Z" }, + { url = "https://files.pythonhosted.org/packages/21/7b/c414866a138400b2e81973d006da7f694cfeaf895ef07d2cba9a8743841a/charset_normalizer-3.4.7-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:87fad7d9ba98c86bcb41b2dc8dbb326619be2562af1f8ff50776a39e55721c5a", size = 216323, upload-time = "2026-04-02T09:28:18.863Z" }, + { url = "https://files.pythonhosted.org/packages/2e/92/bdcf94997e06b223d826df3abed45a5ad6e17f609b7df9d25cd23b5bde30/charset_normalizer-3.4.7-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f22dec1690b584cea26fade98b2435c132c1b5f68e39f5a0b7627cd7ae31f1dc", size = 208419, upload-time = "2026-04-02T09:28:20.332Z" }, + { url = "https://files.pythonhosted.org/packages/1a/64/3f9142293c88b1b10e199649ed1330f070c2a68e305335a5819fa7f25fa7/charset_normalizer-3.4.7-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:d61f00a0869d77422d9b2aba989e2d24afa6ffd552af442e0e58de4f35ea6d00", size = 195016, upload-time = "2026-04-02T09:28:21.657Z" }, + { url = "https://files.pythonhosted.org/packages/c1/d1/d8a6b7dd5c5636b76ce0d080bc57d8e56c7bbd6bc2ac941529a35e41d84a/charset_normalizer-3.4.7-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6370e8686f662e6a3941ee48ed4742317cafbe5707e36406e9df792cdb535776", size = 206115, upload-time = "2026-04-02T09:28:23.259Z" }, + { url = "https://files.pythonhosted.org/packages/dd/8c/60ebe912379627d023eb96995b40bc50308729f210f43d66109ca0a7bbd2/charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a6c5863edfbe888d9eff9c8b8087354e27618d9da76425c119293f11712a6319", size = 204022, upload-time = "2026-04-02T09:28:24.779Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2a/41816ceda78a551cbfdfbeab6f3891152b0e3f758ce6580c2c18c829f774/charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:ed065083d0898c9d5b4bbec7b026fd755ff7454e6e8b73a67f8c744b13986e24", size = 195914, upload-time = "2026-04-02T09:28:26.181Z" }, + { url = "https://files.pythonhosted.org/packages/8f/9b/7c7f4b7f11525fcbdfba752455314ac60646bae91cdd671d531c1f7a97c6/charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2cd4a60d0e2fb04537162c62bbbb4182f53541fe0ede35cdf270a1c1e723cc42", size = 222159, upload-time = "2026-04-02T09:28:27.504Z" }, + { url = "https://files.pythonhosted.org/packages/9f/57/301682e7469bdbfa2ce219a804f0668b2266ab8520570d85d3b3ef483ea3/charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:813c0e0132266c08eb87469a642cb30aaff57c5f426255419572aaeceeaa7bf4", size = 206154, upload-time = "2026-04-02T09:28:28.848Z" }, + { url = "https://files.pythonhosted.org/packages/20/ec/90339ff5cdc598b265748c1f231c7d7fbd9123a92cee10f757e0b1448de4/charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:07d9e39b01743c3717745f4c530a6349eadbfa043c7577eef86c502c15df2c67", size = 217423, upload-time = "2026-04-02T09:28:30.248Z" }, + { url = "https://files.pythonhosted.org/packages/2e/e7/a7a6147f8e3375676309cf584b25c72a3bab784ea4085b0011fa07b23aeb/charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c0f081d69a6e58272819b70288d3221a6ee64b98df852631c80f293514d3b274", size = 210604, upload-time = "2026-04-02T09:28:31.736Z" }, + { url = "https://files.pythonhosted.org/packages/1a/62/d9340c7a79c393e57807d7fb6c57e82060687891f81b74d3201958b919c1/charset_normalizer-3.4.7-cp39-cp39-win32.whl", hash = "sha256:8751d2787c9131302398b11e6c8068053dcb55d5a8964e114b6e196cf16cb366", size = 144631, upload-time = "2026-04-02T09:28:33.158Z" }, + { url = "https://files.pythonhosted.org/packages/21/e7/92901117e2ddc8facfe8235a3ecd4eb482185b2ad5d5b6606b37c1afea06/charset_normalizer-3.4.7-cp39-cp39-win_amd64.whl", hash = "sha256:12a6fff75f6bc66711b73a2f0addfc4c8c15a20e805146a02d147a318962c444", size = 154710, upload-time = "2026-04-02T09:28:34.557Z" }, + { url = "https://files.pythonhosted.org/packages/cc/4f/e1fb138201ad9a32499dd9a98aa4a5a5441fbf7f56b52b619a54b7ee8777/charset_normalizer-3.4.7-cp39-cp39-win_arm64.whl", hash = "sha256:bb8cc7534f51d9a017b93e3e85b260924f909601c3df002bcdb58ddb4dc41a5c", size = 143716, upload-time = "2026-04-02T09:28:35.908Z" }, + { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" }, +] + +[[package]] +name = "chemparse" +version = "0.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/82/81118c32a309408fcbcd5fc561d8f0ba0ab6f977dc4c87fb6b62e25e1d64/chemparse-0.3.2.tar.gz", hash = "sha256:f46e24081f69c3bddd03447a1ab0370c286607c6b6c146b1647f6d57beb26c3d", size = 5550, upload-time = "2025-03-11T18:59:40.321Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/cc/f0a932c8a9cd00ebdadd177c4dd312dd11771ea1856b2b86f57995e6e249/chemparse-0.3.2-py3-none-any.whl", hash = "sha256:67169faa673f804a518fac32e3b2072ef8c0d8c9df557b2d4f385424a750d819", size = 5869, upload-time = "2025-03-11T18:59:39.241Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "filelock" +version = "3.16.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/db/3ef5bb276dae18d6ec2124224403d1d67bccdbefc17af4cc8f553e341ab1/filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435", size = 18037, upload-time = "2024-09-17T19:02:01.779Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/f8/feced7779d755758a52d1f6635d990b8d98dc0a29fa568bbe0625f18fdf3/filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0", size = 16163, upload-time = "2024-09-17T19:02:00.268Z" }, +] + +[[package]] +name = "filelock" +version = "3.19.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/40/bb/0ab3e58d22305b6f5440629d20683af28959bf793d98d11950e305c1c326/filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58", size = 17687, upload-time = "2025-08-14T16:56:03.016Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/14/42b2651a2f46b022ccd948bca9f2d5af0fd8929c4eec235b8d6d844fbe67/filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d", size = 15988, upload-time = "2025-08-14T16:56:01.633Z" }, +] + +[[package]] +name = "filelock" +version = "3.29.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/b5/fe/997687a931ab51049acce6fa1f23e8f01216374ea81374ddee763c493db5/filelock-3.29.0.tar.gz", hash = "sha256:69974355e960702e789734cb4871f884ea6fe50bd8404051a3530bc07809cf90", size = 57571, upload-time = "2026-04-19T15:39:10.068Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl", hash = "sha256:96f5f6344709aa1572bbf631c640e4ebeeb519e08da902c39a001882f30ac258", size = 39812, upload-time = "2026-04-19T15:39:08.752Z" }, +] + +[[package]] +name = "gdown" +version = "5.2.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.9.*'", + "python_full_version < '3.9'", +] +dependencies = [ + { name = "beautifulsoup4", marker = "python_full_version < '3.10'" }, + { name = "filelock", version = "3.16.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "filelock", version = "3.19.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "requests", version = "2.32.4", source = { registry = "https://pypi.org/simple" }, extra = ["socks"], marker = "python_full_version < '3.9'" }, + { name = "requests", version = "2.32.5", source = { registry = "https://pypi.org/simple" }, extra = ["socks"], marker = "python_full_version == '3.9.*'" }, + { name = "tqdm", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5d/e2/831920e510e3cce91627adab9997944b32842f7cd0dc4c023f24655531e0/gdown-5.2.2.tar.gz", hash = "sha256:a4bafbaa627d67bdaa68d274e9e957127ba810b4bcf4505e2b57ea45aa0aa1e6", size = 360218, upload-time = "2026-04-12T06:00:10.571Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/ad/0e30d078a2cc84a17a81c0a1f226d617edf12ddd375dafe021fb66a2c8b0/gdown-5.2.2-py3-none-any.whl", hash = "sha256:e1ed2638abb931668725c79298900629b31e20f02b7c55a2685e9249edb51e09", size = 18630, upload-time = "2026-04-12T06:00:08.346Z" }, +] + +[[package]] +name = "gdown" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "beautifulsoup4", marker = "python_full_version >= '3.10'" }, + { name = "filelock", version = "3.29.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "requests", version = "2.33.1", source = { registry = "https://pypi.org/simple" }, extra = ["socks"], marker = "python_full_version >= '3.10'" }, + { name = "tqdm", marker = "python_full_version >= '3.10'" }, + { name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' and python_full_version < '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/01/9e0280ba321f73295374765dc3c0b1e03058188a592a48a321376f9eb092/gdown-6.0.0.tar.gz", hash = "sha256:1f1f735a174ef3599fca95786aafac1219b9d85d4c729ccb95e674996c47fd44", size = 262729, upload-time = "2026-04-12T06:37:40.182Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/fd/a382bb6684b1fdbe5cd19aa980a04a67f6c91efd0e1e627f93614fe2d24e/gdown-6.0.0-py3-none-any.whl", hash = "sha256:c82d39a6b09ed7778012515c2fa4ab4dc36d7789300cd0b16b87d3a3e4a09955", size = 18243, upload-time = "2026-04-12T06:37:38.209Z" }, +] + +[[package]] +name = "idna" +version = "3.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/cc/762dfb036166873f0059f3b7de4565e1b5bc3d6f28a414c13da27e442f99/idna-3.13.tar.gz", hash = "sha256:585ea8fe5d69b9181ec1afba340451fba6ba764af97026f92a91d4eef164a242", size = 194210, upload-time = "2026-04-22T16:42:42.314Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/13/ad7d7ca3808a898b4612b6fe93cde56b53f3034dcde235acb1f0e1df24c6/idna-3.13-py3-none-any.whl", hash = "sha256:892ea0cde124a99ce773decba204c5552b69c3c67ffd5f232eb7696135bc8bb3", size = 68629, upload-time = "2026-04-22T16:42:40.909Z" }, +] + +[[package]] +name = "importlib-metadata" +version = "8.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp", marker = "python_full_version < '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/12/33e59336dca5be0c398a7482335911a33aa0e20776128f038019f1a95f1b/importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7", size = 55304, upload-time = "2024-09-11T14:56:08.937Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/d9/a1e041c5e7caa9a05c925f4bdbdfb7f006d1f74996af53467bc394c97be7/importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b", size = 26514, upload-time = "2024-09-11T14:56:07.019Z" }, +] + +[[package]] +name = "joblib" +version = "1.4.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +sdist = { url = "https://files.pythonhosted.org/packages/64/33/60135848598c076ce4b231e1b1895170f45fbcaeaa2c9d5e38b04db70c35/joblib-1.4.2.tar.gz", hash = "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e", size = 2116621, upload-time = "2024-05-02T12:15:05.765Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/29/df4b9b42f2be0b623cbd5e2140cafcaa2bef0759a00b7b70104dcfe2fb51/joblib-1.4.2-py3-none-any.whl", hash = "sha256:06d478d5674cbc267e7496a410ee875abd68e4340feff4490bcb7afb88060ae6", size = 301817, upload-time = "2024-05-02T12:15:00.765Z" }, +] + +[[package]] +name = "joblib" +version = "1.5.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/41/f2/d34e8b3a08a9cc79a50b2208a93dce981fe615b64d5a4d4abee421d898df/joblib-1.5.3.tar.gz", hash = "sha256:8561a3269e6801106863fd0d6d84bb737be9e7631e33aaed3fb9ce5953688da3", size = 331603, upload-time = "2025-12-15T08:41:46.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl", hash = "sha256:5fc3c5039fc5ca8c0276333a188bbd59d6b7ab37fe6632daa76bc7f9ec18e713", size = 309071, upload-time = "2025-12-15T08:41:44.973Z" }, +] + +[[package]] +name = "lightgbm" +version = "4.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "1.24.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "scipy", version = "1.10.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "scipy", version = "1.13.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/68/0b/a2e9f5c5da7ef047cc60cef37f86185088845e8433e54d2e7ed439cce8a3/lightgbm-4.6.0.tar.gz", hash = "sha256:cb1c59720eb569389c0ba74d14f52351b573af489f230032a1c9f314f8bab7fe", size = 1703705, upload-time = "2025-02-15T04:03:03.111Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/75/cffc9962cca296bc5536896b7e65b4a7cdeb8db208e71b9c0133c08f8f7e/lightgbm-4.6.0-py3-none-macosx_10_15_x86_64.whl", hash = "sha256:b7a393de8a334d5c8e490df91270f0763f83f959574d504c7ccb9eee4aef70ed", size = 2010151, upload-time = "2025-02-15T04:02:50.961Z" }, + { url = "https://files.pythonhosted.org/packages/21/1b/550ee378512b78847930f5d74228ca1fdba2a7fbdeaac9aeccc085b0e257/lightgbm-4.6.0-py3-none-macosx_12_0_arm64.whl", hash = "sha256:2dafd98d4e02b844ceb0b61450a660681076b1ea6c7adb8c566dfd66832aafad", size = 1592172, upload-time = "2025-02-15T04:02:53.937Z" }, + { url = "https://files.pythonhosted.org/packages/64/41/4fbde2c3d29e25ee7c41d87df2f2e5eda65b431ee154d4d462c31041846c/lightgbm-4.6.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:4d68712bbd2b57a0b14390cbf9376c1d5ed773fa2e71e099cac588703b590336", size = 3454567, upload-time = "2025-02-15T04:02:56.443Z" }, + { url = "https://files.pythonhosted.org/packages/42/86/dabda8fbcb1b00bcfb0003c3776e8ade1aa7b413dff0a2c08f457dace22f/lightgbm-4.6.0-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:cb19b5afea55b5b61cbb2131095f50538bd608a00655f23ad5d25ae3e3bf1c8d", size = 3569831, upload-time = "2025-02-15T04:02:58.925Z" }, + { url = "https://files.pythonhosted.org/packages/5e/23/f8b28ca248bb629b9e08f877dd2965d1994e1674a03d67cd10c5246da248/lightgbm-4.6.0-py3-none-win_amd64.whl", hash = "sha256:37089ee95664b6550a7189d887dbf098e3eadab03537e411f52c63c121e3ba4b", size = 1451509, upload-time = "2025-02-15T04:03:01.515Z" }, +] + +[[package]] +name = "llvmlite" +version = "0.41.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +sdist = { url = "https://files.pythonhosted.org/packages/01/c6/bc6634da9f58edf91a1a002280c6380f404715245a49a46234b1d9d9585a/llvmlite-0.41.1.tar.gz", hash = "sha256:f19f767a018e6ec89608e1f6b13348fa2fcde657151137cb64e56d48598a92db", size = 146564, upload-time = "2023-10-18T13:36:19.997Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/e2/931d2d35350d3c375d6c43a04982fb5d8f90c15002de024a814d919a1e67/llvmlite-0.41.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c1e1029d47ee66d3a0c4d6088641882f75b93db82bd0e6178f7bd744ebce42b9", size = 31032708, upload-time = "2023-10-18T13:34:42.554Z" }, + { url = "https://files.pythonhosted.org/packages/47/d5/2d6261a47dd6b1005d60a6e37a3de007ddc15a297f146ee905605e3cbc3e/llvmlite-0.41.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:150d0bc275a8ac664a705135e639178883293cf08c1a38de3bbaa2f693a0a867", size = 28753858, upload-time = "2023-10-18T13:34:47.195Z" }, + { url = "https://files.pythonhosted.org/packages/da/5e/afce5c757df730be7706ae671f5fce2cacdf4e46eed2b1a916e595453388/llvmlite-0.41.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1eee5cf17ec2b4198b509272cf300ee6577229d237c98cc6e63861b08463ddc6", size = 42623412, upload-time = "2023-10-18T13:34:54.423Z" }, + { url = "https://files.pythonhosted.org/packages/57/7d/ef28d5812f852b93bd2a583d00cdcde56833d31b645ae0eaa7e71eecfb4e/llvmlite-0.41.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dd0338da625346538f1173a17cabf21d1e315cf387ca21b294ff209d176e244", size = 43623334, upload-time = "2023-10-18T13:34:58.57Z" }, + { url = "https://files.pythonhosted.org/packages/48/39/7db84e829b874f90316e9f6d7497f761b9a645abe61f8c9588b292876314/llvmlite-0.41.1-cp310-cp310-win32.whl", hash = "sha256:fa1469901a2e100c17eb8fe2678e34bd4255a3576d1a543421356e9c14d6e2ae", size = 23414330, upload-time = "2023-10-18T13:35:03.791Z" }, + { url = "https://files.pythonhosted.org/packages/1f/05/cfc9c51aff9adbb6f7df93df3fb20100ee5d9c3d0445ade3df8057e50b0a/llvmlite-0.41.1-cp310-cp310-win_amd64.whl", hash = "sha256:2b76acee82ea0e9304be6be9d4b3840208d050ea0dcad75b1635fa06e949a0ae", size = 28108282, upload-time = "2023-10-18T13:35:07.674Z" }, + { url = "https://files.pythonhosted.org/packages/96/e0/54b0a58969b208b3a66ed30f560e127f52c3add6027b8e67d0a00ea0b178/llvmlite-0.41.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:210e458723436b2469d61b54b453474e09e12a94453c97ea3fbb0742ba5a83d8", size = 31032706, upload-time = "2023-10-18T13:35:11.181Z" }, + { url = "https://files.pythonhosted.org/packages/12/f4/9bc78ef5d52cbe595a849d332c872583a6294afff6f5317de6dce4abbe55/llvmlite-0.41.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:855f280e781d49e0640aef4c4af586831ade8f1a6c4df483fb901cbe1a48d127", size = 28753841, upload-time = "2023-10-18T13:35:14.601Z" }, + { url = "https://files.pythonhosted.org/packages/b5/b0/48caac8ea81d6a11a1917dd98258ea67c9b8a78a31fa45b89ae6aa94e678/llvmlite-0.41.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b67340c62c93a11fae482910dc29163a50dff3dfa88bc874872d28ee604a83be", size = 42623411, upload-time = "2023-10-18T13:35:19.577Z" }, + { url = "https://files.pythonhosted.org/packages/0a/dd/e10658cb0e4ad8364b37ae2a6187ebb6b3b35e08b36ef9e98790b9f2f437/llvmlite-0.41.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2181bb63ef3c607e6403813421b46982c3ac6bfc1f11fa16a13eaafb46f578e6", size = 43623334, upload-time = "2023-10-18T13:35:24.741Z" }, + { url = "https://files.pythonhosted.org/packages/14/fe/d3a9c921a2adad2e9f24693754983f290e0dac9410666e802b9dba4d0218/llvmlite-0.41.1-cp311-cp311-win_amd64.whl", hash = "sha256:9564c19b31a0434f01d2025b06b44c7ed422f51e719ab5d24ff03b7560066c9a", size = 28092676, upload-time = "2023-10-18T13:35:28.992Z" }, + { url = "https://files.pythonhosted.org/packages/04/ad/f5d3f8accf618e136a205de92db49516d1e9a868415164dab407107cece5/llvmlite-0.41.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5940bc901fb0325970415dbede82c0b7f3e35c2d5fd1d5e0047134c2c46b3281", size = 31032702, upload-time = "2023-10-18T13:35:32.88Z" }, + { url = "https://files.pythonhosted.org/packages/a7/07/c5c053cebc0d16fbbe5329fd10b9d141681fba7a805796e8517a1217d4e8/llvmlite-0.41.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8b0a9a47c28f67a269bb62f6256e63cef28d3c5f13cbae4fab587c3ad506778b", size = 28753852, upload-time = "2023-10-18T13:35:36.198Z" }, + { url = "https://files.pythonhosted.org/packages/37/c0/2fcbb511e4af6f4bbd053b1ce16ae216ff088f3255add6b516c34175bfbc/llvmlite-0.41.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8afdfa6da33f0b4226af8e64cfc2b28986e005528fbf944d0a24a72acfc9432", size = 42623411, upload-time = "2023-10-18T13:35:39.862Z" }, + { url = "https://files.pythonhosted.org/packages/46/a5/de824aba87a62e0da43f9b590ac91709d460a61e03f1b430b83ca64b76dc/llvmlite-0.41.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8454c1133ef701e8c050a59edd85d238ee18bb9a0eb95faf2fca8b909ee3c89a", size = 43623331, upload-time = "2023-10-18T13:35:44.69Z" }, + { url = "https://files.pythonhosted.org/packages/7e/f0/066eb0d43d276da3fc39cf61ad956293a2d3c97f4e976fcc58d3519862e8/llvmlite-0.41.1-cp38-cp38-win32.whl", hash = "sha256:2d92c51e6e9394d503033ffe3292f5bef1566ab73029ec853861f60ad5c925d0", size = 23414330, upload-time = "2023-10-18T13:35:48.729Z" }, + { url = "https://files.pythonhosted.org/packages/1b/57/6da776f73951956ece7d8fda605c69d512a6724bb7b255adf9d48ef7941d/llvmlite-0.41.1-cp38-cp38-win_amd64.whl", hash = "sha256:df75594e5a4702b032684d5481db3af990b69c249ccb1d32687b8501f0689432", size = 28108279, upload-time = "2023-10-18T13:35:51.863Z" }, + { url = "https://files.pythonhosted.org/packages/fc/7a/c6741000d767fed4b339fcd4fa65afbc5fe776473d5f9e9c41eceab0a7c6/llvmlite-0.41.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:04725975e5b2af416d685ea0769f4ecc33f97be541e301054c9f741003085802", size = 31032700, upload-time = "2023-10-18T13:35:55.347Z" }, + { url = "https://files.pythonhosted.org/packages/8b/66/b3edcd1b2631af2daaf7a09520c5ff863e459eb17dd28f7d4cac63f3532c/llvmlite-0.41.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bf14aa0eb22b58c231243dccf7e7f42f7beec48970f2549b3a6acc737d1a4ba4", size = 28753838, upload-time = "2023-10-18T13:35:58.786Z" }, + { url = "https://files.pythonhosted.org/packages/d3/5b/5af7cc0d2097ada6372b8b90986b45267b7d137a4fda58d7534d867572b2/llvmlite-0.41.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92c32356f669e036eb01016e883b22add883c60739bc1ebee3a1cc0249a50828", size = 42623409, upload-time = "2023-10-18T13:36:02.781Z" }, + { url = "https://files.pythonhosted.org/packages/be/a6/55020df7f04666bceb3ccc5dc0cb0812d68d5fdec883ff015a9110928829/llvmlite-0.41.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24091a6b31242bcdd56ae2dbea40007f462260bc9bdf947953acc39dffd54f8f", size = 43623330, upload-time = "2023-10-18T13:36:09.753Z" }, + { url = "https://files.pythonhosted.org/packages/07/73/0aef09cf017a4f8b9c0eb371d273e7bb3e514992c4f1d8ed09e8f81f4602/llvmlite-0.41.1-cp39-cp39-win32.whl", hash = "sha256:880cb57ca49e862e1cd077104375b9d1dfdc0622596dfa22105f470d7bacb309", size = 23414330, upload-time = "2023-10-18T13:36:13.771Z" }, + { url = "https://files.pythonhosted.org/packages/88/99/aa829162a871f0f91dba9a0344726449cafbff4e9e55d7a714051cbb6215/llvmlite-0.41.1-cp39-cp39-win_amd64.whl", hash = "sha256:92f093986ab92e71c9ffe334c002f96defc7986efda18397d0f08534f3ebdc4d", size = 28092675, upload-time = "2023-10-18T13:36:17.19Z" }, +] + +[[package]] +name = "llvmlite" +version = "0.43.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/3d/f513755f285db51ab363a53e898b85562e950f79a2e6767a364530c2f645/llvmlite-0.43.0.tar.gz", hash = "sha256:ae2b5b5c3ef67354824fb75517c8db5fbe93bc02cd9671f3c62271626bc041d5", size = 157069, upload-time = "2024-06-13T18:09:32.641Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/23/ff/6ca7e98998b573b4bd6566f15c35e5c8bea829663a6df0c7aa55ab559da9/llvmlite-0.43.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a289af9a1687c6cf463478f0fa8e8aa3b6fb813317b0d70bf1ed0759eab6f761", size = 31064408, upload-time = "2024-06-13T18:08:13.462Z" }, + { url = "https://files.pythonhosted.org/packages/ca/5c/a27f9257f86f0cda3f764ff21d9f4217b9f6a0d45e7a39ecfa7905f524ce/llvmlite-0.43.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d4fd101f571a31acb1559ae1af30f30b1dc4b3186669f92ad780e17c81e91bc", size = 28793153, upload-time = "2024-06-13T18:08:17.336Z" }, + { url = "https://files.pythonhosted.org/packages/7e/3c/4410f670ad0a911227ea2ecfcba9f672a77cf1924df5280c4562032ec32d/llvmlite-0.43.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d434ec7e2ce3cc8f452d1cd9a28591745de022f931d67be688a737320dfcead", size = 42857276, upload-time = "2024-06-13T18:08:21.071Z" }, + { url = "https://files.pythonhosted.org/packages/c6/21/2ffbab5714e72f2483207b4a1de79b2eecd9debbf666ff4e7067bcc5c134/llvmlite-0.43.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6912a87782acdff6eb8bf01675ed01d60ca1f2551f8176a300a886f09e836a6a", size = 43871781, upload-time = "2024-06-13T18:08:26.32Z" }, + { url = "https://files.pythonhosted.org/packages/f2/26/b5478037c453554a61625ef1125f7e12bb1429ae11c6376f47beba9b0179/llvmlite-0.43.0-cp310-cp310-win_amd64.whl", hash = "sha256:14f0e4bf2fd2d9a75a3534111e8ebeb08eda2f33e9bdd6dfa13282afacdde0ed", size = 28123487, upload-time = "2024-06-13T18:08:30.348Z" }, + { url = "https://files.pythonhosted.org/packages/95/8c/de3276d773ab6ce3ad676df5fab5aac19696b2956319d65d7dd88fb10f19/llvmlite-0.43.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3e8d0618cb9bfe40ac38a9633f2493d4d4e9fcc2f438d39a4e854f39cc0f5f98", size = 31064409, upload-time = "2024-06-13T18:08:34.006Z" }, + { url = "https://files.pythonhosted.org/packages/ee/e1/38deed89ced4cf378c61e232265cfe933ccde56ae83c901aa68b477d14b1/llvmlite-0.43.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0a9a1a39d4bf3517f2af9d23d479b4175ead205c592ceeb8b89af48a327ea57", size = 28793149, upload-time = "2024-06-13T18:08:37.42Z" }, + { url = "https://files.pythonhosted.org/packages/2f/b2/4429433eb2dc8379e2cb582502dca074c23837f8fd009907f78a24de4c25/llvmlite-0.43.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1da416ab53e4f7f3bc8d4eeba36d801cc1894b9fbfbf2022b29b6bad34a7df2", size = 42857277, upload-time = "2024-06-13T18:08:40.822Z" }, + { url = "https://files.pythonhosted.org/packages/6b/99/5d00a7d671b1ba1751fc9f19d3b36f3300774c6eebe2bcdb5f6191763eb4/llvmlite-0.43.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:977525a1e5f4059316b183fb4fd34fa858c9eade31f165427a3977c95e3ee749", size = 43871781, upload-time = "2024-06-13T18:08:46.41Z" }, + { url = "https://files.pythonhosted.org/packages/20/ab/ed5ed3688c6ba4f0b8d789da19fd8e30a9cf7fc5852effe311bc5aefe73e/llvmlite-0.43.0-cp311-cp311-win_amd64.whl", hash = "sha256:d5bd550001d26450bd90777736c69d68c487d17bf371438f975229b2b8241a91", size = 28107433, upload-time = "2024-06-13T18:08:50.834Z" }, + { url = "https://files.pythonhosted.org/packages/0b/67/9443509e5d2b6d8587bae3ede5598fa8bd586b1c7701696663ea8af15b5b/llvmlite-0.43.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f99b600aa7f65235a5a05d0b9a9f31150c390f31261f2a0ba678e26823ec38f7", size = 31064409, upload-time = "2024-06-13T18:08:54.375Z" }, + { url = "https://files.pythonhosted.org/packages/a2/9c/24139d3712d2d352e300c39c0e00d167472c08b3bd350c3c33d72c88ff8d/llvmlite-0.43.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:35d80d61d0cda2d767f72de99450766250560399edc309da16937b93d3b676e7", size = 28793145, upload-time = "2024-06-13T18:08:57.953Z" }, + { url = "https://files.pythonhosted.org/packages/bf/f1/4c205a48488e574ee9f6505d50e84370a978c90f08dab41a42d8f2c576b6/llvmlite-0.43.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eccce86bba940bae0d8d48ed925f21dbb813519169246e2ab292b5092aba121f", size = 42857276, upload-time = "2024-06-13T18:09:02.067Z" }, + { url = "https://files.pythonhosted.org/packages/00/5f/323c4d56e8401c50185fd0e875fcf06b71bf825a863699be1eb10aa2a9cb/llvmlite-0.43.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df6509e1507ca0760787a199d19439cc887bfd82226f5af746d6977bd9f66844", size = 43871781, upload-time = "2024-06-13T18:09:06.667Z" }, + { url = "https://files.pythonhosted.org/packages/c6/94/dea10e263655ce78d777e78d904903faae39d1fc440762be4a9dc46bed49/llvmlite-0.43.0-cp312-cp312-win_amd64.whl", hash = "sha256:7a2872ee80dcf6b5dbdc838763d26554c2a18aa833d31a2635bff16aafefb9c9", size = 28107442, upload-time = "2024-06-13T18:09:10.709Z" }, + { url = "https://files.pythonhosted.org/packages/2a/73/12925b1bbb3c2beb6d96f892ef5b4d742c34f00ddb9f4a125e9e87b22f52/llvmlite-0.43.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9cd2a7376f7b3367019b664c21f0c61766219faa3b03731113ead75107f3b66c", size = 31064410, upload-time = "2024-06-13T18:09:14.091Z" }, + { url = "https://files.pythonhosted.org/packages/cc/61/58c70aa0808a8cba825a7d98cc65bef4801b99328fba80837bfcb5fc767f/llvmlite-0.43.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:18e9953c748b105668487b7c81a3e97b046d8abf95c4ddc0cd3c94f4e4651ae8", size = 28793145, upload-time = "2024-06-13T18:09:17.531Z" }, + { url = "https://files.pythonhosted.org/packages/c8/c6/9324eb5de2ba9d99cbed853d85ba7a318652a48e077797bec27cf40f911d/llvmlite-0.43.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74937acd22dc11b33946b67dca7680e6d103d6e90eeaaaf932603bec6fe7b03a", size = 42857276, upload-time = "2024-06-13T18:09:21.377Z" }, + { url = "https://files.pythonhosted.org/packages/e0/d0/889e9705107db7b1ec0767b03f15d7b95b4c4f9fdf91928ab1c7e9ffacf6/llvmlite-0.43.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc9efc739cc6ed760f795806f67889923f7274276f0eb45092a1473e40d9b867", size = 43871777, upload-time = "2024-06-13T18:09:25.76Z" }, + { url = "https://files.pythonhosted.org/packages/df/41/73cc26a2634b538cfe813f618c91e7e9960b8c163f8f0c94a2b0f008b9da/llvmlite-0.43.0-cp39-cp39-win_amd64.whl", hash = "sha256:47e147cdda9037f94b399bf03bfd8a6b6b1f2f90be94a454e3386f006455a9b4", size = 28123489, upload-time = "2024-06-13T18:09:29.78Z" }, +] + +[[package]] +name = "llvmlite" +version = "0.47.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/01/88/a8952b6d5c21e74cbf158515b779666f692846502623e9e3c39d8e8ba25f/llvmlite-0.47.0.tar.gz", hash = "sha256:62031ce968ec74e95092184d4b0e857e444f8fdff0b8f9213707699570c33ccc", size = 193614, upload-time = "2026-03-31T18:29:53.497Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/f5/a1bde3aa8c43524b0acaf3f72fb3d80a32dd29dbb42d7dc434f84584cdcc/llvmlite-0.47.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41270b0b1310717f717cf6f2a9c68d3c43bd7905c33f003825aebc361d0d1b17", size = 37232772, upload-time = "2026-03-31T18:28:12.198Z" }, + { url = "https://files.pythonhosted.org/packages/7c/fb/76d88fc05ee1f9c1a6efe39eb493c4a727e5d1690412469017cd23bcb776/llvmlite-0.47.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f9d118bc1dd7623e0e65ca9ac485ec6dd543c3b77bc9928ddc45ebd34e1e30a7", size = 56275179, upload-time = "2026-03-31T18:28:15.725Z" }, + { url = "https://files.pythonhosted.org/packages/4d/08/29da7f36217abd56a0c389ef9a18bea47960826e691ced1a36c92c6ce93c/llvmlite-0.47.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ea5cfb04a6ab5b18e46be72b41b015975ba5980c4ddb41f1975b83e19031063", size = 55128632, upload-time = "2026-03-31T18:28:19.946Z" }, + { url = "https://files.pythonhosted.org/packages/df/f8/5e12e9ed447d65f04acf6fcf2d79cded2355640b5131a46cee4c99a5949d/llvmlite-0.47.0-cp310-cp310-win_amd64.whl", hash = "sha256:166b896a2262a2039d5fc52df5ee1659bd1ccd081183df7a2fba1b74702dd5ea", size = 38138402, upload-time = "2026-03-31T18:28:23.327Z" }, + { url = "https://files.pythonhosted.org/packages/34/0b/b9d1911cfefa61399821dfb37f486d83e0f42630a8d12f7194270c417002/llvmlite-0.47.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:74090f0dcfd6f24ebbef3f21f11e38111c4d7e6919b54c4416e1e357c3446b07", size = 37232770, upload-time = "2026-03-31T18:28:26.765Z" }, + { url = "https://files.pythonhosted.org/packages/46/27/5799b020e4cdfb25a7c951c06a96397c135efcdc21b78d853bbd9c814c7d/llvmlite-0.47.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ca14f02e29134e837982497959a8e2193d6035235de1cb41a9cb2bd6da4eedbb", size = 56275177, upload-time = "2026-03-31T18:28:31.01Z" }, + { url = "https://files.pythonhosted.org/packages/7e/51/48a53fedf01cb1f3f43ef200be17ebf83c8d9a04018d3783c1a226c342c2/llvmlite-0.47.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:12a69d4bb05f402f30477e21eeabe81911e7c251cecb192bed82cd83c9db10d8", size = 55128631, upload-time = "2026-03-31T18:28:36.046Z" }, + { url = "https://files.pythonhosted.org/packages/a2/50/59227d06bdc96e23322713c381af4e77420949d8cd8a042c79e0043096cc/llvmlite-0.47.0-cp311-cp311-win_amd64.whl", hash = "sha256:c37d6eb7aaabfa83ab9c2ff5b5cdb95a5e6830403937b2c588b7490724e05327", size = 38138400, upload-time = "2026-03-31T18:28:40.076Z" }, + { url = "https://files.pythonhosted.org/packages/fa/48/4b7fe0e34c169fa2f12532916133e0b219d2823b540733651b34fdac509a/llvmlite-0.47.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:306a265f408c259067257a732c8e159284334018b4083a9e35f67d19792b164f", size = 37232769, upload-time = "2026-03-31T18:28:43.735Z" }, + { url = "https://files.pythonhosted.org/packages/e6/4b/e3f2cd17822cf772a4a51a0a8080b0032e6d37b2dbe8cfb724eac4e31c52/llvmlite-0.47.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5853bf26160857c0c2573415ff4efe01c4c651e59e2c55c2a088740acfee51cd", size = 56275178, upload-time = "2026-03-31T18:28:48.342Z" }, + { url = "https://files.pythonhosted.org/packages/b6/55/a3b4a543185305a9bdf3d9759d53646ed96e55e7dfd43f53e7a421b8fbae/llvmlite-0.47.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:003bcf7fa579e14db59c1a1e113f93ab8a06b56a4be31c7f08264d1d4072d077", size = 55128632, upload-time = "2026-03-31T18:28:52.901Z" }, + { url = "https://files.pythonhosted.org/packages/2f/f5/d281ae0f79378a5a91f308ea9fdb9f9cc068fddd09629edc0725a5a8fde1/llvmlite-0.47.0-cp312-cp312-win_amd64.whl", hash = "sha256:f3079f25bdc24cd9d27c4b2b5e68f5f60c4fdb7e8ad5ee2b9b006007558f9df7", size = 38138692, upload-time = "2026-03-31T18:28:57.147Z" }, + { url = "https://files.pythonhosted.org/packages/77/6f/4615353e016799f80fa52ccb270a843c413b22361fadda2589b2922fb9b0/llvmlite-0.47.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:a3c6a735d4e1041808434f9d440faa3d78d9b4af2ee64d05a66f351883b6ceec", size = 37232771, upload-time = "2026-03-31T18:29:01.324Z" }, + { url = "https://files.pythonhosted.org/packages/31/b8/69f5565f1a280d032525878a86511eebed0645818492feeb169dfb20ae8e/llvmlite-0.47.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2699a74321189e812d476a43d6d7f652f51811e7b5aad9d9bba842a1c7927acb", size = 56275178, upload-time = "2026-03-31T18:29:05.748Z" }, + { url = "https://files.pythonhosted.org/packages/d6/da/b32cafcb926fb0ce2aa25553bf32cb8764af31438f40e2481df08884c947/llvmlite-0.47.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6c6951e2b29930227963e53ee152441f0e14be92e9d4231852102d986c761e40", size = 55128632, upload-time = "2026-03-31T18:29:11.235Z" }, + { url = "https://files.pythonhosted.org/packages/46/9f/4898b44e4042c60fafcb1162dfb7014f6f15b1ec19bf29cfea6bf26df90d/llvmlite-0.47.0-cp313-cp313-win_amd64.whl", hash = "sha256:c2e9adf8698d813a9a5efb2d4370caf344dbc1e145019851fee6a6f319ba760e", size = 38138695, upload-time = "2026-03-31T18:29:15.43Z" }, + { url = "https://files.pythonhosted.org/packages/1c/d4/33c8af00f0bf6f552d74f3a054f648af2c5bc6bece97972f3bfadce4f5ec/llvmlite-0.47.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:de966c626c35c9dff5ae7bf12db25637738d0df83fc370cf793bc94d43d92d14", size = 37232773, upload-time = "2026-03-31T18:29:19.453Z" }, + { url = "https://files.pythonhosted.org/packages/64/1d/a760e993e0c0ba6db38d46b9f48f6c7dceb8ac838824997fb9e25f97bc04/llvmlite-0.47.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ddbccff2aeaff8670368340a158abefc032fe9b3ccf7d9c496639263d00151aa", size = 56275176, upload-time = "2026-03-31T18:29:24.149Z" }, + { url = "https://files.pythonhosted.org/packages/84/3b/e679bc3b29127182a7f4aa2d2e9e5bea42adb93fb840484147d59c236299/llvmlite-0.47.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4a7b778a2e144fc64468fb9bf509ac1226c9813a00b4d7afea5d988c4e22fca", size = 55128631, upload-time = "2026-03-31T18:29:29.536Z" }, + { url = "https://files.pythonhosted.org/packages/be/f7/19e2a09c62809c9e63bbd14ce71fb92c6ff7b7b3045741bb00c781efc3c9/llvmlite-0.47.0-cp314-cp314-win_amd64.whl", hash = "sha256:694e3c2cdc472ed2bd8bd4555ca002eec4310961dd58ef791d508f57b5cc4c94", size = 39153826, upload-time = "2026-03-31T18:29:33.681Z" }, + { url = "https://files.pythonhosted.org/packages/40/a1/581a8c707b5e80efdbbe1dd94527404d33fe50bceb71f39d5a7e11bd57b7/llvmlite-0.47.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:92ec8a169a20b473c1c54d4695e371bde36489fc1efa3688e11e99beba0abf9c", size = 37232772, upload-time = "2026-03-31T18:29:37.952Z" }, + { url = "https://files.pythonhosted.org/packages/11/03/16090dd6f74ba2b8b922276047f15962fbeea0a75d5601607edb301ba945/llvmlite-0.47.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fa1cbd800edd3b20bc141521f7fd45a6185a5b84109aa6855134e81397ffe72b", size = 56275178, upload-time = "2026-03-31T18:29:42.58Z" }, + { url = "https://files.pythonhosted.org/packages/f5/cb/0abf1dd4c5286a95ffe0c1d8c67aec06b515894a0dd2ac97f5e27b82ab0b/llvmlite-0.47.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f6725179b89f03b17dabe236ff3422cb8291b4c1bf40af152826dfd34e350ae8", size = 55128632, upload-time = "2026-03-31T18:29:46.939Z" }, + { url = "https://files.pythonhosted.org/packages/4f/79/d3bbab197e86e0ff4f9c07122895b66a3e0d024247fcff7f12c473cb36d9/llvmlite-0.47.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6842cf6f707ec4be3d985a385ad03f72b2d724439e118fcbe99b2929964f0453", size = 39153839, upload-time = "2026-03-31T18:29:51.004Z" }, +] + +[[package]] +name = "msbuddy" +version = "0.3.14" +source = { editable = "." } +dependencies = [ + { name = "brain-isotopic-distribution" }, + { name = "chemparse" }, + { name = "gdown", version = "5.2.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "gdown", version = "6.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "joblib", version = "1.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "joblib", version = "1.5.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "lightgbm" }, + { name = "numba", version = "0.58.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "numba", version = "0.60.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "numba", version = "0.65.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "numpy", version = "1.24.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pandas", version = "2.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9' and python_full_version < '3.11'" }, + { name = "pandas", version = "3.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "requests", version = "2.32.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "requests", version = "2.32.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "requests", version = "2.33.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "scipy", version = "1.10.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "scipy", version = "1.13.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "tqdm" }, +] + +[package.metadata] +requires-dist = [ + { name = "brain-isotopic-distribution" }, + { name = "chemparse" }, + { name = "gdown" }, + { name = "joblib" }, + { name = "lightgbm", specifier = ">=4.6.0" }, + { name = "numba" }, + { name = "numpy" }, + { name = "pandas" }, + { name = "requests" }, + { name = "scipy" }, + { name = "tqdm" }, +] + +[[package]] +name = "numba" +version = "0.58.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +dependencies = [ + { name = "importlib-metadata", marker = "python_full_version < '3.9'" }, + { name = "llvmlite", version = "0.41.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "numpy", version = "1.24.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/13/2b/0f750d451fd961fd91d6bc86c512781fa46f9ef64813007e501482522ff9/numba-0.58.1.tar.gz", hash = "sha256:487ded0633efccd9ca3a46364b40006dbdaca0f95e99b8b83e778d1195ebcbaa", size = 2623830, upload-time = "2023-10-18T13:47:46.54Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/e6/7e2c73fcdf3c98dcc0d3035ecafab214ed9f1978ca18a149bad6416bd308/numba-0.58.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:07f2fa7e7144aa6f275f27260e73ce0d808d3c62b30cff8906ad1dec12d87bbe", size = 2588424, upload-time = "2023-10-18T13:46:37.554Z" }, + { url = "https://files.pythonhosted.org/packages/39/44/e5540c376f6b99d906f46bdf98ce4d91511a0ee89ce60a62e2498dacd6fe/numba-0.58.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7bf1ddd4f7b9c2306de0384bf3854cac3edd7b4d8dffae2ec1b925e4c436233f", size = 2591283, upload-time = "2023-10-18T13:46:40.19Z" }, + { url = "https://files.pythonhosted.org/packages/93/fa/40cf7aff6f5836412d8f5f751a92be3efd249ed322a01a5c9c1fe4c4a7a9/numba-0.58.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bc2d904d0319d7a5857bd65062340bed627f5bfe9ae4a495aef342f072880d50", size = 3349802, upload-time = "2023-10-18T13:46:42.688Z" }, + { url = "https://files.pythonhosted.org/packages/ed/13/b66627125b35f2987bd9872cf028b5e1e1ffcbc8d1e182ac4e84eed3998f/numba-0.58.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4e79b6cc0d2bf064a955934a2e02bf676bc7995ab2db929dbbc62e4c16551be6", size = 3641467, upload-time = "2023-10-18T13:46:45.099Z" }, + { url = "https://files.pythonhosted.org/packages/3b/77/fdf609595d4f8f7f7e6e0fcaf11f7223d636cae4cb5c0805f46778c08a57/numba-0.58.1-cp310-cp310-win_amd64.whl", hash = "sha256:81fe5b51532478149b5081311b0fd4206959174e660c372b94ed5364cfb37c82", size = 2606344, upload-time = "2023-10-18T13:46:47.204Z" }, + { url = "https://files.pythonhosted.org/packages/9a/ce/a1e69316135646e83bc77b9eb662e3dc9414a6fea9eef2db4fd4c9fcf5e3/numba-0.58.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bcecd3fb9df36554b342140a4d77d938a549be635d64caf8bd9ef6c47a47f8aa", size = 2588116, upload-time = "2023-10-18T13:46:49.385Z" }, + { url = "https://files.pythonhosted.org/packages/88/60/aa67255e6e166ef6541d007f22470fc459b8c7b66fb6790fd735d0bcd951/numba-0.58.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a1eaa744f518bbd60e1f7ccddfb8002b3d06bd865b94a5d7eac25028efe0e0ff", size = 2590990, upload-time = "2023-10-18T13:46:51.646Z" }, + { url = "https://files.pythonhosted.org/packages/ca/f8/18f24441783f345a805a08bdcfa7cfb9c987a6154da412c734ed04489f07/numba-0.58.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bf68df9c307fb0aa81cacd33faccd6e419496fdc621e83f1efce35cdc5e79cac", size = 3355149, upload-time = "2023-10-18T13:46:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/65/85/72649823bcbfd01820e1c60fa54eb77adb2dba8be921fe6fabdbeb011a88/numba-0.58.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:55a01e1881120e86d54efdff1be08381886fe9f04fc3006af309c602a72bc44d", size = 3647131, upload-time = "2023-10-18T13:46:57.491Z" }, + { url = "https://files.pythonhosted.org/packages/cd/59/5dd8a3059997ec1daf6f9f7c9b1aef7f0a9e23e1334a5774eae65cae6fd0/numba-0.58.1-cp311-cp311-win_amd64.whl", hash = "sha256:811305d5dc40ae43c3ace5b192c670c358a89a4d2ae4f86d1665003798ea7a1a", size = 2606316, upload-time = "2023-10-18T13:47:03.176Z" }, + { url = "https://files.pythonhosted.org/packages/76/ff/9e6726d73effefc14557c869c980898f123749c16fae4c527285169f1d9c/numba-0.58.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ea5bfcf7d641d351c6a80e8e1826eb4a145d619870016eeaf20bbd71ef5caa22", size = 2584254, upload-time = "2023-10-18T13:47:07.011Z" }, + { url = "https://files.pythonhosted.org/packages/fa/01/b8b3d3c4623f3f267d7f52f30f2f994cc00793e67a6242d0b7323099060e/numba-0.58.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e63d6aacaae1ba4ef3695f1c2122b30fa3d8ba039c8f517784668075856d79e2", size = 2589312, upload-time = "2023-10-18T13:47:09.887Z" }, + { url = "https://files.pythonhosted.org/packages/c6/97/dad816d9313041aa967e13f3b21fdff040433c7efe9eff3112bcf3b40bc1/numba-0.58.1-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6fe7a9d8e3bd996fbe5eac0683227ccef26cba98dae6e5cee2c1894d4b9f16c1", size = 3359774, upload-time = "2023-10-18T13:47:13.8Z" }, + { url = "https://files.pythonhosted.org/packages/6e/03/c658004b4b4d52dca7a74582e9fc3e20f94c17cb4b797eb3601876759540/numba-0.58.1-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:898af055b03f09d33a587e9425500e5be84fc90cd2f80b3fb71c6a4a17a7e354", size = 3651491, upload-time = "2023-10-18T13:47:18.489Z" }, + { url = "https://files.pythonhosted.org/packages/ce/76/9266f7bd78f134f6ae09886d87675a8ac34e8dd0a30f6007794da41d8a80/numba-0.58.1-cp38-cp38-win_amd64.whl", hash = "sha256:d3e2fe81fe9a59fcd99cc572002101119059d64d31eb6324995ee8b0f144a306", size = 2626200, upload-time = "2023-10-18T13:47:23.899Z" }, + { url = "https://files.pythonhosted.org/packages/b5/de/e2ef933a99c502d2ec5dda1a43a74ab98b1b606c0ff17422d42c62a6f00f/numba-0.58.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5c765aef472a9406a97ea9782116335ad4f9ef5c9f93fc05fd44aab0db486954", size = 2587997, upload-time = "2023-10-18T13:47:27.885Z" }, + { url = "https://files.pythonhosted.org/packages/eb/61/db566ad7cd3f7e9191ec744f25cf6455e8d91732ad70be8fcdd203a48dab/numba-0.58.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9e9356e943617f5e35a74bf56ff6e7cc83e6b1865d5e13cee535d79bf2cae954", size = 2590940, upload-time = "2023-10-18T13:47:31.597Z" }, + { url = "https://files.pythonhosted.org/packages/3d/db/75bd7bb82e0f05ad066a0f4b1f8913de771dcdec37b063caa4c9282e6b1b/numba-0.58.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:240e7a1ae80eb6b14061dc91263b99dc8d6af9ea45d310751b780888097c1aaa", size = 3345620, upload-time = "2023-10-18T13:47:35.316Z" }, + { url = "https://files.pythonhosted.org/packages/8b/4b/58bdecd06266c5395157727d7191d83c82d190665bdf3d65fdd67a6dbaa5/numba-0.58.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:45698b995914003f890ad839cfc909eeb9c74921849c712a05405d1a79c50f68", size = 3636967, upload-time = "2023-10-18T13:47:38.751Z" }, + { url = "https://files.pythonhosted.org/packages/f8/14/546925a397d7638858a890a01d82cc6de147fca294eccf93ee11b524ca3c/numba-0.58.1-cp39-cp39-win_amd64.whl", hash = "sha256:bd3dda77955be03ff366eebbfdb39919ce7c2620d86c906203bed92124989032", size = 2626240, upload-time = "2023-10-18T13:47:42.379Z" }, +] + +[[package]] +name = "numba" +version = "0.60.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.9.*'", +] +dependencies = [ + { name = "llvmlite", version = "0.43.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3c/93/2849300a9184775ba274aba6f82f303343669b0592b7bb0849ea713dabb0/numba-0.60.0.tar.gz", hash = "sha256:5df6158e5584eece5fc83294b949fd30b9f1125df7708862205217e068aabf16", size = 2702171, upload-time = "2024-06-13T18:11:19.869Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/cf/baa13a7e3556d73d9e38021e6d6aa4aeb30d8b94545aa8b70d0f24a1ccc4/numba-0.60.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d761de835cd38fb400d2c26bb103a2726f548dc30368853121d66201672e651", size = 2647627, upload-time = "2024-06-13T18:10:29.857Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ba/4b57fa498564457c3cc9fc9e570a6b08e6086c74220f24baaf04e54b995f/numba-0.60.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:159e618ef213fba758837f9837fb402bbe65326e60ba0633dbe6c7f274d42c1b", size = 2650322, upload-time = "2024-06-13T18:10:32.849Z" }, + { url = "https://files.pythonhosted.org/packages/28/98/7ea97ee75870a54f938a8c70f7e0be4495ba5349c5f9db09d467c4a5d5b7/numba-0.60.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1527dc578b95c7c4ff248792ec33d097ba6bef9eda466c948b68dfc995c25781", size = 3407390, upload-time = "2024-06-13T18:10:34.741Z" }, + { url = "https://files.pythonhosted.org/packages/79/58/cb4ac5b8f7ec64200460aef1fed88258fb872ceef504ab1f989d2ff0f684/numba-0.60.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fe0b28abb8d70f8160798f4de9d486143200f34458d34c4a214114e445d7124e", size = 3699694, upload-time = "2024-06-13T18:10:37.295Z" }, + { url = "https://files.pythonhosted.org/packages/1c/b0/c61a93ca947d12233ff45de506ddbf52af3f752066a0b8be4d27426e16da/numba-0.60.0-cp310-cp310-win_amd64.whl", hash = "sha256:19407ced081d7e2e4b8d8c36aa57b7452e0283871c296e12d798852bc7d7f198", size = 2687030, upload-time = "2024-06-13T18:10:39.47Z" }, + { url = "https://files.pythonhosted.org/packages/98/ad/df18d492a8f00d29a30db307904b9b296e37507034eedb523876f3a2e13e/numba-0.60.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a17b70fc9e380ee29c42717e8cc0bfaa5556c416d94f9aa96ba13acb41bdece8", size = 2647254, upload-time = "2024-06-13T18:10:41.69Z" }, + { url = "https://files.pythonhosted.org/packages/9a/51/a4dc2c01ce7a850b8e56ff6d5381d047a5daea83d12bad08aa071d34b2ee/numba-0.60.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3fb02b344a2a80efa6f677aa5c40cd5dd452e1b35f8d1c2af0dfd9ada9978e4b", size = 2649970, upload-time = "2024-06-13T18:10:44.682Z" }, + { url = "https://files.pythonhosted.org/packages/f9/4c/8889ac94c0b33dca80bed11564b8c6d9ea14d7f094e674c58e5c5b05859b/numba-0.60.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5f4fde652ea604ea3c86508a3fb31556a6157b2c76c8b51b1d45eb40c8598703", size = 3412492, upload-time = "2024-06-13T18:10:47.1Z" }, + { url = "https://files.pythonhosted.org/packages/57/03/2b4245b05b71c0cee667e6a0b51606dfa7f4157c9093d71c6b208385a611/numba-0.60.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4142d7ac0210cc86432b818338a2bc368dc773a2f5cf1e32ff7c5b378bd63ee8", size = 3705018, upload-time = "2024-06-13T18:10:49.539Z" }, + { url = "https://files.pythonhosted.org/packages/79/89/2d924ca60dbf949f18a6fec223a2445f5f428d9a5f97a6b29c2122319015/numba-0.60.0-cp311-cp311-win_amd64.whl", hash = "sha256:cac02c041e9b5bc8cf8f2034ff6f0dbafccd1ae9590dc146b3a02a45e53af4e2", size = 2686920, upload-time = "2024-06-13T18:10:51.937Z" }, + { url = "https://files.pythonhosted.org/packages/eb/5c/b5ec752c475e78a6c3676b67c514220dbde2725896bbb0b6ec6ea54b2738/numba-0.60.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d7da4098db31182fc5ffe4bc42c6f24cd7d1cb8a14b59fd755bfee32e34b8404", size = 2647866, upload-time = "2024-06-13T18:10:54.453Z" }, + { url = "https://files.pythonhosted.org/packages/65/42/39559664b2e7c15689a638c2a38b3b74c6e69a04e2b3019b9f7742479188/numba-0.60.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:38d6ea4c1f56417076ecf8fc327c831ae793282e0ff51080c5094cb726507b1c", size = 2650208, upload-time = "2024-06-13T18:10:56.779Z" }, + { url = "https://files.pythonhosted.org/packages/67/88/c4459ccc05674ef02119abf2888ccd3e2fed12a323f52255f4982fc95876/numba-0.60.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:62908d29fb6a3229c242e981ca27e32a6e606cc253fc9e8faeb0e48760de241e", size = 3466946, upload-time = "2024-06-13T18:10:58.961Z" }, + { url = "https://files.pythonhosted.org/packages/8b/41/ac11cf33524def12aa5bd698226ae196a1185831c05ed29dc0c56eaa308b/numba-0.60.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0ebaa91538e996f708f1ab30ef4d3ddc344b64b5227b67a57aa74f401bb68b9d", size = 3761463, upload-time = "2024-06-13T18:11:01.657Z" }, + { url = "https://files.pythonhosted.org/packages/ca/bd/0fe29fcd1b6a8de479a4ed25c6e56470e467e3611c079d55869ceef2b6d1/numba-0.60.0-cp312-cp312-win_amd64.whl", hash = "sha256:f75262e8fe7fa96db1dca93d53a194a38c46da28b112b8a4aca168f0df860347", size = 2707588, upload-time = "2024-06-13T18:11:04.261Z" }, + { url = "https://files.pythonhosted.org/packages/68/1a/87c53f836cdf557083248c3f47212271f220280ff766538795e77c8c6bbf/numba-0.60.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:01ef4cd7d83abe087d644eaa3d95831b777aa21d441a23703d649e06b8e06b74", size = 2647186, upload-time = "2024-06-13T18:11:06.753Z" }, + { url = "https://files.pythonhosted.org/packages/28/14/a5baa1f2edea7b49afa4dc1bb1b126645198cf1075186853b5b497be826e/numba-0.60.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:819a3dfd4630d95fd574036f99e47212a1af41cbcb019bf8afac63ff56834449", size = 2650038, upload-time = "2024-06-13T18:11:10.869Z" }, + { url = "https://files.pythonhosted.org/packages/3b/bd/f1985719ff34e37e07bb18f9d3acd17e5a21da255f550c8eae031e2ddf5f/numba-0.60.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0b983bd6ad82fe868493012487f34eae8bf7dd94654951404114f23c3466d34b", size = 3403010, upload-time = "2024-06-13T18:11:13.057Z" }, + { url = "https://files.pythonhosted.org/packages/54/9b/cd73d3f6617ddc8398a63ef97d8dc9139a9879b9ca8a7ca4b8789056ea46/numba-0.60.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c151748cd269ddeab66334bd754817ffc0cabd9433acb0f551697e5151917d25", size = 3695086, upload-time = "2024-06-13T18:11:15.497Z" }, + { url = "https://files.pythonhosted.org/packages/01/01/8b7b670c77c5ea0e47e283d82332969bf672ab6410d0b2610cac5b7a3ded/numba-0.60.0-cp39-cp39-win_amd64.whl", hash = "sha256:3031547a015710140e8c87226b4cfe927cac199835e5bf7d4fe5cb64e814e3ab", size = 2686978, upload-time = "2024-06-13T18:11:17.765Z" }, +] + +[[package]] +name = "numba" +version = "0.65.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "llvmlite", version = "0.47.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f6/c5/db2ac3685833d626c0dcae6bd2330cd68433e1fd248d15f70998160d3ad7/numba-0.65.1.tar.gz", hash = "sha256:19357146c32fe9ed25059ab915e8465fb13951cf6b0aace3826b76886373ab23", size = 2765600, upload-time = "2026-04-24T02:02:56.551Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/1b/3c5a7daf683a95465bf23504bcd1a2d5db8cd5e5e276ca87505d020dffe9/numba-0.65.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:9d993ed0a257aa4116e6f553f114004bcfdee540c7276ab8ea48f650d514c452", size = 2680870, upload-time = "2026-04-24T02:02:10.623Z" }, + { url = "https://files.pythonhosted.org/packages/0f/a4/1831836814018a898e7d252aebe09c0f3ce1f26d145b68264b4ae0be6822/numba-0.65.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5f098109f361681e57295f7e84d8ab2426902539a141811de0703ace52826981", size = 3739780, upload-time = "2026-04-24T02:02:13.097Z" }, + { url = "https://files.pythonhosted.org/packages/9c/1b/a813ddc81def09e257d2b1f67521982ce4b06204a87268796ffc8187271c/numba-0.65.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:973fd8173f2312815e6b7aaae887c4ce8a817eeff46a4f8840b828305b75bc95", size = 3446722, upload-time = "2026-04-24T02:02:15.083Z" }, + { url = "https://files.pythonhosted.org/packages/09/52/ee1d8b3becda384fe0552221641e05aa668a35e8a77470db4db7f6475000/numba-0.65.1-cp310-cp310-win_amd64.whl", hash = "sha256:c63aa0c4193694026452da55d0ef9d85156c1a7a333454c103bb30dec81b7bf8", size = 2747539, upload-time = "2026-04-24T02:02:16.79Z" }, + { url = "https://files.pythonhosted.org/packages/96/b3/650500c2eab4534d98e9166f4298e0f3c69c742afdf24e6eabccd1f16ad8/numba-0.65.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:7020d74b19cdb8cff16506542fdd510756e28c5e7f3bd0b7f574f0f42272fcd9", size = 2680563, upload-time = "2026-04-24T02:02:18.414Z" }, + { url = "https://files.pythonhosted.org/packages/44/0b/0615dbedb98f5b32a35a53290fbdc6e22306968109278d7e58df82d7a9f6/numba-0.65.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f80ed83774b5173abd6581cd8d2165d1d38e13d2e5c8155c0c0b421784745420", size = 3745018, upload-time = "2026-04-24T02:02:20.252Z" }, + { url = "https://files.pythonhosted.org/packages/49/aa/4361698f35bf63bff67dfe6c90493731177f48ede954f77b0588731537bc/numba-0.65.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7ed425a43b0a5f9772f2f4e2dd0bbd12eabecae1af0b24efcfd4e053f012aac6", size = 3450962, upload-time = "2026-04-24T02:02:22.449Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9a/af61ec03b3116c161fd7a06b9e8a265729a8718458333e8ffbb06d9a3978/numba-0.65.1-cp311-cp311-win_amd64.whl", hash = "sha256:df40a5028a975b9ea66f6a2a3f7abbdbd541a863070e34ed367aff21141248e4", size = 2747417, upload-time = "2026-04-24T02:02:24.43Z" }, + { url = "https://files.pythonhosted.org/packages/57/bc/76f8f8c5cf9adee47fdb7bbb03be8900f76f902d451d7477cf12b845e1de/numba-0.65.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:ac3f1e77c352dd0ea9712732c2d8f9ca507717435eec5b5013bf138ac33c4a08", size = 2681371, upload-time = "2026-04-24T02:02:26.105Z" }, + { url = "https://files.pythonhosted.org/packages/69/47/a415af0283e4db0398104c6d1c11c9861a98dc67a7aa442a7769ed5d6196/numba-0.65.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:52bc6f3ceb8fcaff9b2ae26b4c6b1e9fee39db8d355534c0fe4f39a901246b84", size = 3802467, upload-time = "2026-04-24T02:02:27.712Z" }, + { url = "https://files.pythonhosted.org/packages/46/36/246f73ec99cfeab2f2cb2ce7d4218766cc36a2da418901223f4f4da9c813/numba-0.65.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90ca10b3463bae0bd70589726fe3c77d01d6b5fc86bee54bcdf9fb6b47c28977", size = 3502628, upload-time = "2026-04-24T02:02:29.763Z" }, + { url = "https://files.pythonhosted.org/packages/db/9e/3c679b2ee078425b9e99a91e44f8d132a6830d8ccce5227bc5e9181aeed8/numba-0.65.1-cp312-cp312-win_amd64.whl", hash = "sha256:5971c632be2a2351500431f46213821dba8d02b18a9f7d02fd36bd2743e41a6a", size = 2750611, upload-time = "2026-04-24T02:02:31.477Z" }, + { url = "https://files.pythonhosted.org/packages/79/37/14a4579049c1eb673afd0de0cb4842982acd55b9ce2643e763db858bcea0/numba-0.65.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:1735c15c1134a5108b4d6a5c77fc0947924ea066a738dc09a52008c13df9cad3", size = 2681344, upload-time = "2026-04-24T02:02:33.65Z" }, + { url = "https://files.pythonhosted.org/packages/a0/22/b8d873f6466b20aa563fc9b33acd48dec89a07803ddaa2f1c8ca1cd33126/numba-0.65.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c09f49117ef255e1f1c6dad0c7a1ed39868243862a73be5706793241a3755f1b", size = 3810619, upload-time = "2026-04-24T02:02:36.041Z" }, + { url = "https://files.pythonhosted.org/packages/62/08/e16a8b5d9a018962ebb5c66be662317cde32b9f5dab08441f90bed5522fb/numba-0.65.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:594a8680b3fadac99e97e489b1fd89007177e5336713745c3b769528c635a464", size = 3509783, upload-time = "2026-04-24T02:02:38.245Z" }, + { url = "https://files.pythonhosted.org/packages/fd/a5/03c970d57f4c1741354837353ce39fb5206952ae1dba8922d29c86f64805/numba-0.65.1-cp313-cp313-win_amd64.whl", hash = "sha256:85be74c0d036842699a30058f82fb88fc5ffdc59f7615cab5792ea92914c9b62", size = 2750534, upload-time = "2026-04-24T02:02:39.903Z" }, + { url = "https://files.pythonhosted.org/packages/4f/2e/8aed9b726d9ba5f11ad287645fd479e88278db3060a25cb1225d730eb2b7/numba-0.65.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:33f5eb68eb1c843511615d14663ce60258525d6a4c65ab040e2c2b0c4cf17450", size = 2681554, upload-time = "2026-04-24T02:02:41.812Z" }, + { url = "https://files.pythonhosted.org/packages/87/96/f3eb235fafa82a34e2ab5dd7dc9ffff998ebf5f0bbc23fa56a96aeb44da6/numba-0.65.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:71e73029bf53a62cc6afcf96be4bd942290d8b4c55f0a454fb536158115790f7", size = 3779602, upload-time = "2026-04-24T02:02:43.726Z" }, + { url = "https://files.pythonhosted.org/packages/09/90/b0f09b48752d23640b8284f22aa597737e8adaddc7fbfacc4708b7f73a4c/numba-0.65.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a07635e0be926b9bdbffb09137c230fb13f6ec0e564914ba937cee12ce3eb35", size = 3479532, upload-time = "2026-04-24T02:02:45.427Z" }, + { url = "https://files.pythonhosted.org/packages/56/46/3f7fc04fb853559e74b210e0b62c19974ec844cefec611f9e535f4da3761/numba-0.65.1-cp314-cp314-win_amd64.whl", hash = "sha256:2a20fcdabdefbdacf88d85caf70c3b18c4bcb7ebb8f82e6a19486383dd26ab63", size = 2752637, upload-time = "2026-04-24T02:02:47.664Z" }, + { url = "https://files.pythonhosted.org/packages/81/7b/c1a341a9067367778f4152a5f01061cf281fb09582c92c510ec4918cabf6/numba-0.65.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:548dd4b3a4508d5062768d1514b2cd7b015f9a25ec7af651c50dee243965e652", size = 2684600, upload-time = "2026-04-24T02:02:49.653Z" }, + { url = "https://files.pythonhosted.org/packages/03/36/98ddbcf3e4f04a6dd07e1c67249955920579ba4af6bb6868e3088f4ed282/numba-0.65.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:78abc28feff2c2ff8307fff3975b6438352759c9acb797ecd6b1fb6e7e39e31d", size = 3817198, upload-time = "2026-04-24T02:02:51.266Z" }, + { url = "https://files.pythonhosted.org/packages/a3/83/0dad21057ece5a835599f5d24099b091703995e23dbbf894f259e91c010b/numba-0.65.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee7676cb389555805f9b9a1840cbcd1ea6c8bd5376ab6918e3a29c5ea1dbda20", size = 3533862, upload-time = "2026-04-24T02:02:52.987Z" }, + { url = "https://files.pythonhosted.org/packages/32/36/8be7118ffd4c8440881046eac3d0982cc5ab42909508cf5d67024d62a2e4/numba-0.65.1-cp314-cp314t-win_amd64.whl", hash = "sha256:20609346e3bd75204950dcbbfe383a8d7dbf4902f442aedbf00f97fef4aa8f38", size = 2758237, upload-time = "2026-04-24T02:02:54.612Z" }, +] + +[[package]] +name = "numpy" +version = "1.24.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +sdist = { url = "https://files.pythonhosted.org/packages/a4/9b/027bec52c633f6556dba6b722d9a0befb40498b9ceddd29cbe67a45a127c/numpy-1.24.4.tar.gz", hash = "sha256:80f5e3a4e498641401868df4208b74581206afbee7cf7b8329daae82676d9463", size = 10911229, upload-time = "2023-06-26T13:39:33.218Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/80/6cdfb3e275d95155a34659163b83c09e3a3ff9f1456880bec6cc63d71083/numpy-1.24.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0bfb52d2169d58c1cdb8cc1f16989101639b34c7d3ce60ed70b19c63eba0b64", size = 19789140, upload-time = "2023-06-26T13:22:33.184Z" }, + { url = "https://files.pythonhosted.org/packages/64/5f/3f01d753e2175cfade1013eea08db99ba1ee4bdb147ebcf3623b75d12aa7/numpy-1.24.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ed094d4f0c177b1b8e7aa9cba7d6ceed51c0e569a5318ac0ca9a090680a6a1b1", size = 13854297, upload-time = "2023-06-26T13:22:59.541Z" }, + { url = "https://files.pythonhosted.org/packages/5a/b3/2f9c21d799fa07053ffa151faccdceeb69beec5a010576b8991f614021f7/numpy-1.24.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79fc682a374c4a8ed08b331bef9c5f582585d1048fa6d80bc6c35bc384eee9b4", size = 13995611, upload-time = "2023-06-26T13:23:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/10/be/ae5bf4737cb79ba437879915791f6f26d92583c738d7d960ad94e5c36adf/numpy-1.24.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ffe43c74893dbf38c2b0a1f5428760a1a9c98285553c89e12d70a96a7f3a4d6", size = 17282357, upload-time = "2023-06-26T13:23:51.446Z" }, + { url = "https://files.pythonhosted.org/packages/c0/64/908c1087be6285f40e4b3e79454552a701664a079321cff519d8c7051d06/numpy-1.24.4-cp310-cp310-win32.whl", hash = "sha256:4c21decb6ea94057331e111a5bed9a79d335658c27ce2adb580fb4d54f2ad9bc", size = 12429222, upload-time = "2023-06-26T13:24:13.849Z" }, + { url = "https://files.pythonhosted.org/packages/22/55/3d5a7c1142e0d9329ad27cece17933b0e2ab4e54ddc5c1861fbfeb3f7693/numpy-1.24.4-cp310-cp310-win_amd64.whl", hash = "sha256:b4bea75e47d9586d31e892a7401f76e909712a0fd510f58f5337bea9572c571e", size = 14841514, upload-time = "2023-06-26T13:24:38.129Z" }, + { url = "https://files.pythonhosted.org/packages/a9/cc/5ed2280a27e5dab12994c884f1f4d8c3bd4d885d02ae9e52a9d213a6a5e2/numpy-1.24.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f136bab9c2cfd8da131132c2cf6cc27331dd6fae65f95f69dcd4ae3c3639c810", size = 19775508, upload-time = "2023-06-26T13:25:08.882Z" }, + { url = "https://files.pythonhosted.org/packages/c0/bc/77635c657a3668cf652806210b8662e1aff84b818a55ba88257abf6637a8/numpy-1.24.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2926dac25b313635e4d6cf4dc4e51c8c0ebfed60b801c799ffc4c32bf3d1254", size = 13840033, upload-time = "2023-06-26T13:25:33.417Z" }, + { url = "https://files.pythonhosted.org/packages/a7/4c/96cdaa34f54c05e97c1c50f39f98d608f96f0677a6589e64e53104e22904/numpy-1.24.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:222e40d0e2548690405b0b3c7b21d1169117391c2e82c378467ef9ab4c8f0da7", size = 13991951, upload-time = "2023-06-26T13:25:55.725Z" }, + { url = "https://files.pythonhosted.org/packages/22/97/dfb1a31bb46686f09e68ea6ac5c63fdee0d22d7b23b8f3f7ea07712869ef/numpy-1.24.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7215847ce88a85ce39baf9e89070cb860c98fdddacbaa6c0da3ffb31b3350bd5", size = 17278923, upload-time = "2023-06-26T13:26:25.658Z" }, + { url = "https://files.pythonhosted.org/packages/35/e2/76a11e54139654a324d107da1d98f99e7aa2a7ef97cfd7c631fba7dbde71/numpy-1.24.4-cp311-cp311-win32.whl", hash = "sha256:4979217d7de511a8d57f4b4b5b2b965f707768440c17cb70fbf254c4b225238d", size = 12422446, upload-time = "2023-06-26T13:26:49.302Z" }, + { url = "https://files.pythonhosted.org/packages/d8/ec/ebef2f7d7c28503f958f0f8b992e7ce606fb74f9e891199329d5f5f87404/numpy-1.24.4-cp311-cp311-win_amd64.whl", hash = "sha256:b7b1fc9864d7d39e28f41d089bfd6353cb5f27ecd9905348c24187a768c79694", size = 14834466, upload-time = "2023-06-26T13:27:16.029Z" }, + { url = "https://files.pythonhosted.org/packages/11/10/943cfb579f1a02909ff96464c69893b1d25be3731b5d3652c2e0cf1281ea/numpy-1.24.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1452241c290f3e2a312c137a9999cdbf63f78864d63c79039bda65ee86943f61", size = 19780722, upload-time = "2023-06-26T13:27:49.573Z" }, + { url = "https://files.pythonhosted.org/packages/a7/ae/f53b7b265fdc701e663fbb322a8e9d4b14d9cb7b2385f45ddfabfc4327e4/numpy-1.24.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:04640dab83f7c6c85abf9cd729c5b65f1ebd0ccf9de90b270cd61935eef0197f", size = 13843102, upload-time = "2023-06-26T13:28:12.288Z" }, + { url = "https://files.pythonhosted.org/packages/25/6f/2586a50ad72e8dbb1d8381f837008a0321a3516dfd7cb57fc8cf7e4bb06b/numpy-1.24.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5425b114831d1e77e4b5d812b69d11d962e104095a5b9c3b641a218abcc050e", size = 14039616, upload-time = "2023-06-26T13:28:35.659Z" }, + { url = "https://files.pythonhosted.org/packages/98/5d/5738903efe0ecb73e51eb44feafba32bdba2081263d40c5043568ff60faf/numpy-1.24.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd80e219fd4c71fc3699fc1dadac5dcf4fd882bfc6f7ec53d30fa197b8ee22dc", size = 17316263, upload-time = "2023-06-26T13:29:09.272Z" }, + { url = "https://files.pythonhosted.org/packages/d1/57/8d328f0b91c733aa9aa7ee540dbc49b58796c862b4fbcb1146c701e888da/numpy-1.24.4-cp38-cp38-win32.whl", hash = "sha256:4602244f345453db537be5314d3983dbf5834a9701b7723ec28923e2889e0bb2", size = 12455660, upload-time = "2023-06-26T13:29:33.434Z" }, + { url = "https://files.pythonhosted.org/packages/69/65/0d47953afa0ad569d12de5f65d964321c208492064c38fe3b0b9744f8d44/numpy-1.24.4-cp38-cp38-win_amd64.whl", hash = "sha256:692f2e0f55794943c5bfff12b3f56f99af76f902fc47487bdfe97856de51a706", size = 14868112, upload-time = "2023-06-26T13:29:58.385Z" }, + { url = "https://files.pythonhosted.org/packages/9a/cd/d5b0402b801c8a8b56b04c1e85c6165efab298d2f0ab741c2406516ede3a/numpy-1.24.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2541312fbf09977f3b3ad449c4e5f4bb55d0dbf79226d7724211acc905049400", size = 19816549, upload-time = "2023-06-26T13:30:36.976Z" }, + { url = "https://files.pythonhosted.org/packages/14/27/638aaa446f39113a3ed38b37a66243e21b38110d021bfcb940c383e120f2/numpy-1.24.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9667575fb6d13c95f1b36aca12c5ee3356bf001b714fc354eb5465ce1609e62f", size = 13879950, upload-time = "2023-06-26T13:31:01.787Z" }, + { url = "https://files.pythonhosted.org/packages/8f/27/91894916e50627476cff1a4e4363ab6179d01077d71b9afed41d9e1f18bf/numpy-1.24.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a86ed21e4f87050382c7bc96571755193c4c1392490744ac73d660e8f564a9", size = 14030228, upload-time = "2023-06-26T13:31:26.696Z" }, + { url = "https://files.pythonhosted.org/packages/7a/7c/d7b2a0417af6428440c0ad7cb9799073e507b1a465f827d058b826236964/numpy-1.24.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d11efb4dbecbdf22508d55e48d9c8384db795e1b7b51ea735289ff96613ff74d", size = 17311170, upload-time = "2023-06-26T13:31:56.615Z" }, + { url = "https://files.pythonhosted.org/packages/18/9d/e02ace5d7dfccee796c37b995c63322674daf88ae2f4a4724c5dd0afcc91/numpy-1.24.4-cp39-cp39-win32.whl", hash = "sha256:6620c0acd41dbcb368610bb2f4d83145674040025e5536954782467100aa8835", size = 12454918, upload-time = "2023-06-26T13:32:16.8Z" }, + { url = "https://files.pythonhosted.org/packages/63/38/6cc19d6b8bfa1d1a459daf2b3fe325453153ca7019976274b6f33d8b5663/numpy-1.24.4-cp39-cp39-win_amd64.whl", hash = "sha256:befe2bf740fd8373cf56149a5c23a0f601e82869598d41f8e188a0e9869926f8", size = 14867441, upload-time = "2023-06-26T13:32:40.521Z" }, + { url = "https://files.pythonhosted.org/packages/a4/fd/8dff40e25e937c94257455c237b9b6bf5a30d42dd1cc11555533be099492/numpy-1.24.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:31f13e25b4e304632a4619d0e0777662c2ffea99fcae2029556b17d8ff958aef", size = 19156590, upload-time = "2023-06-26T13:33:10.36Z" }, + { url = "https://files.pythonhosted.org/packages/42/e7/4bf953c6e05df90c6d351af69966384fed8e988d0e8c54dad7103b59f3ba/numpy-1.24.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95f7ac6540e95bc440ad77f56e520da5bf877f87dca58bd095288dce8940532a", size = 16705744, upload-time = "2023-06-26T13:33:36.703Z" }, + { url = "https://files.pythonhosted.org/packages/fc/dd/9106005eb477d022b60b3817ed5937a43dad8fd1f20b0610ea8a32fcb407/numpy-1.24.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e98f220aa76ca2a977fe435f5b04d7b3470c0a2e6312907b37ba6068f26787f2", size = 14734290, upload-time = "2023-06-26T13:34:05.409Z" }, +] + +[[package]] +name = "numpy" +version = "2.0.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/75/10dd1f8116a8b796cb2c737b674e02d02e80454bda953fa7e65d8c12b016/numpy-2.0.2.tar.gz", hash = "sha256:883c987dee1880e2a864ab0dc9892292582510604156762362d9326444636e78", size = 18902015, upload-time = "2024-08-26T20:19:40.945Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/21/91/3495b3237510f79f5d81f2508f9f13fea78ebfdf07538fc7444badda173d/numpy-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:51129a29dbe56f9ca83438b706e2e69a39892b5eda6cedcb6b0c9fdc9b0d3ece", size = 21165245, upload-time = "2024-08-26T20:04:14.625Z" }, + { url = "https://files.pythonhosted.org/packages/05/33/26178c7d437a87082d11019292dce6d3fe6f0e9026b7b2309cbf3e489b1d/numpy-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f15975dfec0cf2239224d80e32c3170b1d168335eaedee69da84fbe9f1f9cd04", size = 13738540, upload-time = "2024-08-26T20:04:36.784Z" }, + { url = "https://files.pythonhosted.org/packages/ec/31/cc46e13bf07644efc7a4bf68df2df5fb2a1a88d0cd0da9ddc84dc0033e51/numpy-2.0.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8c5713284ce4e282544c68d1c3b2c7161d38c256d2eefc93c1d683cf47683e66", size = 5300623, upload-time = "2024-08-26T20:04:46.491Z" }, + { url = "https://files.pythonhosted.org/packages/6e/16/7bfcebf27bb4f9d7ec67332ffebee4d1bf085c84246552d52dbb548600e7/numpy-2.0.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:becfae3ddd30736fe1889a37f1f580e245ba79a5855bff5f2a29cb3ccc22dd7b", size = 6901774, upload-time = "2024-08-26T20:04:58.173Z" }, + { url = "https://files.pythonhosted.org/packages/f9/a3/561c531c0e8bf082c5bef509d00d56f82e0ea7e1e3e3a7fc8fa78742a6e5/numpy-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2da5960c3cf0df7eafefd806d4e612c5e19358de82cb3c343631188991566ccd", size = 13907081, upload-time = "2024-08-26T20:05:19.098Z" }, + { url = "https://files.pythonhosted.org/packages/fa/66/f7177ab331876200ac7563a580140643d1179c8b4b6a6b0fc9838de2a9b8/numpy-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:496f71341824ed9f3d2fd36cf3ac57ae2e0165c143b55c3a035ee219413f3318", size = 19523451, upload-time = "2024-08-26T20:05:47.479Z" }, + { url = "https://files.pythonhosted.org/packages/25/7f/0b209498009ad6453e4efc2c65bcdf0ae08a182b2b7877d7ab38a92dc542/numpy-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a61ec659f68ae254e4d237816e33171497e978140353c0c2038d46e63282d0c8", size = 19927572, upload-time = "2024-08-26T20:06:17.137Z" }, + { url = "https://files.pythonhosted.org/packages/3e/df/2619393b1e1b565cd2d4c4403bdd979621e2c4dea1f8532754b2598ed63b/numpy-2.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d731a1c6116ba289c1e9ee714b08a8ff882944d4ad631fd411106a30f083c326", size = 14400722, upload-time = "2024-08-26T20:06:39.16Z" }, + { url = "https://files.pythonhosted.org/packages/22/ad/77e921b9f256d5da36424ffb711ae79ca3f451ff8489eeca544d0701d74a/numpy-2.0.2-cp310-cp310-win32.whl", hash = "sha256:984d96121c9f9616cd33fbd0618b7f08e0cfc9600a7ee1d6fd9b239186d19d97", size = 6472170, upload-time = "2024-08-26T20:06:50.361Z" }, + { url = "https://files.pythonhosted.org/packages/10/05/3442317535028bc29cf0c0dd4c191a4481e8376e9f0db6bcf29703cadae6/numpy-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:c7b0be4ef08607dd04da4092faee0b86607f111d5ae68036f16cc787e250a131", size = 15905558, upload-time = "2024-08-26T20:07:13.881Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cf/034500fb83041aa0286e0fb16e7c76e5c8b67c0711bb6e9e9737a717d5fe/numpy-2.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:49ca4decb342d66018b01932139c0961a8f9ddc7589611158cb3c27cbcf76448", size = 21169137, upload-time = "2024-08-26T20:07:45.345Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d9/32de45561811a4b87fbdee23b5797394e3d1504b4a7cf40c10199848893e/numpy-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:11a76c372d1d37437857280aa142086476136a8c0f373b2e648ab2c8f18fb195", size = 13703552, upload-time = "2024-08-26T20:08:06.666Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ca/2f384720020c7b244d22508cb7ab23d95f179fcfff33c31a6eeba8d6c512/numpy-2.0.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:807ec44583fd708a21d4a11d94aedf2f4f3c3719035c76a2bbe1fe8e217bdc57", size = 5298957, upload-time = "2024-08-26T20:08:15.83Z" }, + { url = "https://files.pythonhosted.org/packages/0e/78/a3e4f9fb6aa4e6fdca0c5428e8ba039408514388cf62d89651aade838269/numpy-2.0.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8cafab480740e22f8d833acefed5cc87ce276f4ece12fdaa2e8903db2f82897a", size = 6905573, upload-time = "2024-08-26T20:08:27.185Z" }, + { url = "https://files.pythonhosted.org/packages/a0/72/cfc3a1beb2caf4efc9d0b38a15fe34025230da27e1c08cc2eb9bfb1c7231/numpy-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a15f476a45e6e5a3a79d8a14e62161d27ad897381fecfa4a09ed5322f2085669", size = 13914330, upload-time = "2024-08-26T20:08:48.058Z" }, + { url = "https://files.pythonhosted.org/packages/ba/a8/c17acf65a931ce551fee11b72e8de63bf7e8a6f0e21add4c937c83563538/numpy-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13e689d772146140a252c3a28501da66dfecd77490b498b168b501835041f951", size = 19534895, upload-time = "2024-08-26T20:09:16.536Z" }, + { url = "https://files.pythonhosted.org/packages/ba/86/8767f3d54f6ae0165749f84648da9dcc8cd78ab65d415494962c86fac80f/numpy-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9ea91dfb7c3d1c56a0e55657c0afb38cf1eeae4544c208dc465c3c9f3a7c09f9", size = 19937253, upload-time = "2024-08-26T20:09:46.263Z" }, + { url = "https://files.pythonhosted.org/packages/df/87/f76450e6e1c14e5bb1eae6836478b1028e096fd02e85c1c37674606ab752/numpy-2.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c1c9307701fec8f3f7a1e6711f9089c06e6284b3afbbcd259f7791282d660a15", size = 14414074, upload-time = "2024-08-26T20:10:08.483Z" }, + { url = "https://files.pythonhosted.org/packages/5c/ca/0f0f328e1e59f73754f06e1adfb909de43726d4f24c6a3f8805f34f2b0fa/numpy-2.0.2-cp311-cp311-win32.whl", hash = "sha256:a392a68bd329eafac5817e5aefeb39038c48b671afd242710b451e76090e81f4", size = 6470640, upload-time = "2024-08-26T20:10:19.732Z" }, + { url = "https://files.pythonhosted.org/packages/eb/57/3a3f14d3a759dcf9bf6e9eda905794726b758819df4663f217d658a58695/numpy-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:286cd40ce2b7d652a6f22efdfc6d1edf879440e53e76a75955bc0c826c7e64dc", size = 15910230, upload-time = "2024-08-26T20:10:43.413Z" }, + { url = "https://files.pythonhosted.org/packages/45/40/2e117be60ec50d98fa08c2f8c48e09b3edea93cfcabd5a9ff6925d54b1c2/numpy-2.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:df55d490dea7934f330006d0f81e8551ba6010a5bf035a249ef61a94f21c500b", size = 20895803, upload-time = "2024-08-26T20:11:13.916Z" }, + { url = "https://files.pythonhosted.org/packages/46/92/1b8b8dee833f53cef3e0a3f69b2374467789e0bb7399689582314df02651/numpy-2.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8df823f570d9adf0978347d1f926b2a867d5608f434a7cff7f7908c6570dcf5e", size = 13471835, upload-time = "2024-08-26T20:11:34.779Z" }, + { url = "https://files.pythonhosted.org/packages/7f/19/e2793bde475f1edaea6945be141aef6c8b4c669b90c90a300a8954d08f0a/numpy-2.0.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9a92ae5c14811e390f3767053ff54eaee3bf84576d99a2456391401323f4ec2c", size = 5038499, upload-time = "2024-08-26T20:11:43.902Z" }, + { url = "https://files.pythonhosted.org/packages/e3/ff/ddf6dac2ff0dd50a7327bcdba45cb0264d0e96bb44d33324853f781a8f3c/numpy-2.0.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:a842d573724391493a97a62ebbb8e731f8a5dcc5d285dfc99141ca15a3302d0c", size = 6633497, upload-time = "2024-08-26T20:11:55.09Z" }, + { url = "https://files.pythonhosted.org/packages/72/21/67f36eac8e2d2cd652a2e69595a54128297cdcb1ff3931cfc87838874bd4/numpy-2.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05e238064fc0610c840d1cf6a13bf63d7e391717d247f1bf0318172e759e692", size = 13621158, upload-time = "2024-08-26T20:12:14.95Z" }, + { url = "https://files.pythonhosted.org/packages/39/68/e9f1126d757653496dbc096cb429014347a36b228f5a991dae2c6b6cfd40/numpy-2.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0123ffdaa88fa4ab64835dcbde75dcdf89c453c922f18dced6e27c90d1d0ec5a", size = 19236173, upload-time = "2024-08-26T20:12:44.049Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e9/1f5333281e4ebf483ba1c888b1d61ba7e78d7e910fdd8e6499667041cc35/numpy-2.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:96a55f64139912d61de9137f11bf39a55ec8faec288c75a54f93dfd39f7eb40c", size = 19634174, upload-time = "2024-08-26T20:13:13.634Z" }, + { url = "https://files.pythonhosted.org/packages/71/af/a469674070c8d8408384e3012e064299f7a2de540738a8e414dcfd639996/numpy-2.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec9852fb39354b5a45a80bdab5ac02dd02b15f44b3804e9f00c556bf24b4bded", size = 14099701, upload-time = "2024-08-26T20:13:34.851Z" }, + { url = "https://files.pythonhosted.org/packages/d0/3d/08ea9f239d0e0e939b6ca52ad403c84a2bce1bde301a8eb4888c1c1543f1/numpy-2.0.2-cp312-cp312-win32.whl", hash = "sha256:671bec6496f83202ed2d3c8fdc486a8fc86942f2e69ff0e986140339a63bcbe5", size = 6174313, upload-time = "2024-08-26T20:13:45.653Z" }, + { url = "https://files.pythonhosted.org/packages/b2/b5/4ac39baebf1fdb2e72585c8352c56d063b6126be9fc95bd2bb5ef5770c20/numpy-2.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:cfd41e13fdc257aa5778496b8caa5e856dc4896d4ccf01841daee1d96465467a", size = 15606179, upload-time = "2024-08-26T20:14:08.786Z" }, + { url = "https://files.pythonhosted.org/packages/43/c1/41c8f6df3162b0c6ffd4437d729115704bd43363de0090c7f913cfbc2d89/numpy-2.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9059e10581ce4093f735ed23f3b9d283b9d517ff46009ddd485f1747eb22653c", size = 21169942, upload-time = "2024-08-26T20:14:40.108Z" }, + { url = "https://files.pythonhosted.org/packages/39/bc/fd298f308dcd232b56a4031fd6ddf11c43f9917fbc937e53762f7b5a3bb1/numpy-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:423e89b23490805d2a5a96fe40ec507407b8ee786d66f7328be214f9679df6dd", size = 13711512, upload-time = "2024-08-26T20:15:00.985Z" }, + { url = "https://files.pythonhosted.org/packages/96/ff/06d1aa3eeb1c614eda245c1ba4fb88c483bee6520d361641331872ac4b82/numpy-2.0.2-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:2b2955fa6f11907cf7a70dab0d0755159bca87755e831e47932367fc8f2f2d0b", size = 5306976, upload-time = "2024-08-26T20:15:10.876Z" }, + { url = "https://files.pythonhosted.org/packages/2d/98/121996dcfb10a6087a05e54453e28e58694a7db62c5a5a29cee14c6e047b/numpy-2.0.2-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:97032a27bd9d8988b9a97a8c4d2c9f2c15a81f61e2f21404d7e8ef00cb5be729", size = 6906494, upload-time = "2024-08-26T20:15:22.055Z" }, + { url = "https://files.pythonhosted.org/packages/15/31/9dffc70da6b9bbf7968f6551967fc21156207366272c2a40b4ed6008dc9b/numpy-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e795a8be3ddbac43274f18588329c72939870a16cae810c2b73461c40718ab1", size = 13912596, upload-time = "2024-08-26T20:15:42.452Z" }, + { url = "https://files.pythonhosted.org/packages/b9/14/78635daab4b07c0930c919d451b8bf8c164774e6a3413aed04a6d95758ce/numpy-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b258c385842546006213344c50655ff1555a9338e2e5e02a0756dc3e803dd", size = 19526099, upload-time = "2024-08-26T20:16:11.048Z" }, + { url = "https://files.pythonhosted.org/packages/26/4c/0eeca4614003077f68bfe7aac8b7496f04221865b3a5e7cb230c9d055afd/numpy-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fec9451a7789926bcf7c2b8d187292c9f93ea30284802a0ab3f5be8ab36865d", size = 19932823, upload-time = "2024-08-26T20:16:40.171Z" }, + { url = "https://files.pythonhosted.org/packages/f1/46/ea25b98b13dccaebddf1a803f8c748680d972e00507cd9bc6dcdb5aa2ac1/numpy-2.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9189427407d88ff25ecf8f12469d4d39d35bee1db5d39fc5c168c6f088a6956d", size = 14404424, upload-time = "2024-08-26T20:17:02.604Z" }, + { url = "https://files.pythonhosted.org/packages/c8/a6/177dd88d95ecf07e722d21008b1b40e681a929eb9e329684d449c36586b2/numpy-2.0.2-cp39-cp39-win32.whl", hash = "sha256:905d16e0c60200656500c95b6b8dca5d109e23cb24abc701d41c02d74c6b3afa", size = 6476809, upload-time = "2024-08-26T20:17:13.553Z" }, + { url = "https://files.pythonhosted.org/packages/ea/2b/7fc9f4e7ae5b507c1a3a21f0f15ed03e794c1242ea8a242ac158beb56034/numpy-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:a3f4ab0caa7f053f6797fcd4e1e25caee367db3112ef2b6ef82d749530768c73", size = 15911314, upload-time = "2024-08-26T20:17:36.72Z" }, + { url = "https://files.pythonhosted.org/packages/8f/3b/df5a870ac6a3be3a86856ce195ef42eec7ae50d2a202be1f5a4b3b340e14/numpy-2.0.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7f0a0c6f12e07fa94133c8a67404322845220c06a9e80e85999afe727f7438b8", size = 21025288, upload-time = "2024-08-26T20:18:07.732Z" }, + { url = "https://files.pythonhosted.org/packages/2c/97/51af92f18d6f6f2d9ad8b482a99fb74e142d71372da5d834b3a2747a446e/numpy-2.0.2-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:312950fdd060354350ed123c0e25a71327d3711584beaef30cdaa93320c392d4", size = 6762793, upload-time = "2024-08-26T20:18:19.125Z" }, + { url = "https://files.pythonhosted.org/packages/12/46/de1fbd0c1b5ccaa7f9a005b66761533e2f6a3e560096682683a223631fe9/numpy-2.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26df23238872200f63518dd2aa984cfca675d82469535dc7162dc2ee52d9dd5c", size = 19334885, upload-time = "2024-08-26T20:18:47.237Z" }, + { url = "https://files.pythonhosted.org/packages/cc/dc/d330a6faefd92b446ec0f0dfea4c3207bb1fef3c4771d19cf4543efd2c78/numpy-2.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a46288ec55ebbd58947d31d72be2c63cbf839f0a63b49cb755022310792a3385", size = 15828784, upload-time = "2024-08-26T20:19:11.19Z" }, +] + +[[package]] +name = "numpy" +version = "2.2.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245, upload-time = "2025-05-17T21:27:58.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048, upload-time = "2025-05-17T21:28:21.406Z" }, + { url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542, upload-time = "2025-05-17T21:28:30.931Z" }, + { url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301, upload-time = "2025-05-17T21:28:41.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320, upload-time = "2025-05-17T21:29:02.78Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050, upload-time = "2025-05-17T21:29:27.675Z" }, + { url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034, upload-time = "2025-05-17T21:29:51.102Z" }, + { url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185, upload-time = "2025-05-17T21:30:18.703Z" }, + { url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149, upload-time = "2025-05-17T21:30:29.788Z" }, + { url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620, upload-time = "2025-05-17T21:30:48.994Z" }, + { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" }, + { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" }, + { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" }, + { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" }, + { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" }, + { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" }, + { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" }, + { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" }, + { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" }, + { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" }, + { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" }, + { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" }, + { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" }, + { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" }, + { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" }, + { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" }, + { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" }, + { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" }, + { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" }, + { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" }, + { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" }, + { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" }, + { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" }, + { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" }, + { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" }, + { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" }, + { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" }, + { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" }, + { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" }, + { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" }, + { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" }, + { url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" }, + { url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" }, + { url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666, upload-time = "2025-05-17T21:45:31.426Z" }, +] + +[[package]] +name = "numpy" +version = "2.4.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] +sdist = { url = "https://files.pythonhosted.org/packages/d7/9f/b8cef5bffa569759033adda9481211426f12f53299629b410340795c2514/numpy-2.4.4.tar.gz", hash = "sha256:2d390634c5182175533585cc89f3608a4682ccb173cc9bb940b2881c8d6f8fa0", size = 20731587, upload-time = "2026-03-29T13:22:01.298Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/c6/4218570d8c8ecc9704b5157a3348e486e84ef4be0ed3e38218ab473c83d2/numpy-2.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f983334aea213c99992053ede6168500e5f086ce74fbc4acc3f2b00f5762e9db", size = 16976799, upload-time = "2026-03-29T13:18:15.438Z" }, + { url = "https://files.pythonhosted.org/packages/dd/92/b4d922c4a5f5dab9ed44e6153908a5c665b71acf183a83b93b690996e39b/numpy-2.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:72944b19f2324114e9dc86a159787333b77874143efcf89a5167ef83cfee8af0", size = 14971552, upload-time = "2026-03-29T13:18:18.606Z" }, + { url = "https://files.pythonhosted.org/packages/8a/dc/df98c095978fa6ee7b9a9387d1d58cbb3d232d0e69ad169a4ce784bde4fd/numpy-2.4.4-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:86b6f55f5a352b48d7fbfd2dbc3d5b780b2d79f4d3c121f33eb6efb22e9a2015", size = 5476566, upload-time = "2026-03-29T13:18:21.532Z" }, + { url = "https://files.pythonhosted.org/packages/28/34/b3fdcec6e725409223dd27356bdf5a3c2cc2282e428218ecc9cb7acc9763/numpy-2.4.4-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:ba1f4fc670ed79f876f70082eff4f9583c15fb9a4b89d6188412de4d18ae2f40", size = 6806482, upload-time = "2026-03-29T13:18:23.634Z" }, + { url = "https://files.pythonhosted.org/packages/68/62/63417c13aa35d57bee1337c67446761dc25ea6543130cf868eace6e8157b/numpy-2.4.4-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a87ec22c87be071b6bdbd27920b129b94f2fc964358ce38f3822635a3e2e03d", size = 15973376, upload-time = "2026-03-29T13:18:26.677Z" }, + { url = "https://files.pythonhosted.org/packages/cf/c5/9fcb7e0e69cef59cf10c746b84f7d58b08bc66a6b7d459783c5a4f6101a6/numpy-2.4.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:df3775294accfdd75f32c74ae39fcba920c9a378a2fc18a12b6820aa8c1fb502", size = 16925137, upload-time = "2026-03-29T13:18:30.14Z" }, + { url = "https://files.pythonhosted.org/packages/7e/43/80020edacb3f84b9efdd1591120a4296462c23fd8db0dde1666f6ef66f13/numpy-2.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0d4e437e295f18ec29bc79daf55e8a47a9113df44d66f702f02a293d93a2d6dd", size = 17329414, upload-time = "2026-03-29T13:18:33.733Z" }, + { url = "https://files.pythonhosted.org/packages/fd/06/af0658593b18a5f73532d377188b964f239eb0894e664a6c12f484472f97/numpy-2.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6aa3236c78803afbcb255045fbef97a9e25a1f6c9888357d205ddc42f4d6eba5", size = 18658397, upload-time = "2026-03-29T13:18:37.511Z" }, + { url = "https://files.pythonhosted.org/packages/e6/ce/13a09ed65f5d0ce5c7dd0669250374c6e379910f97af2c08c57b0608eee4/numpy-2.4.4-cp311-cp311-win32.whl", hash = "sha256:30caa73029a225b2d40d9fae193e008e24b2026b7ee1a867b7ee8d96ca1a448e", size = 6239499, upload-time = "2026-03-29T13:18:40.372Z" }, + { url = "https://files.pythonhosted.org/packages/bd/63/05d193dbb4b5eec1eca73822d80da98b511f8328ad4ae3ca4caf0f4db91d/numpy-2.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:6bbe4eb67390b0a0265a2c25458f6b90a409d5d069f1041e6aff1e27e3d9a79e", size = 12614257, upload-time = "2026-03-29T13:18:42.95Z" }, + { url = "https://files.pythonhosted.org/packages/87/c5/8168052f080c26fa984c413305012be54741c9d0d74abd7fbeeccae3889f/numpy-2.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:fcfe2045fd2e8f3cb0ce9d4ba6dba6333b8fa05bb8a4939c908cd43322d14c7e", size = 10486775, upload-time = "2026-03-29T13:18:45.835Z" }, + { url = "https://files.pythonhosted.org/packages/28/05/32396bec30fb2263770ee910142f49c1476d08e8ad41abf8403806b520ce/numpy-2.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:15716cfef24d3a9762e3acdf87e27f58dc823d1348f765bbea6bef8c639bfa1b", size = 16689272, upload-time = "2026-03-29T13:18:49.223Z" }, + { url = "https://files.pythonhosted.org/packages/c5/f3/a983d28637bfcd763a9c7aafdb6d5c0ebf3d487d1e1459ffdb57e2f01117/numpy-2.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23cbfd4c17357c81021f21540da84ee282b9c8fba38a03b7b9d09ba6b951421e", size = 14699573, upload-time = "2026-03-29T13:18:52.629Z" }, + { url = "https://files.pythonhosted.org/packages/9b/fd/e5ecca1e78c05106d98028114f5c00d3eddb41207686b2b7de3e477b0e22/numpy-2.4.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8b3b60bb7cba2c8c81837661c488637eee696f59a877788a396d33150c35d842", size = 5204782, upload-time = "2026-03-29T13:18:55.579Z" }, + { url = "https://files.pythonhosted.org/packages/de/2f/702a4594413c1a8632092beae8aba00f1d67947389369b3777aed783fdca/numpy-2.4.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:e4a010c27ff6f210ff4c6ef34394cd61470d01014439b192ec22552ee867f2a8", size = 6552038, upload-time = "2026-03-29T13:18:57.769Z" }, + { url = "https://files.pythonhosted.org/packages/7f/37/eed308a8f56cba4d1fdf467a4fc67ef4ff4bf1c888f5fc980481890104b1/numpy-2.4.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f9e75681b59ddaa5e659898085ae0eaea229d054f2ac0c7e563a62205a700121", size = 15670666, upload-time = "2026-03-29T13:19:00.341Z" }, + { url = "https://files.pythonhosted.org/packages/0a/0d/0e3ecece05b7a7e87ab9fb587855548da437a061326fff64a223b6dcb78a/numpy-2.4.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:81f4a14bee47aec54f883e0cad2d73986640c1590eb9bfaaba7ad17394481e6e", size = 16645480, upload-time = "2026-03-29T13:19:03.63Z" }, + { url = "https://files.pythonhosted.org/packages/34/49/f2312c154b82a286758ee2f1743336d50651f8b5195db18cdb63675ff649/numpy-2.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:62d6b0f03b694173f9fcb1fb317f7222fd0b0b103e784c6549f5e53a27718c44", size = 17020036, upload-time = "2026-03-29T13:19:07.428Z" }, + { url = "https://files.pythonhosted.org/packages/7b/e9/736d17bd77f1b0ec4f9901aaec129c00d59f5d84d5e79bba540ef12c2330/numpy-2.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fbc356aae7adf9e6336d336b9c8111d390a05df88f1805573ebb0807bd06fd1d", size = 18368643, upload-time = "2026-03-29T13:19:10.775Z" }, + { url = "https://files.pythonhosted.org/packages/63/f6/d417977c5f519b17c8a5c3bc9e8304b0908b0e21136fe43bf628a1343914/numpy-2.4.4-cp312-cp312-win32.whl", hash = "sha256:0d35aea54ad1d420c812bfa0385c71cd7cc5bcf7c65fed95fc2cd02fe8c79827", size = 5961117, upload-time = "2026-03-29T13:19:13.464Z" }, + { url = "https://files.pythonhosted.org/packages/2d/5b/e1deebf88ff431b01b7406ca3583ab2bbb90972bbe1c568732e49c844f7e/numpy-2.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:b5f0362dc928a6ecd9db58868fca5e48485205e3855957bdedea308f8672ea4a", size = 12320584, upload-time = "2026-03-29T13:19:16.155Z" }, + { url = "https://files.pythonhosted.org/packages/58/89/e4e856ac82a68c3ed64486a544977d0e7bdd18b8da75b78a577ca31c4395/numpy-2.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:846300f379b5b12cc769334464656bc882e0735d27d9726568bc932fdc49d5ec", size = 10221450, upload-time = "2026-03-29T13:19:18.994Z" }, + { url = "https://files.pythonhosted.org/packages/14/1d/d0a583ce4fefcc3308806a749a536c201ed6b5ad6e1322e227ee4848979d/numpy-2.4.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:08f2e31ed5e6f04b118e49821397f12767934cfdd12a1ce86a058f91e004ee50", size = 16684933, upload-time = "2026-03-29T13:19:22.47Z" }, + { url = "https://files.pythonhosted.org/packages/c1/62/2b7a48fbb745d344742c0277f01286dead15f3f68e4f359fbfcf7b48f70f/numpy-2.4.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e823b8b6edc81e747526f70f71a9c0a07ac4e7ad13020aa736bb7c9d67196115", size = 14694532, upload-time = "2026-03-29T13:19:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/e5/87/499737bfba066b4a3bebff24a8f1c5b2dee410b209bc6668c9be692580f0/numpy-2.4.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4a19d9dba1a76618dd86b164d608566f393f8ec6ac7c44f0cc879011c45e65af", size = 5199661, upload-time = "2026-03-29T13:19:28.31Z" }, + { url = "https://files.pythonhosted.org/packages/cd/da/464d551604320d1491bc345efed99b4b7034143a85787aab78d5691d5a0e/numpy-2.4.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d2a8490669bfe99a233298348acc2d824d496dee0e66e31b66a6022c2ad74a5c", size = 6547539, upload-time = "2026-03-29T13:19:30.97Z" }, + { url = "https://files.pythonhosted.org/packages/7d/90/8d23e3b0dafd024bf31bdec225b3bb5c2dbfa6912f8a53b8659f21216cbf/numpy-2.4.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45dbed2ab436a9e826e302fcdcbe9133f9b0006e5af7168afb8963a6520da103", size = 15668806, upload-time = "2026-03-29T13:19:33.887Z" }, + { url = "https://files.pythonhosted.org/packages/d1/73/a9d864e42a01896bb5974475438f16086be9ba1f0d19d0bb7a07427c4a8b/numpy-2.4.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c901b15172510173f5cb310eae652908340f8dede90fff9e3bf6c0d8dfd92f83", size = 16632682, upload-time = "2026-03-29T13:19:37.336Z" }, + { url = "https://files.pythonhosted.org/packages/34/fb/14570d65c3bde4e202a031210475ae9cde9b7686a2e7dc97ee67d2833b35/numpy-2.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:99d838547ace2c4aace6c4f76e879ddfe02bb58a80c1549928477862b7a6d6ed", size = 17019810, upload-time = "2026-03-29T13:19:40.963Z" }, + { url = "https://files.pythonhosted.org/packages/8a/77/2ba9d87081fd41f6d640c83f26fb7351e536b7ce6dd9061b6af5904e8e46/numpy-2.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0aec54fd785890ecca25a6003fd9a5aed47ad607bbac5cd64f836ad8666f4959", size = 18357394, upload-time = "2026-03-29T13:19:44.859Z" }, + { url = "https://files.pythonhosted.org/packages/a2/23/52666c9a41708b0853fa3b1a12c90da38c507a3074883823126d4e9d5b30/numpy-2.4.4-cp313-cp313-win32.whl", hash = "sha256:07077278157d02f65c43b1b26a3886bce886f95d20aabd11f87932750dfb14ed", size = 5959556, upload-time = "2026-03-29T13:19:47.661Z" }, + { url = "https://files.pythonhosted.org/packages/57/fb/48649b4971cde70d817cf97a2a2fdc0b4d8308569f1dd2f2611959d2e0cf/numpy-2.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:5c70f1cc1c4efbe316a572e2d8b9b9cc44e89b95f79ca3331553fbb63716e2bf", size = 12317311, upload-time = "2026-03-29T13:19:50.67Z" }, + { url = "https://files.pythonhosted.org/packages/ba/d8/11490cddd564eb4de97b4579ef6bfe6a736cc07e94c1598590ae25415e01/numpy-2.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:ef4059d6e5152fa1a39f888e344c73fdc926e1b2dd58c771d67b0acfbf2aa67d", size = 10222060, upload-time = "2026-03-29T13:19:54.229Z" }, + { url = "https://files.pythonhosted.org/packages/99/5d/dab4339177a905aad3e2221c915b35202f1ec30d750dd2e5e9d9a72b804b/numpy-2.4.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4bbc7f303d125971f60ec0aaad5e12c62d0d2c925f0ab1273debd0e4ba37aba5", size = 14822302, upload-time = "2026-03-29T13:19:57.585Z" }, + { url = "https://files.pythonhosted.org/packages/eb/e4/0564a65e7d3d97562ed6f9b0fd0fb0a6f559ee444092f105938b50043876/numpy-2.4.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:4d6d57903571f86180eb98f8f0c839fa9ebbfb031356d87f1361be91e433f5b7", size = 5327407, upload-time = "2026-03-29T13:20:00.601Z" }, + { url = "https://files.pythonhosted.org/packages/29/8d/35a3a6ce5ad371afa58b4700f1c820f8f279948cca32524e0a695b0ded83/numpy-2.4.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:4636de7fd195197b7535f231b5de9e4b36d2c440b6e566d2e4e4746e6af0ca93", size = 6647631, upload-time = "2026-03-29T13:20:02.855Z" }, + { url = "https://files.pythonhosted.org/packages/f4/da/477731acbd5a58a946c736edfdabb2ac5b34c3d08d1ba1a7b437fa0884df/numpy-2.4.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ad2e2ef14e0b04e544ea2fa0a36463f847f113d314aa02e5b402fdf910ef309e", size = 15727691, upload-time = "2026-03-29T13:20:06.004Z" }, + { url = "https://files.pythonhosted.org/packages/e6/db/338535d9b152beabeb511579598418ba0212ce77cf9718edd70262cc4370/numpy-2.4.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a285b3b96f951841799528cd1f4f01cd70e7e0204b4abebac9463eecfcf2a40", size = 16681241, upload-time = "2026-03-29T13:20:09.417Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a9/ad248e8f58beb7a0219b413c9c7d8151c5d285f7f946c3e26695bdbbe2df/numpy-2.4.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f8474c4241bc18b750be2abea9d7a9ec84f46ef861dbacf86a4f6e043401f79e", size = 17085767, upload-time = "2026-03-29T13:20:13.126Z" }, + { url = "https://files.pythonhosted.org/packages/b5/1a/3b88ccd3694681356f70da841630e4725a7264d6a885c8d442a697e1146b/numpy-2.4.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4e874c976154687c1f71715b034739b45c7711bec81db01914770373d125e392", size = 18403169, upload-time = "2026-03-29T13:20:17.096Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c9/fcfd5d0639222c6eac7f304829b04892ef51c96a75d479214d77e3ce6e33/numpy-2.4.4-cp313-cp313t-win32.whl", hash = "sha256:9c585a1790d5436a5374bac930dad6ed244c046ed91b2b2a3634eb2971d21008", size = 6083477, upload-time = "2026-03-29T13:20:20.195Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e3/3938a61d1c538aaec8ed6fd6323f57b0c2d2d2219512434c5c878db76553/numpy-2.4.4-cp313-cp313t-win_amd64.whl", hash = "sha256:93e15038125dc1e5345d9b5b68aa7f996ec33b98118d18c6ca0d0b7d6198b7e8", size = 12457487, upload-time = "2026-03-29T13:20:22.946Z" }, + { url = "https://files.pythonhosted.org/packages/97/6a/7e345032cc60501721ef94e0e30b60f6b0bd601f9174ebd36389a2b86d40/numpy-2.4.4-cp313-cp313t-win_arm64.whl", hash = "sha256:0dfd3f9d3adbe2920b68b5cd3d51444e13a10792ec7154cd0a2f6e74d4ab3233", size = 10292002, upload-time = "2026-03-29T13:20:25.909Z" }, + { url = "https://files.pythonhosted.org/packages/6e/06/c54062f85f673dd5c04cbe2f14c3acb8c8b95e3384869bb8cc9bff8cb9df/numpy-2.4.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f169b9a863d34f5d11b8698ead99febeaa17a13ca044961aa8e2662a6c7766a0", size = 16684353, upload-time = "2026-03-29T13:20:29.504Z" }, + { url = "https://files.pythonhosted.org/packages/4c/39/8a320264a84404c74cc7e79715de85d6130fa07a0898f67fb5cd5bd79908/numpy-2.4.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2483e4584a1cb3092da4470b38866634bafb223cbcd551ee047633fd2584599a", size = 14704914, upload-time = "2026-03-29T13:20:33.547Z" }, + { url = "https://files.pythonhosted.org/packages/91/fb/287076b2614e1d1044235f50f03748f31fa287e3dbe6abeb35cdfa351eca/numpy-2.4.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:2d19e6e2095506d1736b7d80595e0f252d76b89f5e715c35e06e937679ea7d7a", size = 5210005, upload-time = "2026-03-29T13:20:36.45Z" }, + { url = "https://files.pythonhosted.org/packages/63/eb/fcc338595309910de6ecabfcef2419a9ce24399680bfb149421fa2df1280/numpy-2.4.4-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:6a246d5914aa1c820c9443ddcee9c02bec3e203b0c080349533fae17727dfd1b", size = 6544974, upload-time = "2026-03-29T13:20:39.014Z" }, + { url = "https://files.pythonhosted.org/packages/44/5d/e7e9044032a716cdfaa3fba27a8e874bf1c5f1912a1ddd4ed071bf8a14a6/numpy-2.4.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:989824e9faf85f96ec9c7761cd8d29c531ad857bfa1daa930cba85baaecf1a9a", size = 15684591, upload-time = "2026-03-29T13:20:42.146Z" }, + { url = "https://files.pythonhosted.org/packages/98/7c/21252050676612625449b4807d6b695b9ce8a7c9e1c197ee6216c8a65c7c/numpy-2.4.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:27a8d92cd10f1382a67d7cf4db7ce18341b66438bdd9f691d7b0e48d104c2a9d", size = 16637700, upload-time = "2026-03-29T13:20:46.204Z" }, + { url = "https://files.pythonhosted.org/packages/b1/29/56d2bbef9465db24ef25393383d761a1af4f446a1df9b8cded4fe3a5a5d7/numpy-2.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e44319a2953c738205bf3354537979eaa3998ed673395b964c1176083dd46252", size = 17035781, upload-time = "2026-03-29T13:20:50.242Z" }, + { url = "https://files.pythonhosted.org/packages/e3/2b/a35a6d7589d21f44cea7d0a98de5ddcbb3d421b2622a5c96b1edf18707c3/numpy-2.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e892aff75639bbef0d2a2cfd55535510df26ff92f63c92cd84ef8d4ba5a5557f", size = 18362959, upload-time = "2026-03-29T13:20:54.019Z" }, + { url = "https://files.pythonhosted.org/packages/64/c9/d52ec581f2390e0f5f85cbfd80fb83d965fc15e9f0e1aec2195faa142cde/numpy-2.4.4-cp314-cp314-win32.whl", hash = "sha256:1378871da56ca8943c2ba674530924bb8ca40cd228358a3b5f302ad60cf875fc", size = 6008768, upload-time = "2026-03-29T13:20:56.912Z" }, + { url = "https://files.pythonhosted.org/packages/fa/22/4cc31a62a6c7b74a8730e31a4274c5dc80e005751e277a2ce38e675e4923/numpy-2.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:715d1c092715954784bc79e1174fc2a90093dc4dc84ea15eb14dad8abdcdeb74", size = 12449181, upload-time = "2026-03-29T13:20:59.548Z" }, + { url = "https://files.pythonhosted.org/packages/70/2e/14cda6f4d8e396c612d1bf97f22958e92148801d7e4f110cabebdc0eef4b/numpy-2.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:2c194dd721e54ecad9ad387c1d35e63dce5c4450c6dc7dd5611283dda239aabb", size = 10496035, upload-time = "2026-03-29T13:21:02.524Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e8/8fed8c8d848d7ecea092dc3469643f9d10bc3a134a815a3b033da1d2039b/numpy-2.4.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2aa0613a5177c264ff5921051a5719d20095ea586ca88cc802c5c218d1c67d3e", size = 14824958, upload-time = "2026-03-29T13:21:05.671Z" }, + { url = "https://files.pythonhosted.org/packages/05/1a/d8007a5138c179c2bf33ef44503e83d70434d2642877ee8fbb230e7c0548/numpy-2.4.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:42c16925aa5a02362f986765f9ebabf20de75cdefdca827d14315c568dcab113", size = 5330020, upload-time = "2026-03-29T13:21:08.635Z" }, + { url = "https://files.pythonhosted.org/packages/99/64/ffb99ac6ae93faf117bcbd5c7ba48a7f45364a33e8e458545d3633615dda/numpy-2.4.4-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:874f200b2a981c647340f841730fc3a2b54c9d940566a3c4149099591e2c4c3d", size = 6650758, upload-time = "2026-03-29T13:21:10.949Z" }, + { url = "https://files.pythonhosted.org/packages/6e/6e/795cc078b78a384052e73b2f6281ff7a700e9bf53bcce2ee579d4f6dd879/numpy-2.4.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9b39d38a9bd2ae1becd7eac1303d031c5c110ad31f2b319c6e7d98b135c934d", size = 15729948, upload-time = "2026-03-29T13:21:14.047Z" }, + { url = "https://files.pythonhosted.org/packages/5f/86/2acbda8cc2af5f3d7bfc791192863b9e3e19674da7b5e533fded124d1299/numpy-2.4.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b268594bccac7d7cf5844c7732e3f20c50921d94e36d7ec9b79e9857694b1b2f", size = 16679325, upload-time = "2026-03-29T13:21:17.561Z" }, + { url = "https://files.pythonhosted.org/packages/bc/59/cafd83018f4aa55e0ac6fa92aa066c0a1877b77a615ceff1711c260ffae8/numpy-2.4.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ac6b31e35612a26483e20750126d30d0941f949426974cace8e6b5c58a3657b0", size = 17084883, upload-time = "2026-03-29T13:21:21.106Z" }, + { url = "https://files.pythonhosted.org/packages/f0/85/a42548db84e65ece46ab2caea3d3f78b416a47af387fcbb47ec28e660dc2/numpy-2.4.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8e3ed142f2728df44263aaf5fb1f5b0b99f4070c553a0d7f033be65338329150", size = 18403474, upload-time = "2026-03-29T13:21:24.828Z" }, + { url = "https://files.pythonhosted.org/packages/ed/ad/483d9e262f4b831000062e5d8a45e342166ec8aaa1195264982bca267e62/numpy-2.4.4-cp314-cp314t-win32.whl", hash = "sha256:dddbbd259598d7240b18c9d87c56a9d2fb3b02fe266f49a7c101532e78c1d871", size = 6155500, upload-time = "2026-03-29T13:21:28.205Z" }, + { url = "https://files.pythonhosted.org/packages/c7/03/2fc4e14c7bd4ff2964b74ba90ecb8552540b6315f201df70f137faa5c589/numpy-2.4.4-cp314-cp314t-win_amd64.whl", hash = "sha256:a7164afb23be6e37ad90b2f10426149fd75aee07ca55653d2aa41e66c4ef697e", size = 12637755, upload-time = "2026-03-29T13:21:31.107Z" }, + { url = "https://files.pythonhosted.org/packages/58/78/548fb8e07b1a341746bfbecb32f2c268470f45fa028aacdbd10d9bc73aab/numpy-2.4.4-cp314-cp314t-win_arm64.whl", hash = "sha256:ba203255017337d39f89bdd58417f03c4426f12beed0440cfd933cb15f8669c7", size = 10566643, upload-time = "2026-03-29T13:21:34.339Z" }, + { url = "https://files.pythonhosted.org/packages/6b/33/8fae8f964a4f63ed528264ddf25d2b683d0b663e3cba26961eb838a7c1bd/numpy-2.4.4-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:58c8b5929fcb8287cbd6f0a3fae19c6e03a5c48402ae792962ac465224a629a4", size = 16854491, upload-time = "2026-03-29T13:21:38.03Z" }, + { url = "https://files.pythonhosted.org/packages/bc/d0/1aabee441380b981cf8cdda3ae7a46aa827d1b5a8cce84d14598bc94d6d9/numpy-2.4.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:eea7ac5d2dce4189771cedb559c738a71512768210dc4e4753b107a2048b3d0e", size = 14895830, upload-time = "2026-03-29T13:21:41.509Z" }, + { url = "https://files.pythonhosted.org/packages/a5/b8/aafb0d1065416894fccf4df6b49ef22b8db045187949545bced89c034b8e/numpy-2.4.4-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:51fc224f7ca4d92656d5a5eb315f12eb5fe2c97a66249aa7b5f562528a3be38c", size = 5400927, upload-time = "2026-03-29T13:21:44.747Z" }, + { url = "https://files.pythonhosted.org/packages/d6/77/063baa20b08b431038c7f9ff5435540c7b7265c78cf56012a483019ca72d/numpy-2.4.4-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:28a650663f7314afc3e6ec620f44f333c386aad9f6fc472030865dc0ebb26ee3", size = 6715557, upload-time = "2026-03-29T13:21:47.406Z" }, + { url = "https://files.pythonhosted.org/packages/c7/a8/379542d45a14f149444c5c4c4e7714707239ce9cc1de8c2803958889da14/numpy-2.4.4-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:19710a9ca9992d7174e9c52f643d4272dcd1558c5f7af7f6f8190f633bd651a7", size = 15804253, upload-time = "2026-03-29T13:21:50.753Z" }, + { url = "https://files.pythonhosted.org/packages/a2/c8/f0a45426d6d21e7ea3310a15cf90c43a14d9232c31a837702dba437f3373/numpy-2.4.4-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9b2aec6af35c113b05695ebb5749a787acd63cafc83086a05771d1e1cd1e555f", size = 16753552, upload-time = "2026-03-29T13:21:54.344Z" }, + { url = "https://files.pythonhosted.org/packages/04/74/f4c001f4714c3ad9ce037e18cf2b9c64871a84951eaa0baf683a9ca9301c/numpy-2.4.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f2cf083b324a467e1ab358c105f6cad5ea950f50524668a80c486ff1db24e119", size = 12509075, upload-time = "2026-03-29T13:21:57.644Z" }, +] + +[[package]] +name = "pandas" +version = "2.0.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +dependencies = [ + { name = "numpy", version = "1.24.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "python-dateutil", marker = "python_full_version < '3.9'" }, + { name = "pytz", marker = "python_full_version < '3.9'" }, + { name = "tzdata", marker = "python_full_version < '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/a7/824332581e258b5aa4f3763ecb2a797e5f9a54269044ba2e50ac19936b32/pandas-2.0.3.tar.gz", hash = "sha256:c02f372a88e0d17f36d3093a644c73cfc1788e876a7c4bcb4020a77512e2043c", size = 5284455, upload-time = "2023-06-28T23:19:33.371Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/b2/0d4a5729ce1ce11630c4fc5d5522a33b967b3ca146c210f58efde7c40e99/pandas-2.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e4c7c9f27a4185304c7caf96dc7d91bc60bc162221152de697c98eb0b2648dd8", size = 11760908, upload-time = "2023-06-28T23:15:57.001Z" }, + { url = "https://files.pythonhosted.org/packages/4a/f6/f620ca62365d83e663a255a41b08d2fc2eaf304e0b8b21bb6d62a7390fe3/pandas-2.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f167beed68918d62bffb6ec64f2e1d8a7d297a038f86d4aed056b9493fca407f", size = 10823486, upload-time = "2023-06-28T23:16:06.863Z" }, + { url = "https://files.pythonhosted.org/packages/c2/59/cb4234bc9b968c57e81861b306b10cd8170272c57b098b724d3de5eda124/pandas-2.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce0c6f76a0f1ba361551f3e6dceaff06bde7514a374aa43e33b588ec10420183", size = 11571897, upload-time = "2023-06-28T23:16:14.208Z" }, + { url = "https://files.pythonhosted.org/packages/e3/59/35a2892bf09ded9c1bf3804461efe772836a5261ef5dfb4e264ce813ff99/pandas-2.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba619e410a21d8c387a1ea6e8a0e49bb42216474436245718d7f2e88a2f8d7c0", size = 12306421, upload-time = "2023-06-28T23:16:23.26Z" }, + { url = "https://files.pythonhosted.org/packages/94/71/3a0c25433c54bb29b48e3155b959ac78f4c4f2f06f94d8318aac612cb80f/pandas-2.0.3-cp310-cp310-win32.whl", hash = "sha256:3ef285093b4fe5058eefd756100a367f27029913760773c8bf1d2d8bebe5d210", size = 9540792, upload-time = "2023-06-28T23:16:30.876Z" }, + { url = "https://files.pythonhosted.org/packages/ed/30/b97456e7063edac0e5a405128065f0cd2033adfe3716fb2256c186bd41d0/pandas-2.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:9ee1a69328d5c36c98d8e74db06f4ad518a1840e8ccb94a4ba86920986bb617e", size = 10664333, upload-time = "2023-06-28T23:16:39.209Z" }, + { url = "https://files.pythonhosted.org/packages/b3/92/a5e5133421b49e901a12e02a6a7ef3a0130e10d13db8cb657fdd0cba3b90/pandas-2.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b084b91d8d66ab19f5bb3256cbd5ea661848338301940e17f4492b2ce0801fe8", size = 11645672, upload-time = "2023-06-28T23:16:47.601Z" }, + { url = "https://files.pythonhosted.org/packages/8f/bb/aea1fbeed5b474cb8634364718abe9030d7cc7a30bf51f40bd494bbc89a2/pandas-2.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:37673e3bdf1551b95bf5d4ce372b37770f9529743d2498032439371fc7b7eb26", size = 10693229, upload-time = "2023-06-28T23:16:56.397Z" }, + { url = "https://files.pythonhosted.org/packages/d6/90/e7d387f1a416b14e59290baa7a454a90d719baebbf77433ff1bdcc727800/pandas-2.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9cb1e14fdb546396b7e1b923ffaeeac24e4cedd14266c3497216dd4448e4f2d", size = 11581591, upload-time = "2023-06-28T23:17:04.234Z" }, + { url = "https://files.pythonhosted.org/packages/d0/28/88b81881c056376254618fad622a5e94b5126db8c61157ea1910cd1c040a/pandas-2.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9cd88488cceb7635aebb84809d087468eb33551097d600c6dad13602029c2df", size = 12219370, upload-time = "2023-06-28T23:17:11.783Z" }, + { url = "https://files.pythonhosted.org/packages/e4/a5/212b9039e25bf8ebb97e417a96660e3dc925dacd3f8653d531b8f7fd9be4/pandas-2.0.3-cp311-cp311-win32.whl", hash = "sha256:694888a81198786f0e164ee3a581df7d505024fbb1f15202fc7db88a71d84ebd", size = 9482935, upload-time = "2023-06-28T23:17:21.376Z" }, + { url = "https://files.pythonhosted.org/packages/9e/71/756a1be6bee0209d8c0d8c5e3b9fc72c00373f384a4017095ec404aec3ad/pandas-2.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:6a21ab5c89dcbd57f78d0ae16630b090eec626360085a4148693def5452d8a6b", size = 10607692, upload-time = "2023-06-28T23:17:28.824Z" }, + { url = "https://files.pythonhosted.org/packages/78/a8/07dd10f90ca915ed914853cd57f79bfc22e1ef4384ab56cb4336d2fc1f2a/pandas-2.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9e4da0d45e7f34c069fe4d522359df7d23badf83abc1d1cef398895822d11061", size = 11653303, upload-time = "2023-06-28T23:17:36.329Z" }, + { url = "https://files.pythonhosted.org/packages/53/c3/f8e87361f7fdf42012def602bfa2a593423c729f5cb7c97aed7f51be66ac/pandas-2.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:32fca2ee1b0d93dd71d979726b12b61faa06aeb93cf77468776287f41ff8fdc5", size = 10710932, upload-time = "2023-06-28T23:17:49.875Z" }, + { url = "https://files.pythonhosted.org/packages/a7/87/828d50c81ce0f434163bf70b925a0eec6076808e0bca312a79322b141f66/pandas-2.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:258d3624b3ae734490e4d63c430256e716f488c4fcb7c8e9bde2d3aa46c29089", size = 11684018, upload-time = "2023-06-28T23:18:05.845Z" }, + { url = "https://files.pythonhosted.org/packages/f8/7f/5b047effafbdd34e52c9e2d7e44f729a0655efafb22198c45cf692cdc157/pandas-2.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eae3dc34fa1aa7772dd3fc60270d13ced7346fcbcfee017d3132ec625e23bb0", size = 12353723, upload-time = "2023-06-28T23:18:17.631Z" }, + { url = "https://files.pythonhosted.org/packages/ea/ae/26a2eda7fa581347d69e51f93892493b2074ef3352ac71033c9f32c52389/pandas-2.0.3-cp38-cp38-win32.whl", hash = "sha256:f3421a7afb1a43f7e38e82e844e2bca9a6d793d66c1a7f9f0ff39a795bbc5e02", size = 9646403, upload-time = "2023-06-28T23:18:24.328Z" }, + { url = "https://files.pythonhosted.org/packages/c3/6c/ea362eef61f05553aaf1a24b3e96b2d0603f5dc71a3bd35688a24ed88843/pandas-2.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:69d7f3884c95da3a31ef82b7618af5710dba95bb885ffab339aad925c3e8ce78", size = 10777638, upload-time = "2023-06-28T23:18:30.947Z" }, + { url = "https://files.pythonhosted.org/packages/f8/c7/cfef920b7b457dff6928e824896cb82367650ea127d048ee0b820026db4f/pandas-2.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5247fb1ba347c1261cbbf0fcfba4a3121fbb4029d95d9ef4dc45406620b25c8b", size = 11834160, upload-time = "2023-06-28T23:18:40.332Z" }, + { url = "https://files.pythonhosted.org/packages/6c/1c/689c9d99bc4e5d366a5fd871f0bcdee98a6581e240f96b78d2d08f103774/pandas-2.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:81af086f4543c9d8bb128328b5d32e9986e0c84d3ee673a2ac6fb57fd14f755e", size = 10862752, upload-time = "2023-06-28T23:18:50.016Z" }, + { url = "https://files.pythonhosted.org/packages/cc/b8/4d082f41c27c95bf90485d1447b647cc7e5680fea75e315669dc6e4cb398/pandas-2.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1994c789bf12a7c5098277fb43836ce090f1073858c10f9220998ac74f37c69b", size = 11715852, upload-time = "2023-06-28T23:19:00.594Z" }, + { url = "https://files.pythonhosted.org/packages/9e/0d/91a9fd2c202f2b1d97a38ab591890f86480ecbb596cbc56d035f6f23fdcc/pandas-2.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ec591c48e29226bcbb316e0c1e9423622bc7a4eaf1ef7c3c9fa1a3981f89641", size = 12398496, upload-time = "2023-06-28T23:19:11.78Z" }, + { url = "https://files.pythonhosted.org/packages/26/7d/d8aa0a2c4f3f5f8ea59fb946c8eafe8f508090ca73e2b08a9af853c1103e/pandas-2.0.3-cp39-cp39-win32.whl", hash = "sha256:04dbdbaf2e4d46ca8da896e1805bc04eb85caa9a82e259e8eed00254d5e0c682", size = 9630766, upload-time = "2023-06-28T23:19:18.182Z" }, + { url = "https://files.pythonhosted.org/packages/9a/f2/0ad053856debbe90c83de1b4f05915f85fd2146f20faf9daa3b320d36df3/pandas-2.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:1168574b036cd8b93abc746171c9b4f1b83467438a5e45909fed645cf8692dbc", size = 10755902, upload-time = "2023-06-28T23:19:25.151Z" }, +] + +[[package]] +name = "pandas" +version = "2.3.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", +] +dependencies = [ + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "python-dateutil", marker = "python_full_version >= '3.9' and python_full_version < '3.11'" }, + { name = "pytz", marker = "python_full_version >= '3.9' and python_full_version < '3.11'" }, + { name = "tzdata", marker = "python_full_version >= '3.9' and python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/f7/f425a00df4fcc22b292c6895c6831c0c8ae1d9fac1e024d16f98a9ce8749/pandas-2.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:376c6446ae31770764215a6c937f72d917f214b43560603cd60da6408f183b6c", size = 11555763, upload-time = "2025-09-29T23:16:53.287Z" }, + { url = "https://files.pythonhosted.org/packages/13/4f/66d99628ff8ce7857aca52fed8f0066ce209f96be2fede6cef9f84e8d04f/pandas-2.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e19d192383eab2f4ceb30b412b22ea30690c9e618f78870357ae1d682912015a", size = 10801217, upload-time = "2025-09-29T23:17:04.522Z" }, + { url = "https://files.pythonhosted.org/packages/1d/03/3fc4a529a7710f890a239cc496fc6d50ad4a0995657dccc1d64695adb9f4/pandas-2.3.3-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5caf26f64126b6c7aec964f74266f435afef1c1b13da3b0636c7518a1fa3e2b1", size = 12148791, upload-time = "2025-09-29T23:17:18.444Z" }, + { url = "https://files.pythonhosted.org/packages/40/a8/4dac1f8f8235e5d25b9955d02ff6f29396191d4e665d71122c3722ca83c5/pandas-2.3.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd7478f1463441ae4ca7308a70e90b33470fa593429f9d4c578dd00d1fa78838", size = 12769373, upload-time = "2025-09-29T23:17:35.846Z" }, + { url = "https://files.pythonhosted.org/packages/df/91/82cc5169b6b25440a7fc0ef3a694582418d875c8e3ebf796a6d6470aa578/pandas-2.3.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4793891684806ae50d1288c9bae9330293ab4e083ccd1c5e383c34549c6e4250", size = 13200444, upload-time = "2025-09-29T23:17:49.341Z" }, + { url = "https://files.pythonhosted.org/packages/10/ae/89b3283800ab58f7af2952704078555fa60c807fff764395bb57ea0b0dbd/pandas-2.3.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:28083c648d9a99a5dd035ec125d42439c6c1c525098c58af0fc38dd1a7a1b3d4", size = 13858459, upload-time = "2025-09-29T23:18:03.722Z" }, + { url = "https://files.pythonhosted.org/packages/85/72/530900610650f54a35a19476eca5104f38555afccda1aa11a92ee14cb21d/pandas-2.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:503cf027cf9940d2ceaa1a93cfb5f8c8c7e6e90720a2850378f0b3f3b1e06826", size = 11346086, upload-time = "2025-09-29T23:18:18.505Z" }, + { url = "https://files.pythonhosted.org/packages/c1/fa/7ac648108144a095b4fb6aa3de1954689f7af60a14cf25583f4960ecb878/pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523", size = 11578790, upload-time = "2025-09-29T23:18:30.065Z" }, + { url = "https://files.pythonhosted.org/packages/9b/35/74442388c6cf008882d4d4bdfc4109be87e9b8b7ccd097ad1e7f006e2e95/pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45", size = 10833831, upload-time = "2025-09-29T23:38:56.071Z" }, + { url = "https://files.pythonhosted.org/packages/fe/e4/de154cbfeee13383ad58d23017da99390b91d73f8c11856f2095e813201b/pandas-2.3.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b468d3dad6ff947df92dcb32ede5b7bd41a9b3cceef0a30ed925f6d01fb8fa66", size = 12199267, upload-time = "2025-09-29T23:18:41.627Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c9/63f8d545568d9ab91476b1818b4741f521646cbdd151c6efebf40d6de6f7/pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b", size = 12789281, upload-time = "2025-09-29T23:18:56.834Z" }, + { url = "https://files.pythonhosted.org/packages/f2/00/a5ac8c7a0e67fd1a6059e40aa08fa1c52cc00709077d2300e210c3ce0322/pandas-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37b5848ba49824e5c30bedb9c830ab9b7751fd049bc7914533e01c65f79791", size = 13240453, upload-time = "2025-09-29T23:19:09.247Z" }, + { url = "https://files.pythonhosted.org/packages/27/4d/5c23a5bc7bd209231618dd9e606ce076272c9bc4f12023a70e03a86b4067/pandas-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db4301b2d1f926ae677a751eb2bd0e8c5f5319c9cb3f88b0becbbb0b07b34151", size = 13890361, upload-time = "2025-09-29T23:19:25.342Z" }, + { url = "https://files.pythonhosted.org/packages/8e/59/712db1d7040520de7a4965df15b774348980e6df45c129b8c64d0dbe74ef/pandas-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c", size = 11348702, upload-time = "2025-09-29T23:19:38.296Z" }, + { url = "https://files.pythonhosted.org/packages/9c/fb/231d89e8637c808b997d172b18e9d4a4bc7bf31296196c260526055d1ea0/pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53", size = 11597846, upload-time = "2025-09-29T23:19:48.856Z" }, + { url = "https://files.pythonhosted.org/packages/5c/bd/bf8064d9cfa214294356c2d6702b716d3cf3bb24be59287a6a21e24cae6b/pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35", size = 10729618, upload-time = "2025-09-29T23:39:08.659Z" }, + { url = "https://files.pythonhosted.org/packages/57/56/cf2dbe1a3f5271370669475ead12ce77c61726ffd19a35546e31aa8edf4e/pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908", size = 11737212, upload-time = "2025-09-29T23:19:59.765Z" }, + { url = "https://files.pythonhosted.org/packages/e5/63/cd7d615331b328e287d8233ba9fdf191a9c2d11b6af0c7a59cfcec23de68/pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89", size = 12362693, upload-time = "2025-09-29T23:20:14.098Z" }, + { url = "https://files.pythonhosted.org/packages/a6/de/8b1895b107277d52f2b42d3a6806e69cfef0d5cf1d0ba343470b9d8e0a04/pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98", size = 12771002, upload-time = "2025-09-29T23:20:26.76Z" }, + { url = "https://files.pythonhosted.org/packages/87/21/84072af3187a677c5893b170ba2c8fbe450a6ff911234916da889b698220/pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084", size = 13450971, upload-time = "2025-09-29T23:20:41.344Z" }, + { url = "https://files.pythonhosted.org/packages/86/41/585a168330ff063014880a80d744219dbf1dd7a1c706e75ab3425a987384/pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b", size = 10992722, upload-time = "2025-09-29T23:20:54.139Z" }, + { url = "https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713", size = 11544671, upload-time = "2025-09-29T23:21:05.024Z" }, + { url = "https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8", size = 10680807, upload-time = "2025-09-29T23:21:15.979Z" }, + { url = "https://files.pythonhosted.org/packages/16/87/9472cf4a487d848476865321de18cc8c920b8cab98453ab79dbbc98db63a/pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d", size = 11709872, upload-time = "2025-09-29T23:21:27.165Z" }, + { url = "https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac", size = 12306371, upload-time = "2025-09-29T23:21:40.532Z" }, + { url = "https://files.pythonhosted.org/packages/33/81/a3afc88fca4aa925804a27d2676d22dcd2031c2ebe08aabd0ae55b9ff282/pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c", size = 12765333, upload-time = "2025-09-29T23:21:55.77Z" }, + { url = "https://files.pythonhosted.org/packages/8d/0f/b4d4ae743a83742f1153464cf1a8ecfafc3ac59722a0b5c8602310cb7158/pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493", size = 13418120, upload-time = "2025-09-29T23:22:10.109Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee", size = 10993991, upload-time = "2025-09-29T23:25:04.889Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ca/3f8d4f49740799189e1395812f3bf23b5e8fc7c190827d55a610da72ce55/pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5", size = 12048227, upload-time = "2025-09-29T23:22:24.343Z" }, + { url = "https://files.pythonhosted.org/packages/0e/5a/f43efec3e8c0cc92c4663ccad372dbdff72b60bdb56b2749f04aa1d07d7e/pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21", size = 11411056, upload-time = "2025-09-29T23:22:37.762Z" }, + { url = "https://files.pythonhosted.org/packages/46/b1/85331edfc591208c9d1a63a06baa67b21d332e63b7a591a5ba42a10bb507/pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78", size = 11645189, upload-time = "2025-09-29T23:22:51.688Z" }, + { url = "https://files.pythonhosted.org/packages/44/23/78d645adc35d94d1ac4f2a3c4112ab6f5b8999f4898b8cdf01252f8df4a9/pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110", size = 12121912, upload-time = "2025-09-29T23:23:05.042Z" }, + { url = "https://files.pythonhosted.org/packages/53/da/d10013df5e6aaef6b425aa0c32e1fc1f3e431e4bcabd420517dceadce354/pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86", size = 12712160, upload-time = "2025-09-29T23:23:28.57Z" }, + { url = "https://files.pythonhosted.org/packages/bd/17/e756653095a083d8a37cbd816cb87148debcfcd920129b25f99dd8d04271/pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc", size = 13199233, upload-time = "2025-09-29T23:24:24.876Z" }, + { url = "https://files.pythonhosted.org/packages/04/fd/74903979833db8390b73b3a8a7d30d146d710bd32703724dd9083950386f/pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0", size = 11540635, upload-time = "2025-09-29T23:25:52.486Z" }, + { url = "https://files.pythonhosted.org/packages/21/00/266d6b357ad5e6d3ad55093a7e8efc7dd245f5a842b584db9f30b0f0a287/pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593", size = 10759079, upload-time = "2025-09-29T23:26:33.204Z" }, + { url = "https://files.pythonhosted.org/packages/ca/05/d01ef80a7a3a12b2f8bbf16daba1e17c98a2f039cbc8e2f77a2c5a63d382/pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c", size = 11814049, upload-time = "2025-09-29T23:27:15.384Z" }, + { url = "https://files.pythonhosted.org/packages/15/b2/0e62f78c0c5ba7e3d2c5945a82456f4fac76c480940f805e0b97fcbc2f65/pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b", size = 12332638, upload-time = "2025-09-29T23:27:51.625Z" }, + { url = "https://files.pythonhosted.org/packages/c5/33/dd70400631b62b9b29c3c93d2feee1d0964dc2bae2e5ad7a6c73a7f25325/pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6", size = 12886834, upload-time = "2025-09-29T23:28:21.289Z" }, + { url = "https://files.pythonhosted.org/packages/d3/18/b5d48f55821228d0d2692b34fd5034bb185e854bdb592e9c640f6290e012/pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3", size = 13409925, upload-time = "2025-09-29T23:28:58.261Z" }, + { url = "https://files.pythonhosted.org/packages/a6/3d/124ac75fcd0ecc09b8fdccb0246ef65e35b012030defb0e0eba2cbbbe948/pandas-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5", size = 11109071, upload-time = "2025-09-29T23:32:27.484Z" }, + { url = "https://files.pythonhosted.org/packages/89/9c/0e21c895c38a157e0faa1fb64587a9226d6dd46452cac4532d80c3c4a244/pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec", size = 12048504, upload-time = "2025-09-29T23:29:31.47Z" }, + { url = "https://files.pythonhosted.org/packages/d7/82/b69a1c95df796858777b68fbe6a81d37443a33319761d7c652ce77797475/pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7", size = 11410702, upload-time = "2025-09-29T23:29:54.591Z" }, + { url = "https://files.pythonhosted.org/packages/f9/88/702bde3ba0a94b8c73a0181e05144b10f13f29ebfc2150c3a79062a8195d/pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450", size = 11634535, upload-time = "2025-09-29T23:30:21.003Z" }, + { url = "https://files.pythonhosted.org/packages/a4/1e/1bac1a839d12e6a82ec6cb40cda2edde64a2013a66963293696bbf31fbbb/pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5", size = 12121582, upload-time = "2025-09-29T23:30:43.391Z" }, + { url = "https://files.pythonhosted.org/packages/44/91/483de934193e12a3b1d6ae7c8645d083ff88dec75f46e827562f1e4b4da6/pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788", size = 12699963, upload-time = "2025-09-29T23:31:10.009Z" }, + { url = "https://files.pythonhosted.org/packages/70/44/5191d2e4026f86a2a109053e194d3ba7a31a2d10a9c2348368c63ed4e85a/pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87", size = 13202175, upload-time = "2025-09-29T23:31:59.173Z" }, + { url = "https://files.pythonhosted.org/packages/56/b4/52eeb530a99e2a4c55ffcd352772b599ed4473a0f892d127f4147cf0f88e/pandas-2.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c503ba5216814e295f40711470446bc3fd00f0faea8a086cbc688808e26f92a2", size = 11567720, upload-time = "2025-09-29T23:33:06.209Z" }, + { url = "https://files.pythonhosted.org/packages/48/4a/2d8b67632a021bced649ba940455ed441ca854e57d6e7658a6024587b083/pandas-2.3.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a637c5cdfa04b6d6e2ecedcb81fc52ffb0fd78ce2ebccc9ea964df9f658de8c8", size = 10810302, upload-time = "2025-09-29T23:33:35.846Z" }, + { url = "https://files.pythonhosted.org/packages/13/e6/d2465010ee0569a245c975dc6967b801887068bc893e908239b1f4b6c1ac/pandas-2.3.3-cp39-cp39-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:854d00d556406bffe66a4c0802f334c9ad5a96b4f1f868adf036a21b11ef13ff", size = 12154874, upload-time = "2025-09-29T23:33:49.939Z" }, + { url = "https://files.pythonhosted.org/packages/1f/18/aae8c0aa69a386a3255940e9317f793808ea79d0a525a97a903366bb2569/pandas-2.3.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf1f8a81d04ca90e32a0aceb819d34dbd378a98bf923b6398b9a3ec0bf44de29", size = 12790141, upload-time = "2025-09-29T23:34:05.655Z" }, + { url = "https://files.pythonhosted.org/packages/f7/26/617f98de789de00c2a444fbe6301bb19e66556ac78cff933d2c98f62f2b4/pandas-2.3.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:23ebd657a4d38268c7dfbdf089fbc31ea709d82e4923c5ffd4fbd5747133ce73", size = 13208697, upload-time = "2025-09-29T23:34:21.835Z" }, + { url = "https://files.pythonhosted.org/packages/b9/fb/25709afa4552042bd0e15717c75e9b4a2294c3dc4f7e6ea50f03c5136600/pandas-2.3.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5554c929ccc317d41a5e3d1234f3be588248e61f08a74dd17c9eabb535777dc9", size = 13879233, upload-time = "2025-09-29T23:34:35.079Z" }, + { url = "https://files.pythonhosted.org/packages/98/af/7be05277859a7bc399da8ba68b88c96b27b48740b6cf49688899c6eb4176/pandas-2.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:d3e28b3e83862ccf4d85ff19cf8c20b2ae7e503881711ff2d534dc8f761131aa", size = 11359119, upload-time = "2025-09-29T23:34:46.339Z" }, +] + +[[package]] +name = "pandas" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] +dependencies = [ + { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "python-dateutil", marker = "python_full_version >= '3.11'" }, + { name = "tzdata", marker = "(python_full_version >= '3.11' and sys_platform == 'emscripten') or (python_full_version >= '3.11' and sys_platform == 'win32')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/da/99/b342345300f13440fe9fe385c3c481e2d9a595ee3bab4d3219247ac94e9a/pandas-3.0.2.tar.gz", hash = "sha256:f4753e73e34c8d83221ba58f232433fca2748be8b18dbca02d242ed153945043", size = 4645855, upload-time = "2026-03-31T06:48:30.816Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/35/6411db530c618e0e0005187e35aa02ce60ae4c4c4d206964a2f978217c27/pandas-3.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a727a73cbdba2f7458dc82449e2315899d5140b449015d822f515749a46cbbe0", size = 10326926, upload-time = "2026-03-31T06:46:08.29Z" }, + { url = "https://files.pythonhosted.org/packages/c4/d3/b7da1d5d7dbdc5ef52ed7debd2b484313b832982266905315dad5a0bf0b1/pandas-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dbbd4aa20ca51e63b53bbde6a0fa4254b1aaabb74d2f542df7a7959feb1d760c", size = 9926987, upload-time = "2026-03-31T06:46:11.724Z" }, + { url = "https://files.pythonhosted.org/packages/52/77/9b1c2d6070b5dbe239a7bc889e21bfa58720793fb902d1e070695d87c6d0/pandas-3.0.2-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:339dda302bd8369dedeae979cb750e484d549b563c3f54f3922cb8ff4978c5eb", size = 10757067, upload-time = "2026-03-31T06:46:14.903Z" }, + { url = "https://files.pythonhosted.org/packages/20/17/ec40d981705654853726e7ac9aea9ddbb4a5d9cf54d8472222f4f3de06c2/pandas-3.0.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:61c2fd96d72b983a9891b2598f286befd4ad262161a609c92dc1652544b46b76", size = 11258787, upload-time = "2026-03-31T06:46:17.683Z" }, + { url = "https://files.pythonhosted.org/packages/90/e3/3f1126d43d3702ca8773871a81c9f15122a1f412342cc56284ffda5b1f70/pandas-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c934008c733b8bbea273ea308b73b3156f0181e5b72960790b09c18a2794fe1e", size = 11771616, upload-time = "2026-03-31T06:46:20.532Z" }, + { url = "https://files.pythonhosted.org/packages/2e/cf/0f4e268e1f5062e44a6bda9f925806721cd4c95c2b808a4c82ebe914f96b/pandas-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:60a80bb4feacbef5e1447a3f82c33209c8b7e07f28d805cfd1fb951e5cb443aa", size = 12337623, upload-time = "2026-03-31T06:46:23.754Z" }, + { url = "https://files.pythonhosted.org/packages/44/a0/97a6339859d4acb2536efb24feb6708e82f7d33b2ed7e036f2983fcced82/pandas-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:ed72cb3f45190874eb579c64fa92d9df74e98fd63e2be7f62bce5ace0ade61df", size = 9897372, upload-time = "2026-03-31T06:46:26.703Z" }, + { url = "https://files.pythonhosted.org/packages/8f/eb/781516b808a99ddf288143cec46b342b3016c3414d137da1fdc3290d8860/pandas-3.0.2-cp311-cp311-win_arm64.whl", hash = "sha256:f12b1a9e332c01e09510586f8ca9b108fd631fd656af82e452d7315ef6df5f9f", size = 9154922, upload-time = "2026-03-31T06:46:30.284Z" }, + { url = "https://files.pythonhosted.org/packages/f3/b0/c20bd4d6d3f736e6bd6b55794e9cd0a617b858eaad27c8f410ea05d953b7/pandas-3.0.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:232a70ebb568c0c4d2db4584f338c1577d81e3af63292208d615907b698a0f18", size = 10347921, upload-time = "2026-03-31T06:46:33.36Z" }, + { url = "https://files.pythonhosted.org/packages/35/d0/4831af68ce30cc2d03c697bea8450e3225a835ef497d0d70f31b8cdde965/pandas-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:970762605cff1ca0d3f71ed4f3a769ea8f85fc8e6348f6e110b8fea7e6eb5a14", size = 9888127, upload-time = "2026-03-31T06:46:36.253Z" }, + { url = "https://files.pythonhosted.org/packages/61/a9/16ea9346e1fc4a96e2896242d9bc674764fb9049b0044c0132502f7a771e/pandas-3.0.2-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aff4e6f4d722e0652707d7bcb190c445fe58428500c6d16005b02401764b1b3d", size = 10399577, upload-time = "2026-03-31T06:46:39.224Z" }, + { url = "https://files.pythonhosted.org/packages/c4/a8/3a61a721472959ab0ce865ef05d10b0d6bfe27ce8801c99f33d4fa996e65/pandas-3.0.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ef8b27695c3d3dc78403c9a7d5e59a62d5464a7e1123b4e0042763f7104dc74f", size = 10880030, upload-time = "2026-03-31T06:46:42.412Z" }, + { url = "https://files.pythonhosted.org/packages/da/65/7225c0ea4d6ce9cb2160a7fb7f39804871049f016e74782e5dade4d14109/pandas-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f8d68083e49e16b84734eb1a4dcae4259a75c90fb6e2251ab9a00b61120c06ab", size = 11409468, upload-time = "2026-03-31T06:46:45.2Z" }, + { url = "https://files.pythonhosted.org/packages/fa/5b/46e7c76032639f2132359b5cf4c785dd8cf9aea5ea64699eac752f02b9db/pandas-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:32cc41f310ebd4a296d93515fcac312216adfedb1894e879303987b8f1e2b97d", size = 11936381, upload-time = "2026-03-31T06:46:48.293Z" }, + { url = "https://files.pythonhosted.org/packages/7b/8b/721a9cff6fa6a91b162eb51019c6243b82b3226c71bb6c8ef4a9bd65cbc6/pandas-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:a4785e1d6547d8427c5208b748ae2efb64659a21bd82bf440d4262d02bfa02a4", size = 9744993, upload-time = "2026-03-31T06:46:51.488Z" }, + { url = "https://files.pythonhosted.org/packages/d5/18/7f0bd34ae27b28159aa80f2a6799f47fda34f7fb938a76e20c7b7fe3b200/pandas-3.0.2-cp312-cp312-win_arm64.whl", hash = "sha256:08504503f7101300107ecdc8df73658e4347586db5cfdadabc1592e9d7e7a0fd", size = 9056118, upload-time = "2026-03-31T06:46:54.548Z" }, + { url = "https://files.pythonhosted.org/packages/bf/ca/3e639a1ea6fcd0617ca4e8ca45f62a74de33a56ae6cd552735470b22c8d3/pandas-3.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b5918ba197c951dec132b0c5929a00c0bf05d5942f590d3c10a807f6e15a57d3", size = 10321105, upload-time = "2026-03-31T06:46:57.327Z" }, + { url = "https://files.pythonhosted.org/packages/0b/77/dbc82ff2fb0e63c6564356682bf201edff0ba16c98630d21a1fb312a8182/pandas-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d606a041c89c0a474a4702d532ab7e73a14fe35c8d427b972a625c8e46373668", size = 9864088, upload-time = "2026-03-31T06:46:59.935Z" }, + { url = "https://files.pythonhosted.org/packages/5c/2b/341f1b04bbca2e17e13cd3f08c215b70ef2c60c5356ef1e8c6857449edc7/pandas-3.0.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:710246ba0616e86891b58ab95f2495143bb2bc83ab6b06747c74216f583a6ac9", size = 10369066, upload-time = "2026-03-31T06:47:02.792Z" }, + { url = "https://files.pythonhosted.org/packages/12/c5/cbb1ffefb20a93d3f0e1fdcda699fb84976210d411b008f97f48bf6ce27e/pandas-3.0.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5d3cfe227c725b1f3dff4278b43d8c784656a42a9325b63af6b1492a8232209e", size = 10876780, upload-time = "2026-03-31T06:47:06.205Z" }, + { url = "https://files.pythonhosted.org/packages/98/fe/2249ae5e0a69bd0ddf17353d0a5d26611d70970111f5b3600cdc8be883e7/pandas-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c3b723df9087a9a9a840e263ebd9f88b64a12075d1bf2ea401a5a42f254f084d", size = 11375181, upload-time = "2026-03-31T06:47:09.383Z" }, + { url = "https://files.pythonhosted.org/packages/de/64/77a38b09e70b6464883b8d7584ab543e748e42c1b5d337a2ee088e0df741/pandas-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a3096110bf9eac0070b7208465f2740e2d8a670d5cb6530b5bb884eca495fd39", size = 11928899, upload-time = "2026-03-31T06:47:12.686Z" }, + { url = "https://files.pythonhosted.org/packages/5e/52/42855bf626868413f761addd574acc6195880ae247a5346477a4361c3acb/pandas-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:07a10f5c36512eead51bc578eb3354ad17578b22c013d89a796ab5eee90cd991", size = 9746574, upload-time = "2026-03-31T06:47:15.64Z" }, + { url = "https://files.pythonhosted.org/packages/88/39/21304ae06a25e8bf9fc820d69b29b2c495b2ae580d1e143146c309941760/pandas-3.0.2-cp313-cp313-win_arm64.whl", hash = "sha256:5fdbfa05931071aba28b408e59226186b01eb5e92bea2ab78b65863ca3228d84", size = 9047156, upload-time = "2026-03-31T06:47:18.595Z" }, + { url = "https://files.pythonhosted.org/packages/72/20/7defa8b27d4f330a903bb68eea33be07d839c5ea6bdda54174efcec0e1d2/pandas-3.0.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:dbc20dea3b9e27d0e66d74c42b2d0c1bed9c2ffe92adea33633e3bedeb5ac235", size = 10756238, upload-time = "2026-03-31T06:47:22.012Z" }, + { url = "https://files.pythonhosted.org/packages/e9/95/49433c14862c636afc0e9b2db83ff16b3ad92959364e52b2955e44c8e94c/pandas-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b75c347eff42497452116ce05ef461822d97ce5b9ff8df6edacb8076092c855d", size = 10408520, upload-time = "2026-03-31T06:47:25.197Z" }, + { url = "https://files.pythonhosted.org/packages/3b/f8/462ad2b5881d6b8ec8e5f7ed2ea1893faa02290d13870a1600fe72ad8efc/pandas-3.0.2-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1478075142e83a5571782ad007fb201ed074bdeac7ebcc8890c71442e96adf7", size = 10324154, upload-time = "2026-03-31T06:47:28.097Z" }, + { url = "https://files.pythonhosted.org/packages/0a/65/d1e69b649cbcddda23ad6e4c40ef935340f6f652a006e5cbc3555ac8adb3/pandas-3.0.2-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5880314e69e763d4c8b27937090de570f1fb8d027059a7ada3f7f8e98bdcb677", size = 10714449, upload-time = "2026-03-31T06:47:30.85Z" }, + { url = "https://files.pythonhosted.org/packages/47/a4/85b59bc65b8190ea3689882db6cdf32a5003c0ccd5a586c30fdcc3ffc4fc/pandas-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b5329e26898896f06035241a626d7c335daa479b9bbc82be7c2742d048e41172", size = 11338475, upload-time = "2026-03-31T06:47:34.026Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c4/bc6966c6e38e5d9478b935272d124d80a589511ed1612a5d21d36f664c68/pandas-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:81526c4afd31971f8b62671442a4b2b51e0aa9acc3819c9f0f12a28b6fcf85f1", size = 11786568, upload-time = "2026-03-31T06:47:36.941Z" }, + { url = "https://files.pythonhosted.org/packages/e8/74/09298ca9740beed1d3504e073d67e128aa07e5ca5ca2824b0c674c0b8676/pandas-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:7cadd7e9a44ec13b621aec60f9150e744cfc7a3dd32924a7e2f45edff31823b0", size = 10488652, upload-time = "2026-03-31T06:47:40.612Z" }, + { url = "https://files.pythonhosted.org/packages/bb/40/c6ea527147c73b24fc15c891c3fcffe9c019793119c5742b8784a062c7db/pandas-3.0.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:db0dbfd2a6cdf3770aa60464d50333d8f3d9165b2f2671bcc299b72de5a6677b", size = 10326084, upload-time = "2026-03-31T06:47:43.834Z" }, + { url = "https://files.pythonhosted.org/packages/95/25/bdb9326c3b5455f8d4d3549fce7abcf967259de146fe2cf7a82368141948/pandas-3.0.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0555c5882688a39317179ab4a0ed41d3ebc8812ab14c69364bbee8fb7a3f6288", size = 9914146, upload-time = "2026-03-31T06:47:46.67Z" }, + { url = "https://files.pythonhosted.org/packages/8d/77/3a227ff3337aa376c60d288e1d61c5d097131d0ac71f954d90a8f369e422/pandas-3.0.2-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:01f31a546acd5574ef77fe199bc90b55527c225c20ccda6601cf6b0fd5ed597c", size = 10444081, upload-time = "2026-03-31T06:47:49.681Z" }, + { url = "https://files.pythonhosted.org/packages/15/88/3cdd54fa279341afa10acf8d2b503556b1375245dccc9315659f795dd2e9/pandas-3.0.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:deeca1b5a931fdf0c2212c8a659ade6d3b1edc21f0914ce71ef24456ca7a6535", size = 10897535, upload-time = "2026-03-31T06:47:53.033Z" }, + { url = "https://files.pythonhosted.org/packages/06/9d/98cc7a7624f7932e40f434299260e2917b090a579d75937cb8a57b9d2de3/pandas-3.0.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0f48afd9bb13300ffb5a3316973324c787054ba6665cda0da3fbd67f451995db", size = 11446992, upload-time = "2026-03-31T06:47:56.193Z" }, + { url = "https://files.pythonhosted.org/packages/9a/cd/19ff605cc3760e80602e6826ddef2824d8e7050ed80f2e11c4b079741dc3/pandas-3.0.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6c4d8458b97a35717b62469a4ea0e85abd5ed8687277f5ccfc67f8a5126f8c53", size = 11968257, upload-time = "2026-03-31T06:47:59.137Z" }, + { url = "https://files.pythonhosted.org/packages/db/60/aba6a38de456e7341285102bede27514795c1eaa353bc0e7638b6b785356/pandas-3.0.2-cp314-cp314-win_amd64.whl", hash = "sha256:b35d14bb5d8285d9494fe93815a9e9307c0876e10f1e8e89ac5b88f728ec8dcf", size = 9865893, upload-time = "2026-03-31T06:48:02.038Z" }, + { url = "https://files.pythonhosted.org/packages/08/71/e5ec979dd2e8a093dacb8864598c0ff59a0cee0bbcdc0bfec16a51684d4f/pandas-3.0.2-cp314-cp314-win_arm64.whl", hash = "sha256:63d141b56ef686f7f0d714cfb8de4e320475b86bf4b620aa0b7da89af8cbdbbb", size = 9188644, upload-time = "2026-03-31T06:48:05.045Z" }, + { url = "https://files.pythonhosted.org/packages/f1/6c/7b45d85db19cae1eb524f2418ceaa9d85965dcf7b764ed151386b7c540f0/pandas-3.0.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:140f0cffb1fa2524e874dde5b477d9defe10780d8e9e220d259b2c0874c89d9d", size = 10776246, upload-time = "2026-03-31T06:48:07.789Z" }, + { url = "https://files.pythonhosted.org/packages/a8/3e/7b00648b086c106e81766f25322b48aa8dfa95b55e621dbdf2fdd413a117/pandas-3.0.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ae37e833ff4fed0ba352f6bdd8b73ba3ab3256a85e54edfd1ab51ae40cca0af8", size = 10424801, upload-time = "2026-03-31T06:48:10.897Z" }, + { url = "https://files.pythonhosted.org/packages/da/6e/558dd09a71b53b4008e7fc8a98ec6d447e9bfb63cdaeea10e5eb9b2dabe8/pandas-3.0.2-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4d888a5c678a419a5bb41a2a93818e8ed9fd3172246555c0b37b7cc27027effd", size = 10345643, upload-time = "2026-03-31T06:48:13.7Z" }, + { url = "https://files.pythonhosted.org/packages/be/e3/921c93b4d9a280409451dc8d07b062b503bbec0531d2627e73a756e99a82/pandas-3.0.2-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b444dc64c079e84df91baa8bf613d58405645461cabca929d9178f2cd392398d", size = 10743641, upload-time = "2026-03-31T06:48:16.659Z" }, + { url = "https://files.pythonhosted.org/packages/56/ca/fd17286f24fa3b4d067965d8d5d7e14fe557dd4f979a0b068ac0deaf8228/pandas-3.0.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4544c7a54920de8eeacaa1466a6b7268ecfbc9bc64ab4dbb89c6bbe94d5e0660", size = 11361993, upload-time = "2026-03-31T06:48:19.475Z" }, + { url = "https://files.pythonhosted.org/packages/e4/a5/2f6ed612056819de445a433ca1f2821ac3dab7f150d569a59e9cc105de1d/pandas-3.0.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:734be7551687c00fbd760dc0522ed974f82ad230d4a10f54bf51b80d44a08702", size = 11815274, upload-time = "2026-03-31T06:48:22.695Z" }, + { url = "https://files.pythonhosted.org/packages/00/2f/b622683e99ec3ce00b0854bac9e80868592c5b051733f2cf3a868e5fea26/pandas-3.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:57a07209bebcbcf768d2d13c9b78b852f9a15978dac41b9e6421a81ad4cdd276", size = 10888530, upload-time = "2026-03-31T06:48:25.806Z" }, + { url = "https://files.pythonhosted.org/packages/cb/2b/f8434233fab2bd66a02ec014febe4e5adced20e2693e0e90a07d118ed30e/pandas-3.0.2-cp314-cp314t-win_arm64.whl", hash = "sha256:5371b72c2d4d415d08765f32d689217a43227484e81b2305b52076e328f6f482", size = 9455341, upload-time = "2026-03-31T06:48:28.418Z" }, +] + +[[package]] +name = "pysocks" +version = "1.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/11/293dd436aea955d45fc4e8a35b6ae7270f5b8e00b53cf6c024c83b657a11/PySocks-1.7.1.tar.gz", hash = "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0", size = 284429, upload-time = "2019-09-20T02:07:35.714Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/59/b4572118e098ac8e46e399a1dd0f2d85403ce8bbaad9ec79373ed6badaf9/PySocks-1.7.1-py3-none-any.whl", hash = "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5", size = 16725, upload-time = "2019-09-20T02:06:22.938Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "pytz" +version = "2026.1.post1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/56/db/b8721d71d945e6a8ac63c0fc900b2067181dbb50805958d4d4661cf7d277/pytz-2026.1.post1.tar.gz", hash = "sha256:3378dde6a0c3d26719182142c56e60c7f9af7e968076f31aae569d72a0358ee1", size = 321088, upload-time = "2026-03-03T07:47:50.683Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/99/781fe0c827be2742bcc775efefccb3b048a3a9c6ce9aec0cbf4a101677e5/pytz-2026.1.post1-py2.py3-none-any.whl", hash = "sha256:f2fd16142fda348286a75e1a524be810bb05d444e5a081f37f7affc635035f7a", size = 510489, upload-time = "2026-03-03T07:47:49.167Z" }, +] + +[[package]] +name = "requests" +version = "2.32.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +dependencies = [ + { name = "certifi", marker = "python_full_version < '3.9'" }, + { name = "charset-normalizer", marker = "python_full_version < '3.9'" }, + { name = "idna", marker = "python_full_version < '3.9'" }, + { name = "urllib3", version = "2.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258, upload-time = "2025-06-09T16:43:07.34Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" }, +] + +[package.optional-dependencies] +socks = [ + { name = "pysocks", marker = "python_full_version < '3.9'" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.9.*'", +] +dependencies = [ + { name = "certifi", marker = "python_full_version == '3.9.*'" }, + { name = "charset-normalizer", marker = "python_full_version == '3.9.*'" }, + { name = "idna", marker = "python_full_version == '3.9.*'" }, + { name = "urllib3", version = "2.6.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[package.optional-dependencies] +socks = [ + { name = "pysocks", marker = "python_full_version == '3.9.*'" }, +] + +[[package]] +name = "requests" +version = "2.33.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "certifi", marker = "python_full_version >= '3.10'" }, + { name = "charset-normalizer", marker = "python_full_version >= '3.10'" }, + { name = "idna", marker = "python_full_version >= '3.10'" }, + { name = "urllib3", version = "2.6.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5f/a4/98b9c7c6428a668bf7e42ebb7c79d576a1c3c1e3ae2d47e674b468388871/requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517", size = 134120, upload-time = "2026-03-30T16:09:15.531Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a", size = 64947, upload-time = "2026-03-30T16:09:13.83Z" }, +] + +[package.optional-dependencies] +socks = [ + { name = "pysocks", marker = "python_full_version >= '3.10'" }, +] + +[[package]] +name = "scipy" +version = "1.10.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +dependencies = [ + { name = "numpy", version = "1.24.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/84/a9/2bf119f3f9cff1f376f924e39cfae18dec92a1514784046d185731301281/scipy-1.10.1.tar.gz", hash = "sha256:2cf9dfb80a7b4589ba4c40ce7588986d6d5cebc5457cad2c2880f6bc2d42f3a5", size = 42407997, upload-time = "2023-02-19T21:20:13.395Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/ac/b1f1bbf7b01d96495f35be003b881f10f85bf6559efb6e9578da832c2140/scipy-1.10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e7354fd7527a4b0377ce55f286805b34e8c54b91be865bac273f527e1b839019", size = 35093243, upload-time = "2023-02-19T20:33:55.754Z" }, + { url = "https://files.pythonhosted.org/packages/ea/e5/452086ebed676ce4000ceb5eeeb0ee4f8c6f67c7e70fb9323a370ff95c1f/scipy-1.10.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:4b3f429188c66603a1a5c549fb414e4d3bdc2a24792e061ffbd607d3d75fd84e", size = 28772969, upload-time = "2023-02-19T20:34:39.318Z" }, + { url = "https://files.pythonhosted.org/packages/04/0b/a1b119c869b79a2ab459b7f9fd7e2dea75a9c7d432e64e915e75586bd00b/scipy-1.10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1553b5dcddd64ba9a0d95355e63fe6c3fc303a8fd77c7bc91e77d61363f7433f", size = 30886961, upload-time = "2023-02-19T20:35:33.724Z" }, + { url = "https://files.pythonhosted.org/packages/1f/4b/3bacad9a166350cb2e518cea80ab891016933cc1653f15c90279512c5fa9/scipy-1.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c0ff64b06b10e35215abce517252b375e580a6125fd5fdf6421b98efbefb2d2", size = 34422544, upload-time = "2023-02-19T20:37:03.859Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e3/b06ac3738bf365e89710205a471abe7dceec672a51c244b469bc5d1291c7/scipy-1.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:fae8a7b898c42dffe3f7361c40d5952b6bf32d10c4569098d276b4c547905ee1", size = 42484848, upload-time = "2023-02-19T20:39:09.467Z" }, + { url = "https://files.pythonhosted.org/packages/e7/53/053cd3669be0d474deae8fe5f757bff4c4f480b8a410231e0631c068873d/scipy-1.10.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0f1564ea217e82c1bbe75ddf7285ba0709ecd503f048cb1236ae9995f64217bd", size = 35003170, upload-time = "2023-02-19T20:40:53.274Z" }, + { url = "https://files.pythonhosted.org/packages/0d/3e/d05b9de83677195886fb79844fcca19609a538db63b1790fa373155bc3cf/scipy-1.10.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:d925fa1c81b772882aa55bcc10bf88324dadb66ff85d548c71515f6689c6dac5", size = 28717513, upload-time = "2023-02-19T20:42:20.82Z" }, + { url = "https://files.pythonhosted.org/packages/a5/3d/b69746c50e44893da57a68457da3d7e5bb75f6a37fbace3769b70d017488/scipy-1.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaea0a6be54462ec027de54fca511540980d1e9eea68b2d5c1dbfe084797be35", size = 30687257, upload-time = "2023-02-19T20:43:48.139Z" }, + { url = "https://files.pythonhosted.org/packages/21/cd/fe2d4af234b80dc08c911ce63fdaee5badcdde3e9bcd9a68884580652ef0/scipy-1.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15a35c4242ec5f292c3dd364a7c71a61be87a3d4ddcc693372813c0b73c9af1d", size = 34124096, upload-time = "2023-02-19T20:45:27.415Z" }, + { url = "https://files.pythonhosted.org/packages/65/76/903324159e4a3566e518c558aeb21571d642f781d842d8dd0fd9c6b0645a/scipy-1.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:43b8e0bcb877faf0abfb613d51026cd5cc78918e9530e375727bf0625c82788f", size = 42238704, upload-time = "2023-02-19T20:47:26.366Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e3/37508a11dae501349d7c16e4dd18c706a023629eedc650ee094593887a89/scipy-1.10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5678f88c68ea866ed9ebe3a989091088553ba12c6090244fdae3e467b1139c35", size = 35041063, upload-time = "2023-02-19T20:49:02.296Z" }, + { url = "https://files.pythonhosted.org/packages/93/4a/50c436de1353cce8b66b26e49a687f10b91fe7465bf34e4565d810153003/scipy-1.10.1-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:39becb03541f9e58243f4197584286e339029e8908c46f7221abeea4b749fa88", size = 28797694, upload-time = "2023-02-19T20:50:19.381Z" }, + { url = "https://files.pythonhosted.org/packages/d2/b5/ff61b79ad0ebd15d87ade10e0f4e80114dd89fac34a5efade39e99048c91/scipy-1.10.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bce5869c8d68cf383ce240e44c1d9ae7c06078a9396df68ce88a1230f93a30c1", size = 31024657, upload-time = "2023-02-19T20:51:49.175Z" }, + { url = "https://files.pythonhosted.org/packages/69/f0/fb07a9548e48b687b8bf2fa81d71aba9cfc548d365046ca1c791e24db99d/scipy-1.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07c3457ce0b3ad5124f98a86533106b643dd811dd61b548e78cf4c8786652f6f", size = 34540352, upload-time = "2023-02-19T20:53:30.821Z" }, + { url = "https://files.pythonhosted.org/packages/32/8e/7f403535ddf826348c9b8417791e28712019962f7e90ff845896d6325d09/scipy-1.10.1-cp38-cp38-win_amd64.whl", hash = "sha256:049a8bbf0ad95277ffba9b3b7d23e5369cc39e66406d60422c8cfef40ccc8415", size = 42215036, upload-time = "2023-02-19T20:55:09.639Z" }, + { url = "https://files.pythonhosted.org/packages/d9/7d/78b8035bc93c869b9f17261c87aae97a9cdb937f65f0d453c2831aa172fc/scipy-1.10.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cd9f1027ff30d90618914a64ca9b1a77a431159df0e2a195d8a9e8a04c78abf9", size = 35158611, upload-time = "2023-02-19T20:56:02.715Z" }, + { url = "https://files.pythonhosted.org/packages/e7/f0/55d81813b1a4cb79ce7dc8290eac083bf38bfb36e1ada94ea13b7b1a5f79/scipy-1.10.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:79c8e5a6c6ffaf3a2262ef1be1e108a035cf4f05c14df56057b64acc5bebffb6", size = 28902591, upload-time = "2023-02-19T20:56:45.728Z" }, + { url = "https://files.pythonhosted.org/packages/77/d1/722c457b319eed1d642e0a14c9be37eb475f0e6ed1f3401fa480d5d6d36e/scipy-1.10.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51af417a000d2dbe1ec6c372dfe688e041a7084da4fdd350aeb139bd3fb55353", size = 30960654, upload-time = "2023-02-19T20:57:32.091Z" }, + { url = "https://files.pythonhosted.org/packages/5d/30/b2a2a5bf1a3beefb7609fb871dcc6aef7217c69cef19a4631b7ab5622a8a/scipy-1.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b4735d6c28aad3cdcf52117e0e91d6b39acd4272f3f5cd9907c24ee931ad601", size = 34458863, upload-time = "2023-02-19T20:58:23.601Z" }, + { url = "https://files.pythonhosted.org/packages/35/20/0ec6246bbb43d18650c9a7cad6602e1a84fd8f9564a9b84cc5faf1e037d0/scipy-1.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:7ff7f37b1bf4417baca958d254e8e2875d0cc23aaadbe65b3d5b3077b0eb23ea", size = 42509516, upload-time = "2023-02-19T20:59:26.296Z" }, +] + +[[package]] +name = "scipy" +version = "1.13.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.9.*'", +] +dependencies = [ + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/00/48c2f661e2816ccf2ecd77982f6605b2950afe60f60a52b4cbbc2504aa8f/scipy-1.13.1.tar.gz", hash = "sha256:095a87a0312b08dfd6a6155cbbd310a8c51800fc931b8c0b84003014b874ed3c", size = 57210720, upload-time = "2024-05-23T03:29:26.079Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/59/41b2529908c002ade869623b87eecff3e11e3ce62e996d0bdcb536984187/scipy-1.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:20335853b85e9a49ff7572ab453794298bcf0354d8068c5f6775a0eabf350aca", size = 39328076, upload-time = "2024-05-23T03:19:01.687Z" }, + { url = "https://files.pythonhosted.org/packages/d5/33/f1307601f492f764062ce7dd471a14750f3360e33cd0f8c614dae208492c/scipy-1.13.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:d605e9c23906d1994f55ace80e0125c587f96c020037ea6aa98d01b4bd2e222f", size = 30306232, upload-time = "2024-05-23T03:19:09.089Z" }, + { url = "https://files.pythonhosted.org/packages/c0/66/9cd4f501dd5ea03e4a4572ecd874936d0da296bd04d1c45ae1a4a75d9c3a/scipy-1.13.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfa31f1def5c819b19ecc3a8b52d28ffdcc7ed52bb20c9a7589669dd3c250989", size = 33743202, upload-time = "2024-05-23T03:19:15.138Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ba/7255e5dc82a65adbe83771c72f384d99c43063648456796436c9a5585ec3/scipy-1.13.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26264b282b9da0952a024ae34710c2aff7d27480ee91a2e82b7b7073c24722f", size = 38577335, upload-time = "2024-05-23T03:19:21.984Z" }, + { url = "https://files.pythonhosted.org/packages/49/a5/bb9ded8326e9f0cdfdc412eeda1054b914dfea952bda2097d174f8832cc0/scipy-1.13.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:eccfa1906eacc02de42d70ef4aecea45415f5be17e72b61bafcfd329bdc52e94", size = 38820728, upload-time = "2024-05-23T03:19:28.225Z" }, + { url = "https://files.pythonhosted.org/packages/12/30/df7a8fcc08f9b4a83f5f27cfaaa7d43f9a2d2ad0b6562cced433e5b04e31/scipy-1.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:2831f0dc9c5ea9edd6e51e6e769b655f08ec6db6e2e10f86ef39bd32eb11da54", size = 46210588, upload-time = "2024-05-23T03:19:35.661Z" }, + { url = "https://files.pythonhosted.org/packages/b4/15/4a4bb1b15bbd2cd2786c4f46e76b871b28799b67891f23f455323a0cdcfb/scipy-1.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:27e52b09c0d3a1d5b63e1105f24177e544a222b43611aaf5bc44d4a0979e32f9", size = 39333805, upload-time = "2024-05-23T03:19:43.081Z" }, + { url = "https://files.pythonhosted.org/packages/ba/92/42476de1af309c27710004f5cdebc27bec62c204db42e05b23a302cb0c9a/scipy-1.13.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:54f430b00f0133e2224c3ba42b805bfd0086fe488835effa33fa291561932326", size = 30317687, upload-time = "2024-05-23T03:19:48.799Z" }, + { url = "https://files.pythonhosted.org/packages/80/ba/8be64fe225360a4beb6840f3cbee494c107c0887f33350d0a47d55400b01/scipy-1.13.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e89369d27f9e7b0884ae559a3a956e77c02114cc60a6058b4e5011572eea9299", size = 33694638, upload-time = "2024-05-23T03:19:55.104Z" }, + { url = "https://files.pythonhosted.org/packages/36/07/035d22ff9795129c5a847c64cb43c1fa9188826b59344fee28a3ab02e283/scipy-1.13.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a78b4b3345f1b6f68a763c6e25c0c9a23a9fd0f39f5f3d200efe8feda560a5fa", size = 38569931, upload-time = "2024-05-23T03:20:01.82Z" }, + { url = "https://files.pythonhosted.org/packages/d9/10/f9b43de37e5ed91facc0cfff31d45ed0104f359e4f9a68416cbf4e790241/scipy-1.13.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:45484bee6d65633752c490404513b9ef02475b4284c4cfab0ef946def50b3f59", size = 38838145, upload-time = "2024-05-23T03:20:09.173Z" }, + { url = "https://files.pythonhosted.org/packages/4a/48/4513a1a5623a23e95f94abd675ed91cfb19989c58e9f6f7d03990f6caf3d/scipy-1.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:5713f62f781eebd8d597eb3f88b8bf9274e79eeabf63afb4a737abc6c84ad37b", size = 46196227, upload-time = "2024-05-23T03:20:16.433Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7b/fb6b46fbee30fc7051913068758414f2721003a89dd9a707ad49174e3843/scipy-1.13.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5d72782f39716b2b3509cd7c33cdc08c96f2f4d2b06d51e52fb45a19ca0c86a1", size = 39357301, upload-time = "2024-05-23T03:20:23.538Z" }, + { url = "https://files.pythonhosted.org/packages/dc/5a/2043a3bde1443d94014aaa41e0b50c39d046dda8360abd3b2a1d3f79907d/scipy-1.13.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:017367484ce5498445aade74b1d5ab377acdc65e27095155e448c88497755a5d", size = 30363348, upload-time = "2024-05-23T03:20:29.885Z" }, + { url = "https://files.pythonhosted.org/packages/e7/cb/26e4a47364bbfdb3b7fb3363be6d8a1c543bcd70a7753ab397350f5f189a/scipy-1.13.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:949ae67db5fa78a86e8fa644b9a6b07252f449dcf74247108c50e1d20d2b4627", size = 33406062, upload-time = "2024-05-23T03:20:36.012Z" }, + { url = "https://files.pythonhosted.org/packages/88/ab/6ecdc526d509d33814835447bbbeedbebdec7cca46ef495a61b00a35b4bf/scipy-1.13.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de3ade0e53bc1f21358aa74ff4830235d716211d7d077e340c7349bc3542e884", size = 38218311, upload-time = "2024-05-23T03:20:42.086Z" }, + { url = "https://files.pythonhosted.org/packages/0b/00/9f54554f0f8318100a71515122d8f4f503b1a2c4b4cfab3b4b68c0eb08fa/scipy-1.13.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2ac65fb503dad64218c228e2dc2d0a0193f7904747db43014645ae139c8fad16", size = 38442493, upload-time = "2024-05-23T03:20:48.292Z" }, + { url = "https://files.pythonhosted.org/packages/3e/df/963384e90733e08eac978cd103c34df181d1fec424de383cdc443f418dd4/scipy-1.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:cdd7dacfb95fea358916410ec61bbc20440f7860333aee6d882bb8046264e949", size = 45910955, upload-time = "2024-05-23T03:20:55.091Z" }, + { url = "https://files.pythonhosted.org/packages/7f/29/c2ea58c9731b9ecb30b6738113a95d147e83922986b34c685b8f6eefde21/scipy-1.13.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:436bbb42a94a8aeef855d755ce5a465479c721e9d684de76bf61a62e7c2b81d5", size = 39352927, upload-time = "2024-05-23T03:21:01.95Z" }, + { url = "https://files.pythonhosted.org/packages/5c/c0/e71b94b20ccf9effb38d7147c0064c08c622309fd487b1b677771a97d18c/scipy-1.13.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:8335549ebbca860c52bf3d02f80784e91a004b71b059e3eea9678ba994796a24", size = 30324538, upload-time = "2024-05-23T03:21:07.634Z" }, + { url = "https://files.pythonhosted.org/packages/6d/0f/aaa55b06d474817cea311e7b10aab2ea1fd5d43bc6a2861ccc9caec9f418/scipy-1.13.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d533654b7d221a6a97304ab63c41c96473ff04459e404b83275b60aa8f4b7004", size = 33732190, upload-time = "2024-05-23T03:21:14.41Z" }, + { url = "https://files.pythonhosted.org/packages/35/f5/d0ad1a96f80962ba65e2ce1de6a1e59edecd1f0a7b55990ed208848012e0/scipy-1.13.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:637e98dcf185ba7f8e663e122ebf908c4702420477ae52a04f9908707456ba4d", size = 38612244, upload-time = "2024-05-23T03:21:21.827Z" }, + { url = "https://files.pythonhosted.org/packages/8d/02/1165905f14962174e6569076bcc3315809ae1291ed14de6448cc151eedfd/scipy-1.13.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a014c2b3697bde71724244f63de2476925596c24285c7a637364761f8710891c", size = 38845637, upload-time = "2024-05-23T03:21:28.729Z" }, + { url = "https://files.pythonhosted.org/packages/3e/77/dab54fe647a08ee4253963bcd8f9cf17509c8ca64d6335141422fe2e2114/scipy-1.13.1-cp39-cp39-win_amd64.whl", hash = "sha256:392e4ec766654852c25ebad4f64e4e584cf19820b980bc04960bca0b0cd6eaa2", size = 46227440, upload-time = "2024-05-23T03:21:35.888Z" }, +] + +[[package]] +name = "scipy" +version = "1.15.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/37/6964b830433e654ec7485e45a00fc9a27cf868d622838f6b6d9c5ec0d532/scipy-1.15.3.tar.gz", hash = "sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf", size = 59419214, upload-time = "2025-05-08T16:13:05.955Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/2f/4966032c5f8cc7e6a60f1b2e0ad686293b9474b65246b0c642e3ef3badd0/scipy-1.15.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:a345928c86d535060c9c2b25e71e87c39ab2f22fc96e9636bd74d1dbf9de448c", size = 38702770, upload-time = "2025-05-08T16:04:20.849Z" }, + { url = "https://files.pythonhosted.org/packages/a0/6e/0c3bf90fae0e910c274db43304ebe25a6b391327f3f10b5dcc638c090795/scipy-1.15.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:ad3432cb0f9ed87477a8d97f03b763fd1d57709f1bbde3c9369b1dff5503b253", size = 30094511, upload-time = "2025-05-08T16:04:27.103Z" }, + { url = "https://files.pythonhosted.org/packages/ea/b1/4deb37252311c1acff7f101f6453f0440794f51b6eacb1aad4459a134081/scipy-1.15.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:aef683a9ae6eb00728a542b796f52a5477b78252edede72b8327a886ab63293f", size = 22368151, upload-time = "2025-05-08T16:04:31.731Z" }, + { url = "https://files.pythonhosted.org/packages/38/7d/f457626e3cd3c29b3a49ca115a304cebb8cc6f31b04678f03b216899d3c6/scipy-1.15.3-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:1c832e1bd78dea67d5c16f786681b28dd695a8cb1fb90af2e27580d3d0967e92", size = 25121732, upload-time = "2025-05-08T16:04:36.596Z" }, + { url = "https://files.pythonhosted.org/packages/db/0a/92b1de4a7adc7a15dcf5bddc6e191f6f29ee663b30511ce20467ef9b82e4/scipy-1.15.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:263961f658ce2165bbd7b99fa5135195c3a12d9bef045345016b8b50c315cb82", size = 35547617, upload-time = "2025-05-08T16:04:43.546Z" }, + { url = "https://files.pythonhosted.org/packages/8e/6d/41991e503e51fc1134502694c5fa7a1671501a17ffa12716a4a9151af3df/scipy-1.15.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e2abc762b0811e09a0d3258abee2d98e0c703eee49464ce0069590846f31d40", size = 37662964, upload-time = "2025-05-08T16:04:49.431Z" }, + { url = "https://files.pythonhosted.org/packages/25/e1/3df8f83cb15f3500478c889be8fb18700813b95e9e087328230b98d547ff/scipy-1.15.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ed7284b21a7a0c8f1b6e5977ac05396c0d008b89e05498c8b7e8f4a1423bba0e", size = 37238749, upload-time = "2025-05-08T16:04:55.215Z" }, + { url = "https://files.pythonhosted.org/packages/93/3e/b3257cf446f2a3533ed7809757039016b74cd6f38271de91682aa844cfc5/scipy-1.15.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5380741e53df2c566f4d234b100a484b420af85deb39ea35a1cc1be84ff53a5c", size = 40022383, upload-time = "2025-05-08T16:05:01.914Z" }, + { url = "https://files.pythonhosted.org/packages/d1/84/55bc4881973d3f79b479a5a2e2df61c8c9a04fcb986a213ac9c02cfb659b/scipy-1.15.3-cp310-cp310-win_amd64.whl", hash = "sha256:9d61e97b186a57350f6d6fd72640f9e99d5a4a2b8fbf4b9ee9a841eab327dc13", size = 41259201, upload-time = "2025-05-08T16:05:08.166Z" }, + { url = "https://files.pythonhosted.org/packages/96/ab/5cc9f80f28f6a7dff646c5756e559823614a42b1939d86dd0ed550470210/scipy-1.15.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:993439ce220d25e3696d1b23b233dd010169b62f6456488567e830654ee37a6b", size = 38714255, upload-time = "2025-05-08T16:05:14.596Z" }, + { url = "https://files.pythonhosted.org/packages/4a/4a/66ba30abe5ad1a3ad15bfb0b59d22174012e8056ff448cb1644deccbfed2/scipy-1.15.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:34716e281f181a02341ddeaad584205bd2fd3c242063bd3423d61ac259ca7eba", size = 30111035, upload-time = "2025-05-08T16:05:20.152Z" }, + { url = "https://files.pythonhosted.org/packages/4b/fa/a7e5b95afd80d24313307f03624acc65801846fa75599034f8ceb9e2cbf6/scipy-1.15.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3b0334816afb8b91dab859281b1b9786934392aa3d527cd847e41bb6f45bee65", size = 22384499, upload-time = "2025-05-08T16:05:24.494Z" }, + { url = "https://files.pythonhosted.org/packages/17/99/f3aaddccf3588bb4aea70ba35328c204cadd89517a1612ecfda5b2dd9d7a/scipy-1.15.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:6db907c7368e3092e24919b5e31c76998b0ce1684d51a90943cb0ed1b4ffd6c1", size = 25152602, upload-time = "2025-05-08T16:05:29.313Z" }, + { url = "https://files.pythonhosted.org/packages/56/c5/1032cdb565f146109212153339f9cb8b993701e9fe56b1c97699eee12586/scipy-1.15.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:721d6b4ef5dc82ca8968c25b111e307083d7ca9091bc38163fb89243e85e3889", size = 35503415, upload-time = "2025-05-08T16:05:34.699Z" }, + { url = "https://files.pythonhosted.org/packages/bd/37/89f19c8c05505d0601ed5650156e50eb881ae3918786c8fd7262b4ee66d3/scipy-1.15.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39cb9c62e471b1bb3750066ecc3a3f3052b37751c7c3dfd0fd7e48900ed52982", size = 37652622, upload-time = "2025-05-08T16:05:40.762Z" }, + { url = "https://files.pythonhosted.org/packages/7e/31/be59513aa9695519b18e1851bb9e487de66f2d31f835201f1b42f5d4d475/scipy-1.15.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:795c46999bae845966368a3c013e0e00947932d68e235702b5c3f6ea799aa8c9", size = 37244796, upload-time = "2025-05-08T16:05:48.119Z" }, + { url = "https://files.pythonhosted.org/packages/10/c0/4f5f3eeccc235632aab79b27a74a9130c6c35df358129f7ac8b29f562ac7/scipy-1.15.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:18aaacb735ab38b38db42cb01f6b92a2d0d4b6aabefeb07f02849e47f8fb3594", size = 40047684, upload-time = "2025-05-08T16:05:54.22Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a7/0ddaf514ce8a8714f6ed243a2b391b41dbb65251affe21ee3077ec45ea9a/scipy-1.15.3-cp311-cp311-win_amd64.whl", hash = "sha256:ae48a786a28412d744c62fd7816a4118ef97e5be0bee968ce8f0a2fba7acf3bb", size = 41246504, upload-time = "2025-05-08T16:06:00.437Z" }, + { url = "https://files.pythonhosted.org/packages/37/4b/683aa044c4162e10ed7a7ea30527f2cbd92e6999c10a8ed8edb253836e9c/scipy-1.15.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6ac6310fdbfb7aa6612408bd2f07295bcbd3fda00d2d702178434751fe48e019", size = 38766735, upload-time = "2025-05-08T16:06:06.471Z" }, + { url = "https://files.pythonhosted.org/packages/7b/7e/f30be3d03de07f25dc0ec926d1681fed5c732d759ac8f51079708c79e680/scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:185cd3d6d05ca4b44a8f1595af87f9c372bb6acf9c808e99aa3e9aa03bd98cf6", size = 30173284, upload-time = "2025-05-08T16:06:11.686Z" }, + { url = "https://files.pythonhosted.org/packages/07/9c/0ddb0d0abdabe0d181c1793db51f02cd59e4901da6f9f7848e1f96759f0d/scipy-1.15.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:05dc6abcd105e1a29f95eada46d4a3f251743cfd7d3ae8ddb4088047f24ea477", size = 22446958, upload-time = "2025-05-08T16:06:15.97Z" }, + { url = "https://files.pythonhosted.org/packages/af/43/0bce905a965f36c58ff80d8bea33f1f9351b05fad4beaad4eae34699b7a1/scipy-1.15.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:06efcba926324df1696931a57a176c80848ccd67ce6ad020c810736bfd58eb1c", size = 25242454, upload-time = "2025-05-08T16:06:20.394Z" }, + { url = "https://files.pythonhosted.org/packages/56/30/a6f08f84ee5b7b28b4c597aca4cbe545535c39fe911845a96414700b64ba/scipy-1.15.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05045d8b9bfd807ee1b9f38761993297b10b245f012b11b13b91ba8945f7e45", size = 35210199, upload-time = "2025-05-08T16:06:26.159Z" }, + { url = "https://files.pythonhosted.org/packages/0b/1f/03f52c282437a168ee2c7c14a1a0d0781a9a4a8962d84ac05c06b4c5b555/scipy-1.15.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271e3713e645149ea5ea3e97b57fdab61ce61333f97cfae392c28ba786f9bb49", size = 37309455, upload-time = "2025-05-08T16:06:32.778Z" }, + { url = "https://files.pythonhosted.org/packages/89/b1/fbb53137f42c4bf630b1ffdfc2151a62d1d1b903b249f030d2b1c0280af8/scipy-1.15.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6cfd56fc1a8e53f6e89ba3a7a7251f7396412d655bca2aa5611c8ec9a6784a1e", size = 36885140, upload-time = "2025-05-08T16:06:39.249Z" }, + { url = "https://files.pythonhosted.org/packages/2e/2e/025e39e339f5090df1ff266d021892694dbb7e63568edcfe43f892fa381d/scipy-1.15.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ff17c0bb1cb32952c09217d8d1eed9b53d1463e5f1dd6052c7857f83127d539", size = 39710549, upload-time = "2025-05-08T16:06:45.729Z" }, + { url = "https://files.pythonhosted.org/packages/e6/eb/3bf6ea8ab7f1503dca3a10df2e4b9c3f6b3316df07f6c0ded94b281c7101/scipy-1.15.3-cp312-cp312-win_amd64.whl", hash = "sha256:52092bc0472cfd17df49ff17e70624345efece4e1a12b23783a1ac59a1b728ed", size = 40966184, upload-time = "2025-05-08T16:06:52.623Z" }, + { url = "https://files.pythonhosted.org/packages/73/18/ec27848c9baae6e0d6573eda6e01a602e5649ee72c27c3a8aad673ebecfd/scipy-1.15.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c620736bcc334782e24d173c0fdbb7590a0a436d2fdf39310a8902505008759", size = 38728256, upload-time = "2025-05-08T16:06:58.696Z" }, + { url = "https://files.pythonhosted.org/packages/74/cd/1aef2184948728b4b6e21267d53b3339762c285a46a274ebb7863c9e4742/scipy-1.15.3-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:7e11270a000969409d37ed399585ee530b9ef6aa99d50c019de4cb01e8e54e62", size = 30109540, upload-time = "2025-05-08T16:07:04.209Z" }, + { url = "https://files.pythonhosted.org/packages/5b/d8/59e452c0a255ec352bd0a833537a3bc1bfb679944c4938ab375b0a6b3a3e/scipy-1.15.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8c9ed3ba2c8a2ce098163a9bdb26f891746d02136995df25227a20e71c396ebb", size = 22383115, upload-time = "2025-05-08T16:07:08.998Z" }, + { url = "https://files.pythonhosted.org/packages/08/f5/456f56bbbfccf696263b47095291040655e3cbaf05d063bdc7c7517f32ac/scipy-1.15.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0bdd905264c0c9cfa74a4772cdb2070171790381a5c4d312c973382fc6eaf730", size = 25163884, upload-time = "2025-05-08T16:07:14.091Z" }, + { url = "https://files.pythonhosted.org/packages/a2/66/a9618b6a435a0f0c0b8a6d0a2efb32d4ec5a85f023c2b79d39512040355b/scipy-1.15.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79167bba085c31f38603e11a267d862957cbb3ce018d8b38f79ac043bc92d825", size = 35174018, upload-time = "2025-05-08T16:07:19.427Z" }, + { url = "https://files.pythonhosted.org/packages/b5/09/c5b6734a50ad4882432b6bb7c02baf757f5b2f256041da5df242e2d7e6b6/scipy-1.15.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9deabd6d547aee2c9a81dee6cc96c6d7e9a9b1953f74850c179f91fdc729cb7", size = 37269716, upload-time = "2025-05-08T16:07:25.712Z" }, + { url = "https://files.pythonhosted.org/packages/77/0a/eac00ff741f23bcabd352731ed9b8995a0a60ef57f5fd788d611d43d69a1/scipy-1.15.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dde4fc32993071ac0c7dd2d82569e544f0bdaff66269cb475e0f369adad13f11", size = 36872342, upload-time = "2025-05-08T16:07:31.468Z" }, + { url = "https://files.pythonhosted.org/packages/fe/54/4379be86dd74b6ad81551689107360d9a3e18f24d20767a2d5b9253a3f0a/scipy-1.15.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f77f853d584e72e874d87357ad70f44b437331507d1c311457bed8ed2b956126", size = 39670869, upload-time = "2025-05-08T16:07:38.002Z" }, + { url = "https://files.pythonhosted.org/packages/87/2e/892ad2862ba54f084ffe8cc4a22667eaf9c2bcec6d2bff1d15713c6c0703/scipy-1.15.3-cp313-cp313-win_amd64.whl", hash = "sha256:b90ab29d0c37ec9bf55424c064312930ca5f4bde15ee8619ee44e69319aab163", size = 40988851, upload-time = "2025-05-08T16:08:33.671Z" }, + { url = "https://files.pythonhosted.org/packages/1b/e9/7a879c137f7e55b30d75d90ce3eb468197646bc7b443ac036ae3fe109055/scipy-1.15.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3ac07623267feb3ae308487c260ac684b32ea35fd81e12845039952f558047b8", size = 38863011, upload-time = "2025-05-08T16:07:44.039Z" }, + { url = "https://files.pythonhosted.org/packages/51/d1/226a806bbd69f62ce5ef5f3ffadc35286e9fbc802f606a07eb83bf2359de/scipy-1.15.3-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6487aa99c2a3d509a5227d9a5e889ff05830a06b2ce08ec30df6d79db5fcd5c5", size = 30266407, upload-time = "2025-05-08T16:07:49.891Z" }, + { url = "https://files.pythonhosted.org/packages/e5/9b/f32d1d6093ab9eeabbd839b0f7619c62e46cc4b7b6dbf05b6e615bbd4400/scipy-1.15.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:50f9e62461c95d933d5c5ef4a1f2ebf9a2b4e83b0db374cb3f1de104d935922e", size = 22540030, upload-time = "2025-05-08T16:07:54.121Z" }, + { url = "https://files.pythonhosted.org/packages/e7/29/c278f699b095c1a884f29fda126340fcc201461ee8bfea5c8bdb1c7c958b/scipy-1.15.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:14ed70039d182f411ffc74789a16df3835e05dc469b898233a245cdfd7f162cb", size = 25218709, upload-time = "2025-05-08T16:07:58.506Z" }, + { url = "https://files.pythonhosted.org/packages/24/18/9e5374b617aba742a990581373cd6b68a2945d65cc588482749ef2e64467/scipy-1.15.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a769105537aa07a69468a0eefcd121be52006db61cdd8cac8a0e68980bbb723", size = 34809045, upload-time = "2025-05-08T16:08:03.929Z" }, + { url = "https://files.pythonhosted.org/packages/e1/fe/9c4361e7ba2927074360856db6135ef4904d505e9b3afbbcb073c4008328/scipy-1.15.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9db984639887e3dffb3928d118145ffe40eff2fa40cb241a306ec57c219ebbbb", size = 36703062, upload-time = "2025-05-08T16:08:09.558Z" }, + { url = "https://files.pythonhosted.org/packages/b7/8e/038ccfe29d272b30086b25a4960f757f97122cb2ec42e62b460d02fe98e9/scipy-1.15.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:40e54d5c7e7ebf1aa596c374c49fa3135f04648a0caabcb66c52884b943f02b4", size = 36393132, upload-time = "2025-05-08T16:08:15.34Z" }, + { url = "https://files.pythonhosted.org/packages/10/7e/5c12285452970be5bdbe8352c619250b97ebf7917d7a9a9e96b8a8140f17/scipy-1.15.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5e721fed53187e71d0ccf382b6bf977644c533e506c4d33c3fb24de89f5c3ed5", size = 38979503, upload-time = "2025-05-08T16:08:21.513Z" }, + { url = "https://files.pythonhosted.org/packages/81/06/0a5e5349474e1cbc5757975b21bd4fad0e72ebf138c5592f191646154e06/scipy-1.15.3-cp313-cp313t-win_amd64.whl", hash = "sha256:76ad1fb5f8752eabf0fa02e4cc0336b4e8f021e2d5f061ed37d6d264db35e3ca", size = 40308097, upload-time = "2025-05-08T16:08:27.627Z" }, +] + +[[package]] +name = "scipy" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] +dependencies = [ + { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/75/b4ce781849931fef6fd529afa6b63711d5a733065722d0c3e2724af9e40a/scipy-1.17.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:1f95b894f13729334fb990162e911c9e5dc1ab390c58aa6cbecb389c5b5e28ec", size = 31613675, upload-time = "2026-02-23T00:16:00.13Z" }, + { url = "https://files.pythonhosted.org/packages/f7/58/bccc2861b305abdd1b8663d6130c0b3d7cc22e8d86663edbc8401bfd40d4/scipy-1.17.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:e18f12c6b0bc5a592ed23d3f7b891f68fd7f8241d69b7883769eb5d5dfb52696", size = 28162057, upload-time = "2026-02-23T00:16:09.456Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ee/18146b7757ed4976276b9c9819108adbc73c5aad636e5353e20746b73069/scipy-1.17.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a3472cfbca0a54177d0faa68f697d8ba4c80bbdc19908c3465556d9f7efce9ee", size = 20334032, upload-time = "2026-02-23T00:16:17.358Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e6/cef1cf3557f0c54954198554a10016b6a03b2ec9e22a4e1df734936bd99c/scipy-1.17.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:766e0dc5a616d026a3a1cffa379af959671729083882f50307e18175797b3dfd", size = 22709533, upload-time = "2026-02-23T00:16:25.791Z" }, + { url = "https://files.pythonhosted.org/packages/4d/60/8804678875fc59362b0fb759ab3ecce1f09c10a735680318ac30da8cd76b/scipy-1.17.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:744b2bf3640d907b79f3fd7874efe432d1cf171ee721243e350f55234b4cec4c", size = 33062057, upload-time = "2026-02-23T00:16:36.931Z" }, + { url = "https://files.pythonhosted.org/packages/09/7d/af933f0f6e0767995b4e2d705a0665e454d1c19402aa7e895de3951ebb04/scipy-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43af8d1f3bea642559019edfe64e9b11192a8978efbd1539d7bc2aaa23d92de4", size = 35349300, upload-time = "2026-02-23T00:16:49.108Z" }, + { url = "https://files.pythonhosted.org/packages/b4/3d/7ccbbdcbb54c8fdc20d3b6930137c782a163fa626f0aef920349873421ba/scipy-1.17.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd96a1898c0a47be4520327e01f874acfd61fb48a9420f8aa9f6483412ffa444", size = 35127333, upload-time = "2026-02-23T00:17:01.293Z" }, + { url = "https://files.pythonhosted.org/packages/e8/19/f926cb11c42b15ba08e3a71e376d816ac08614f769b4f47e06c3580c836a/scipy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4eb6c25dd62ee8d5edf68a8e1c171dd71c292fdae95d8aeb3dd7d7de4c364082", size = 37741314, upload-time = "2026-02-23T00:17:12.576Z" }, + { url = "https://files.pythonhosted.org/packages/95/da/0d1df507cf574b3f224ccc3d45244c9a1d732c81dcb26b1e8a766ae271a8/scipy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:d30e57c72013c2a4fe441c2fcb8e77b14e152ad48b5464858e07e2ad9fbfceff", size = 36607512, upload-time = "2026-02-23T00:17:23.424Z" }, + { url = "https://files.pythonhosted.org/packages/68/7f/bdd79ceaad24b671543ffe0ef61ed8e659440eb683b66f033454dcee90eb/scipy-1.17.1-cp311-cp311-win_arm64.whl", hash = "sha256:9ecb4efb1cd6e8c4afea0daa91a87fbddbce1b99d2895d151596716c0b2e859d", size = 24599248, upload-time = "2026-02-23T00:17:34.561Z" }, + { url = "https://files.pythonhosted.org/packages/35/48/b992b488d6f299dbe3f11a20b24d3dda3d46f1a635ede1c46b5b17a7b163/scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:35c3a56d2ef83efc372eaec584314bd0ef2e2f0d2adb21c55e6ad5b344c0dcb8", size = 31610954, upload-time = "2026-02-23T00:17:49.855Z" }, + { url = "https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fcb310ddb270a06114bb64bbe53c94926b943f5b7f0842194d585c65eb4edd76", size = 28172662, upload-time = "2026-02-23T00:18:01.64Z" }, + { url = "https://files.pythonhosted.org/packages/cf/a9/599c28631bad314d219cf9ffd40e985b24d603fc8a2f4ccc5ae8419a535b/scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cc90d2e9c7e5c7f1a482c9875007c095c3194b1cfedca3c2f3291cdc2bc7c086", size = 20344366, upload-time = "2026-02-23T00:18:12.015Z" }, + { url = "https://files.pythonhosted.org/packages/35/f5/906eda513271c8deb5af284e5ef0206d17a96239af79f9fa0aebfe0e36b4/scipy-1.17.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:c80be5ede8f3f8eded4eff73cc99a25c388ce98e555b17d31da05287015ffa5b", size = 22704017, upload-time = "2026-02-23T00:18:21.502Z" }, + { url = "https://files.pythonhosted.org/packages/da/34/16f10e3042d2f1d6b66e0428308ab52224b6a23049cb2f5c1756f713815f/scipy-1.17.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e19ebea31758fac5893a2ac360fedd00116cbb7628e650842a6691ba7ca28a21", size = 32927842, upload-time = "2026-02-23T00:18:35.367Z" }, + { url = "https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02ae3b274fde71c5e92ac4d54bc06c42d80e399fec704383dcd99b301df37458", size = 35235890, upload-time = "2026-02-23T00:18:49.188Z" }, + { url = "https://files.pythonhosted.org/packages/c5/5c/9d7f4c88bea6e0d5a4f1bc0506a53a00e9fcb198de372bfe4d3652cef482/scipy-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a604bae87c6195d8b1045eddece0514d041604b14f2727bbc2b3020172045eb", size = 35003557, upload-time = "2026-02-23T00:18:54.74Z" }, + { url = "https://files.pythonhosted.org/packages/65/94/7698add8f276dbab7a9de9fb6b0e02fc13ee61d51c7c3f85ac28b65e1239/scipy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f590cd684941912d10becc07325a3eeb77886fe981415660d9265c4c418d0bea", size = 37625856, upload-time = "2026-02-23T00:19:00.307Z" }, + { url = "https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:41b71f4a3a4cab9d366cd9065b288efc4d4f3c0b37a91a8e0947fb5bd7f31d87", size = 36549682, upload-time = "2026-02-23T00:19:07.67Z" }, + { url = "https://files.pythonhosted.org/packages/bc/98/fe9ae9ffb3b54b62559f52dedaebe204b408db8109a8c66fdd04869e6424/scipy-1.17.1-cp312-cp312-win_arm64.whl", hash = "sha256:f4115102802df98b2b0db3cce5cb9b92572633a1197c77b7553e5203f284a5b3", size = 24547340, upload-time = "2026-02-23T00:19:12.024Z" }, + { url = "https://files.pythonhosted.org/packages/76/27/07ee1b57b65e92645f219b37148a7e7928b82e2b5dbeccecb4dff7c64f0b/scipy-1.17.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5e3c5c011904115f88a39308379c17f91546f77c1667cea98739fe0fccea804c", size = 31590199, upload-time = "2026-02-23T00:19:17.192Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ae/db19f8ab842e9b724bf5dbb7db29302a91f1e55bc4d04b1025d6d605a2c5/scipy-1.17.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:6fac755ca3d2c3edcb22f479fceaa241704111414831ddd3bc6056e18516892f", size = 28154001, upload-time = "2026-02-23T00:19:22.241Z" }, + { url = "https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:7ff200bf9d24f2e4d5dc6ee8c3ac64d739d3a89e2326ba68aaf6c4a2b838fd7d", size = 20325719, upload-time = "2026-02-23T00:19:26.329Z" }, + { url = "https://files.pythonhosted.org/packages/b2/83/15087d945e0e4d48ce2377498abf5ad171ae013232ae31d06f336e64c999/scipy-1.17.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4b400bdc6f79fa02a4d86640310dde87a21fba0c979efff5248908c6f15fad1b", size = 22683595, upload-time = "2026-02-23T00:19:30.304Z" }, + { url = "https://files.pythonhosted.org/packages/b4/e0/e58fbde4a1a594c8be8114eb4aac1a55bcd6587047efc18a61eb1f5c0d30/scipy-1.17.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b64ca7d4aee0102a97f3ba22124052b4bd2152522355073580bf4845e2550b6", size = 32896429, upload-time = "2026-02-23T00:19:35.536Z" }, + { url = "https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:581b2264fc0aa555f3f435a5944da7504ea3a065d7029ad60e7c3d1ae09c5464", size = 35203952, upload-time = "2026-02-23T00:19:42.259Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a5/9afd17de24f657fdfe4df9a3f1ea049b39aef7c06000c13db1530d81ccca/scipy-1.17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:beeda3d4ae615106d7094f7e7cef6218392e4465cc95d25f900bebabfded0950", size = 34979063, upload-time = "2026-02-23T00:19:47.547Z" }, + { url = "https://files.pythonhosted.org/packages/8b/13/88b1d2384b424bf7c924f2038c1c409f8d88bb2a8d49d097861dd64a57b2/scipy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6609bc224e9568f65064cfa72edc0f24ee6655b47575954ec6339534b2798369", size = 37598449, upload-time = "2026-02-23T00:19:53.238Z" }, + { url = "https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:37425bc9175607b0268f493d79a292c39f9d001a357bebb6b88fdfaff13f6448", size = 36510943, upload-time = "2026-02-23T00:20:50.89Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fd/3be73c564e2a01e690e19cc618811540ba5354c67c8680dce3281123fb79/scipy-1.17.1-cp313-cp313-win_arm64.whl", hash = "sha256:5cf36e801231b6a2059bf354720274b7558746f3b1a4efb43fcf557ccd484a87", size = 24545621, upload-time = "2026-02-23T00:20:55.871Z" }, + { url = "https://files.pythonhosted.org/packages/6f/6b/17787db8b8114933a66f9dcc479a8272e4b4da75fe03b0c282f7b0ade8cd/scipy-1.17.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:d59c30000a16d8edc7e64152e30220bfbd724c9bbb08368c054e24c651314f0a", size = 31936708, upload-time = "2026-02-23T00:19:58.694Z" }, + { url = "https://files.pythonhosted.org/packages/38/2e/524405c2b6392765ab1e2b722a41d5da33dc5c7b7278184a8ad29b6cb206/scipy-1.17.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:010f4333c96c9bb1a4516269e33cb5917b08ef2166d5556ca2fd9f082a9e6ea0", size = 28570135, upload-time = "2026-02-23T00:20:03.934Z" }, + { url = "https://files.pythonhosted.org/packages/fd/c3/5bd7199f4ea8556c0c8e39f04ccb014ac37d1468e6cfa6a95c6b3562b76e/scipy-1.17.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2ceb2d3e01c5f1d83c4189737a42d9cb2fc38a6eeed225e7515eef71ad301dce", size = 20741977, upload-time = "2026-02-23T00:20:07.935Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b8/8ccd9b766ad14c78386599708eb745f6b44f08400a5fd0ade7cf89b6fc93/scipy-1.17.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:844e165636711ef41f80b4103ed234181646b98a53c8f05da12ca5ca289134f6", size = 23029601, upload-time = "2026-02-23T00:20:12.161Z" }, + { url = "https://files.pythonhosted.org/packages/6d/a0/3cb6f4d2fb3e17428ad2880333cac878909ad1a89f678527b5328b93c1d4/scipy-1.17.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:158dd96d2207e21c966063e1635b1063cd7787b627b6f07305315dd73d9c679e", size = 33019667, upload-time = "2026-02-23T00:20:17.208Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c3/2d834a5ac7bf3a0c806ad1508efc02dda3c8c61472a56132d7894c312dea/scipy-1.17.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74cbb80d93260fe2ffa334efa24cb8f2f0f622a9b9febf8b483c0b865bfb3475", size = 35264159, upload-time = "2026-02-23T00:20:23.087Z" }, + { url = "https://files.pythonhosted.org/packages/4d/77/d3ed4becfdbd217c52062fafe35a72388d1bd82c2d0ba5ca19d6fcc93e11/scipy-1.17.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dbc12c9f3d185f5c737d801da555fb74b3dcfa1a50b66a1a93e09190f41fab50", size = 35102771, upload-time = "2026-02-23T00:20:28.636Z" }, + { url = "https://files.pythonhosted.org/packages/bd/12/d19da97efde68ca1ee5538bb261d5d2c062f0c055575128f11a2730e3ac1/scipy-1.17.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94055a11dfebe37c656e70317e1996dc197e1a15bbcc351bcdd4610e128fe1ca", size = 37665910, upload-time = "2026-02-23T00:20:34.743Z" }, + { url = "https://files.pythonhosted.org/packages/06/1c/1172a88d507a4baaf72c5a09bb6c018fe2ae0ab622e5830b703a46cc9e44/scipy-1.17.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e30bdeaa5deed6bc27b4cc490823cd0347d7dae09119b8803ae576ea0ce52e4c", size = 36562980, upload-time = "2026-02-23T00:20:40.575Z" }, + { url = "https://files.pythonhosted.org/packages/70/b0/eb757336e5a76dfa7911f63252e3b7d1de00935d7705cf772db5b45ec238/scipy-1.17.1-cp313-cp313t-win_arm64.whl", hash = "sha256:a720477885a9d2411f94a93d16f9d89bad0f28ca23c3f8daa521e2dcc3f44d49", size = 24856543, upload-time = "2026-02-23T00:20:45.313Z" }, + { url = "https://files.pythonhosted.org/packages/cf/83/333afb452af6f0fd70414dc04f898647ee1423979ce02efa75c3b0f2c28e/scipy-1.17.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:a48a72c77a310327f6a3a920092fa2b8fd03d7deaa60f093038f22d98e096717", size = 31584510, upload-time = "2026-02-23T00:21:01.015Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a6/d05a85fd51daeb2e4ea71d102f15b34fedca8e931af02594193ae4fd25f7/scipy-1.17.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:45abad819184f07240d8a696117a7aacd39787af9e0b719d00285549ed19a1e9", size = 28170131, upload-time = "2026-02-23T00:21:05.888Z" }, + { url = "https://files.pythonhosted.org/packages/db/7b/8624a203326675d7746a254083a187398090a179335b2e4a20e2ddc46e83/scipy-1.17.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3fd1fcdab3ea951b610dc4cef356d416d5802991e7e32b5254828d342f7b7e0b", size = 20342032, upload-time = "2026-02-23T00:21:09.904Z" }, + { url = "https://files.pythonhosted.org/packages/c9/35/2c342897c00775d688d8ff3987aced3426858fd89d5a0e26e020b660b301/scipy-1.17.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:7bdf2da170b67fdf10bca777614b1c7d96ae3ca5794fd9587dce41eb2966e866", size = 22678766, upload-time = "2026-02-23T00:21:14.313Z" }, + { url = "https://files.pythonhosted.org/packages/ef/f2/7cdb8eb308a1a6ae1e19f945913c82c23c0c442a462a46480ce487fdc0ac/scipy-1.17.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:adb2642e060a6549c343603a3851ba76ef0b74cc8c079a9a58121c7ec9fe2350", size = 32957007, upload-time = "2026-02-23T00:21:19.663Z" }, + { url = "https://files.pythonhosted.org/packages/0b/2e/7eea398450457ecb54e18e9d10110993fa65561c4f3add5e8eccd2b9cd41/scipy-1.17.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eee2cfda04c00a857206a4330f0c5e3e56535494e30ca445eb19ec624ae75118", size = 35221333, upload-time = "2026-02-23T00:21:25.278Z" }, + { url = "https://files.pythonhosted.org/packages/d9/77/5b8509d03b77f093a0d52e606d3c4f79e8b06d1d38c441dacb1e26cacf46/scipy-1.17.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d2650c1fb97e184d12d8ba010493ee7b322864f7d3d00d3f9bb97d9c21de4068", size = 35042066, upload-time = "2026-02-23T00:21:31.358Z" }, + { url = "https://files.pythonhosted.org/packages/f9/df/18f80fb99df40b4070328d5ae5c596f2f00fffb50167e31439e932f29e7d/scipy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:08b900519463543aa604a06bec02461558a6e1cef8fdbb8098f77a48a83c8118", size = 37612763, upload-time = "2026-02-23T00:21:37.247Z" }, + { url = "https://files.pythonhosted.org/packages/4b/39/f0e8ea762a764a9dc52aa7dabcfad51a354819de1f0d4652b6a1122424d6/scipy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:3877ac408e14da24a6196de0ddcace62092bfc12a83823e92e49e40747e52c19", size = 37290984, upload-time = "2026-02-23T00:22:35.023Z" }, + { url = "https://files.pythonhosted.org/packages/7c/56/fe201e3b0f93d1a8bcf75d3379affd228a63d7e2d80ab45467a74b494947/scipy-1.17.1-cp314-cp314-win_arm64.whl", hash = "sha256:f8885db0bc2bffa59d5c1b72fad7a6a92d3e80e7257f967dd81abb553a90d293", size = 25192877, upload-time = "2026-02-23T00:22:39.798Z" }, + { url = "https://files.pythonhosted.org/packages/96/ad/f8c414e121f82e02d76f310f16db9899c4fcde36710329502a6b2a3c0392/scipy-1.17.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:1cc682cea2ae55524432f3cdff9e9a3be743d52a7443d0cba9017c23c87ae2f6", size = 31949750, upload-time = "2026-02-23T00:21:42.289Z" }, + { url = "https://files.pythonhosted.org/packages/7c/b0/c741e8865d61b67c81e255f4f0a832846c064e426636cd7de84e74d209be/scipy-1.17.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:2040ad4d1795a0ae89bfc7e8429677f365d45aa9fd5e4587cf1ea737f927b4a1", size = 28585858, upload-time = "2026-02-23T00:21:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/ed/1b/3985219c6177866628fa7c2595bfd23f193ceebbe472c98a08824b9466ff/scipy-1.17.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:131f5aaea57602008f9822e2115029b55d4b5f7c070287699fe45c661d051e39", size = 20757723, upload-time = "2026-02-23T00:21:52.039Z" }, + { url = "https://files.pythonhosted.org/packages/c0/19/2a04aa25050d656d6f7b9e7b685cc83d6957fb101665bfd9369ca6534563/scipy-1.17.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9cdc1a2fcfd5c52cfb3045feb399f7b3ce822abdde3a193a6b9a60b3cb5854ca", size = 23043098, upload-time = "2026-02-23T00:21:56.185Z" }, + { url = "https://files.pythonhosted.org/packages/86/f1/3383beb9b5d0dbddd030335bf8a8b32d4317185efe495374f134d8be6cce/scipy-1.17.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e3dcd57ab780c741fde8dc68619de988b966db759a3c3152e8e9142c26295ad", size = 33030397, upload-time = "2026-02-23T00:22:01.404Z" }, + { url = "https://files.pythonhosted.org/packages/41/68/8f21e8a65a5a03f25a79165ec9d2b28c00e66dc80546cf5eb803aeeff35b/scipy-1.17.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a9956e4d4f4a301ebf6cde39850333a6b6110799d470dbbb1e25326ac447f52a", size = 35281163, upload-time = "2026-02-23T00:22:07.024Z" }, + { url = "https://files.pythonhosted.org/packages/84/8d/c8a5e19479554007a5632ed7529e665c315ae7492b4f946b0deb39870e39/scipy-1.17.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a4328d245944d09fd639771de275701ccadf5f781ba0ff092ad141e017eccda4", size = 35116291, upload-time = "2026-02-23T00:22:12.585Z" }, + { url = "https://files.pythonhosted.org/packages/52/52/e57eceff0e342a1f50e274264ed47497b59e6a4e3118808ee58ddda7b74a/scipy-1.17.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a77cbd07b940d326d39a1d1b37817e2ee4d79cb30e7338f3d0cddffae70fcaa2", size = 37682317, upload-time = "2026-02-23T00:22:18.513Z" }, + { url = "https://files.pythonhosted.org/packages/11/2f/b29eafe4a3fbc3d6de9662b36e028d5f039e72d345e05c250e121a230dd4/scipy-1.17.1-cp314-cp314t-win_amd64.whl", hash = "sha256:eb092099205ef62cd1782b006658db09e2fed75bffcae7cc0d44052d8aa0f484", size = 37345327, upload-time = "2026-02-23T00:22:24.442Z" }, + { url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165, upload-time = "2026-02-23T00:22:29.563Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "soupsieve" +version = "2.7" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +sdist = { url = "https://files.pythonhosted.org/packages/3f/f4/4a80cd6ef364b2e8b65b15816a843c0980f7a5a2b4dc701fc574952aa19f/soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a", size = 103418, upload-time = "2025-04-20T18:50:08.518Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/9c/0e6afc12c269578be5c0c1c9f4b49a8d32770a080260c333ac04cc1c832d/soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4", size = 36677, upload-time = "2025-04-20T18:50:07.196Z" }, +] + +[[package]] +name = "soupsieve" +version = "2.8.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/7b/ae/2d9c981590ed9999a0d91755b47fc74f74de286b0f5cee14c9269041e6c4/soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349", size = 118627, upload-time = "2026-01-20T04:27:02.457Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95", size = 37016, upload-time = "2026-01-20T04:27:01.012Z" }, +] + +[[package]] +name = "tqdm" +version = "4.67.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598, upload-time = "2026-02-03T17:35:53.048Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.13.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "tzdata" +version = "2026.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/19/1b9b0e29f30c6d35cb345486df41110984ea67ae69dddbc0e8a100999493/tzdata-2026.2.tar.gz", hash = "sha256:9173fde7d80d9018e02a662e168e5a2d04f87c41ea174b139fbef642eda62d10", size = 198254, upload-time = "2026-04-24T15:22:08.651Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/e4/dccd7f47c4b64213ac01ef921a1337ee6e30e8c6466046018326977efd95/tzdata-2026.2-py2.py3-none-any.whl", hash = "sha256:bbe9af844f658da81a5f95019480da3a89415801f6cc966806612cc7169bffe7", size = 349321, upload-time = "2026-04-24T15:22:05.876Z" }, +] + +[[package]] +name = "urllib3" +version = "2.2.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +sdist = { url = "https://files.pythonhosted.org/packages/ed/63/22ba4ebfe7430b76388e7cd448d5478814d3032121827c12a2cc287e2260/urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9", size = 300677, upload-time = "2024-09-12T10:52:18.401Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/d9/5f4c13cecde62396b0d3fe530a50ccea91e7dfc1ccf0e09c228841bb5ba8/urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", size = 126338, upload-time = "2024-09-12T10:52:16.589Z" }, +] + +[[package]] +name = "urllib3" +version = "2.6.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, +] + +[[package]] +name = "zipp" +version = "3.20.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/bf/5c0000c44ebc80123ecbdddba1f5dcd94a5ada602a9c225d84b5aaa55e86/zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29", size = 24199, upload-time = "2024-09-13T13:44:16.101Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/8b/5ba542fa83c90e09eac972fc9baca7a88e7e7ca4b221a89251954019308b/zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350", size = 9200, upload-time = "2024-09-13T13:44:14.38Z" }, +]