code: def create_tfidf_ann_index(
      out_path: str, kb: Optional[KnowledgeBase] = None
  ) -> Tuple[List[str], TfidfVectorizer, FloatIndex]:
      """
      Build tfidf vectorizer and ann index.
  
      Parameters
      ----------
      out_path: str, required.
          The path where the various model pieces will be saved.
      kb : KnowledgeBase, optional.
          The kb items to generate the index and vectors for.
  
      """
      tfidf_vectorizer_path = f"{out_path}/tfidf_vectorizer.joblib"
      ann_index_path = f"{out_path}/nmslib_index.bin"
      tfidf_vectors_path = f"{out_path}/tfidf_vectors_sparse.npz"
      umls_concept_aliases_path = f"{out_path}/concept_aliases.json"
  
      kb = kb or UmlsKnowledgeBase()
  
      # nmslib hyperparameters (very important)
      # guide: https://github.com/nmslib/nmslib/blob/master/manual/methods.md
      # Default values resulted in very low recall.
  
      # set to the maximum recommended value. Improves recall at the expense of longer indexing time.
      # We use the HNSW (Hierarchical Navigable Small World Graph) representation which is constructed
      # by consecutive insertion of elements in a random order by connecting them to M closest neighbours
      # from the previously inserted elements. These later become bridges between the network hubs that
      # improve overall graph connectivity. (bigger M -> higher recall, slower creation)
      # For more details see:  https://arxiv.org/pdf/1603.09320.pdf?
      m_parameter = 100
      # `C` for Construction. Set to the maximum recommended value
      # Improves recall at the expense of longer indexing time
      construction = 2000
      num_threads = 60  # set based on the machine
      index_params = {
          "M": m_parameter,
          "indexThreadQty": num_threads,
          "efConstruction": construction,
          "post": 0,
      }
  
      print(
          f"No tfidf vectorizer on {tfidf_vectorizer_path} or ann index on {ann_index_path}"
      )
      concept_aliases = list(kb.alias_to_cuis.keys())
  
      # NOTE: here we are creating the tf-idf vectorizer with float32 type, but we can serialize the
      # resulting vectors using float16, meaning they take up half the memory on disk. Unfortunately
      # we can't use the float16 format to actually run the vectorizer, because of this bug in sparse
      # matrix representations in scipy: https://github.com/scipy/scipy/issues/7408
      print(
          f"Fitting tfidf vectorizer on {len(concept_aliases)} aliases"
      )
      tfidf_vectorizer = TfidfVectorizer(
          analyzer="char_wb",
          ngram_range=(3, 3),
          min_df=10,
          dtype=numpy.float32,
      )
      start_time = datetime.datetime.now()
      concept_alias_tfidfs = tfidf_vectorizer.fit_transform(
          concept_aliases
      )
      print(f"Saving tfidf vectorizer to {tfidf_vectorizer_path}")
      joblib.dump(tfidf_vectorizer, tfidf_vectorizer_path)
      end_time = datetime.datetime.now()
      total_time = end_time - start_time
      print(
          f"Fitting and saving vectorizer took {total_time.total_seconds()} seconds"
      )
  
      print("Finding empty (all zeros) tfidf vectors")
      empty_tfidfs_boolean_flags = numpy.array(
          concept_alias_tfidfs.sum(axis=1) != 0
      ).reshape(-1)
      number_of_non_empty_tfidfs = sum(
          empty_tfidfs_boolean_flags == False
      )  # noqa: E712
      total_number_of_tfidfs = numpy.size(concept_alias_tfidfs, 0)
  
      print(
          f"Deleting {number_of_non_empty_tfidfs}/{total_number_of_tfidfs} aliases because their tfidf is empty"
      )
      # remove empty tfidf vectors, otherwise nmslib will crash
      concept_aliases = [
          alias
          for alias, flag in zip(
              concept_aliases, empty_tfidfs_boolean_flags
          )
          if flag
      ]
      concept_alias_tfidfs = concept_alias_tfidfs[
          empty_tfidfs_boolean_flags
      ]
      assert len(concept_aliases) == numpy.size(concept_alias_tfidfs, 0)
  
      print(
          f"Saving list of concept ids and tfidfs vectors to {umls_concept_aliases_path} and {tfidf_vectors_path}"
      )
      json.dump(concept_aliases, open(umls_concept_aliases_path, "w"))
      scipy.sparse.save_npz(
          tfidf_vectors_path, concept_alias_tfidfs.astype(numpy.float16)
      )
  
      print(
          f"Fitting ann index on {len(concept_aliases)} aliases (takes 2 hours)"
      )
      start_time = datetime.datetime.now()
      ann_index = nmslib.init(
          method="hnsw",
          space="cosinesimil_sparse",
          data_type=nmslib.DataType.SPARSE_VECTOR,
      )
      ann_index.addDataPointBatch(concept_alias_tfidfs)
      ann_index.createIndex(index_params, print_progress=True)
      ann_index.saveIndex(ann_index_path)
      end_time = datetime.datetime.now()
      elapsed_time = end_time - start_time
      print(
          f"Fitting ann index took {elapsed_time.total_seconds()} seconds"
      )
  
      return concept_aliases, tfidf_vectorizer, ann_index
  
imports: from typing import Optional, List, Dict, Tuple, NamedTuple, Type
  import json
  import datetime
  from collections import defaultdict
  import scipy
  import numpy
  import joblib
  from sklearn.feature_extraction.text import TfidfVectorizer
  import nmslib
  from nmslib.dist import FloatIndex
  from scispacy.file_cache import cached_path
  from scispacy.linking_utils import KnowledgeBase, UmlsKnowledgeBase, Mesh, GeneOntology, RxNorm, HumanPhenotypeOntology
label_desc: Use image node_id 2 for calling a global function with example usage: create_tfidf_ann_index(out_path, kb) and returns: concept_aliases,  tfidf_vectorizer,  ann_index