def indicator_score(answer_ids, answer_mask, context_ids, vocab):
  """Compute indicator score of answer and context.

  Checks if the answer tokens are a subspan of the context.

  Args:
    answer_ids: <int32> [batch_size, answer_length]
    answer_mask: <int32> [batch_size, answer_length]
    context_ids: <int32> [batch_size, context_length]
    vocab: Instance of text_utils.Vocab.

  Returns:
    score: <float32> [batch_size] tensor of {0.0, 1.0}.
  """
  batch_size = tensor_utils.shape(answer_ids, 0)

  # Get cleanable words.
  remove_ids = list(_get_normalized_set(vocab))
  remove_ids = tf.reshape(remove_ids, [1, 1, -1])
  remove_ids = tf.tile(remove_ids, [batch_size, 1, 1])

  # Clean answer: remove tokens that are in the normalized set.
  should_keep = tf.reduce_all(
      tf.not_equal(tf.expand_dims(answer_ids, -1), remove_ids), axis=-1)
  answer_ids = tensor_utils.boolean_mask(answer_ids, should_keep)
  answer_mask = tensor_utils.boolean_mask(answer_mask, should_keep)

  # Clean context: remove tokens that are in the normalized set.
  should_keep = tf.reduce_all(
      tf.not_equal(tf.expand_dims(context_ids, -1), remove_ids), axis=-1)
  context_ids = tensor_utils.boolean_mask(context_ids, should_keep)

  # Cleaned lengths.
  answer_len = tensor_utils.shape(answer_ids, 1)
  context_len = tensor_utils.shape(context_ids, 1)

  # Pad start of context (to select NULL for over-length indices).
  context_ids = tf.pad(context_ids, [[0, 0], [1, 0]])
  context_len += 1

  # Sliding window approach: take the full context of length N and gather
  # it into a tensor with all windows of length M (a N x M tensor).
  # [context_len, answer_len]
  window_idx = tf.range(answer_len)
  window_idx = tf.tile(tf.expand_dims(window_idx, 0), [context_len, 1])
  offsets = tf.expand_dims(tf.range(context_len), 1)
  window_idx += offsets
  window_idx *= tf.cast(tf.less(window_idx, context_len), tf.int32)

  # [batch_size, context_len * answer_len]
  window_idx = tf.reshape(window_idx, [1, -1])
  window_idx = tf.tile(window_idx, [batch_size, 1])

  # [batch_size, context_len * answer_len]
  batch_idx = tf.range(batch_size)
  batch_idx = tf.expand_dims(batch_idx, 1)
  batch_idx = tf.tile(batch_idx, [1, context_len * answer_len])

  # [batch_size, context_len, answer_len]
  batch_idx = tf.reshape(batch_idx, [-1])
  window_idx = tf.reshape(window_idx, [-1])
  coords = tf.stack([batch_idx, window_idx], axis=1)
  window_ids = tf.gather_nd(context_ids, coords)
  window_ids = tf.reshape(window_ids, [batch_size, context_len, answer_len])

  # [batch_size, context_len, answer_len]
  answer_mask = tf.expand_dims(answer_mask, 1)
  window_ids *= answer_mask

  # Check for equality. The whole window has to match the answer, but only
  # one window has to count to be a positive indicator value.
  answer_ids = tf.expand_dims(answer_ids, 1)
  is_equal = tf.reduce_all(tf.equal(answer_ids, window_ids), axis=-1)
  score = tf.cast(tf.reduce_any(is_equal, axis=-1), tf.float32)

  return score
Compute indicator score of answer and context.

  Checks if the answer tokens are a subspan of the context.

  Args:
    answer_ids: <int32> [batch_size, answer_length]
    answer_mask: <int32> [batch_size, answer_length]
    context_ids: <int32> [batch_size, context_length]
    vocab: Instance of text_utils.Vocab.

  Returns:
    score: <float32> [batch_size] tensor of {0.0, 1.0}.
  
language/capwap/utils/reward_utils.py