code: def scake(
      doc: Doc,
      *,
      normalize: Optional[str | Callable[[Token], str]] = "lemma",
      include_pos: Optional[str | Collection[str]] = (
          "NOUN",
          "PROPN",
          "ADJ",
      ),
      topn: int | float = 10,
  ) -> list[tuple[str, float]]:
      """
      Extract key terms from a document using the sCAKE algorithm.
  
      Args:
          doc: spaCy ``Doc`` from which to extract keyterms. Must be sentence-segmented;
              optionally POS-tagged.
          normalize: If "lemma", lemmatize terms; if "lower", lowercase terms; if None,
              use the form of terms as they appeared in ``doc``; if a callable,
              must accept a ``Token`` and return a str,
              e.g. :func:`textacy.spacier.utils.get_normalized_text()`.
          include_pos: One or more POS tags with which to filter for good candidate keyterms.
              If None, include tokens of all POS tags
              (which also allows keyterm extraction from docs without POS-tagging.)
          topn: Number of top-ranked terms to return as key terms.
              If an integer, represents the absolute number; if a float, value
              must be in the interval (0.0, 1.0], which is converted to an int by
              ``int(round(len(candidates) * topn))``
  
      Returns:
          Sorted list of top ``topn`` key terms and their corresponding scores.
  
      References:
          Duari, Swagata & Bhatnagar, Vasudha. (2018). sCAKE: Semantic Connectivity
          Aware Keyword Extraction. Information Sciences. 477.
          https://arxiv.org/abs/1811.10831v1
      """
      # validate / transform args
      include_pos: Optional[set[str]] = (
          utils.to_set(include_pos) if include_pos else None
      )
      if isinstance(topn, float):
          if not 0.0 < topn <= 1.0:
              raise ValueError(
                  "topn={} is invalid; "
                  "must be an int, or a float between 0.0 and 1.0".format(
                      topn
                  )
              )
  
      # bail out on empty docs
      if not doc:
          return []
  
      # build up a graph of good words, edges weighting by adjacent sentence co-occurrence
      cooc_mat: Counter[tuple[str, str]] = collections.Counter()
      # handle edge case where doc only has 1 sentence
      n_sents = itertoolz.count(doc.sents)
      for window_sents in itertoolz.sliding_window(
          min(2, n_sents), doc.sents
      ):
          if n_sents == 1:
              window_sents = (window_sents[0], [])
          window_words: Iterable[str] = (
              word
              for word in itertoolz.concat(window_sents)
              if not (word.is_stop or word.is_punct or word.is_space)
              and (not include_pos or word.pos_ in include_pos)
          )
          window_words = ext_utils.terms_to_strings(
              window_words, normalize
          )
          cooc_mat.update(
              w1_w2
              for w1_w2 in itertools.combinations(
                  sorted(window_words), 2
              )
              if w1_w2[0] != w1_w2[1]
          )
      # doc doesn't have any valid words...
      if not cooc_mat:
          return []
  
      graph = nx.Graph()
      graph.add_edges_from(
          (w1, w2, {"weight": weight})
          for (w1, w2), weight in cooc_mat.items()
      )
  
      word_scores = _compute_word_scores(
          doc, graph, cooc_mat, normalize
      )
      if not word_scores:
          return []
  
      # generate a list of candidate terms
      candidates = _get_candidates(doc, normalize, include_pos)
      if isinstance(topn, float):
          topn = int(round(len(set(candidates)) * topn))
      # rank candidates by aggregating constituent word scores
      candidate_scores = {
          " ".join(candidate): sum(
              word_scores.get(word, 0.0) for word in candidate
          )
          for candidate in candidates
      }
      sorted_candidate_scores = sorted(
          candidate_scores.items(), key=itemgetter(1, 0), reverse=True
      )
      return ext_utils.get_filtered_topn_terms(
          sorted_candidate_scores, topn, match_threshold=0.8
      )
  
file_path: textacy/src/textacy/extract/keyterms/scake.py
imports: from __future__ import annotations
  import collections
  import itertools
  from operator import itemgetter
  from typing import Callable, Collection, Counter, Iterable, Optional
  import networkx
  from cytoolz import itertoolz
  from spacy.tokens import Doc, Token
  from ...None import utils
  from ..None import utils
label_desc: Use image node_id 1 for calling a global function with example usage: scake(doc) and returns: ext_utils, list, list, list