code: def sample_negatives(
      evaluation_triples: MappedTriples,
      additional_filter_triples: Union[
          None, MappedTriples, List[MappedTriples]
      ] = None,
      num_samples: int = 50,
      num_entities: Optional[int] = None,
  ) -> Mapping[Target, torch.FloatTensor]:
      """
      Sample true negatives for sampled evaluation.
  
      :param evaluation_triples: shape: (n, 3)
          the evaluation triples
      :param additional_filter_triples:
          additional true triples which are to be filtered
      :param num_samples: >0
          the number of samples
      :param num_entities:
          the number of entities
  
      :return:
          A mapping of sides to negative samples
      """
      additional_filter_triples = prepare_filter_triples(
          mapped_triples=evaluation_triples,
          additional_filter_triples=additional_filter_triples,
      )
      num_entities = num_entities or (
          additional_filter_triples[:, [0, 2]].max().item() + 1
      )
      num_triples = evaluation_triples.shape[0]
      df = pd.DataFrame(
          data=evaluation_triples.numpy(), columns=COLUMN_LABELS
      )
      all_df = pd.DataFrame(
          data=additional_filter_triples.numpy(), columns=COLUMN_LABELS
      )
      id_df = df.reset_index()
      all_ids = set(range(num_entities))
      negatives = {}
      for side in [LABEL_HEAD, LABEL_TAIL]:
          this_negatives = cast(
              torch.FloatTensor,
              torch.empty(
                  size=(num_triples, num_samples), dtype=torch.long
              ),
          )
          other = TARGET_TO_KEY_LABELS[side]
          for _, group in pd.merge(
              id_df, all_df, on=other, suffixes=["_eval", "_all"]
          ).groupby(
              by=other,
          ):
              pool = list(
                  all_ids.difference(
                      group[f"{side}_all"].unique().tolist()
                  )
              )
              if len(pool) < num_samples:
                  logger.warning(
                      f"There are less than num_samples={num_samples} candidates for side={side}, triples={group}.",
                  )
                  # repeat
                  pool = int(math.ceil(num_samples / len(pool))) * pool
              for i in group["index"].unique():
                  this_negatives[i, :] = torch.as_tensor(
                      data=random.sample(
                          population=pool, k=num_samples
                      ),
                      dtype=torch.long,
                  )
          negatives[side] = this_negatives
      return negatives
  
imports: from __future__ import annotations
  import functools
  import itertools
  import logging
  import math
  import random
  import re
  from collections import defaultdict
  from typing import Callable, DefaultDict, Iterable, List, Mapping, MutableMapping, NamedTuple, Optional, Sequence, Tuple, Type, TypeVar, Union, cast
  import numpy
  import numpy.random
  import pandas
  import torch
  from class_resolver import HintOrType, OptionalKwargs
  from .evaluator import Evaluator, MetricResults, prepare_filter_triples
  from .ranks import Ranks
  from ..constants import COLUMN_LABELS, TARGET_TO_KEY_LABELS, TARGET_TO_KEYS
  from ..metrics.ranking import HITS_METRICS, RankBasedMetric, rank_based_metric_resolver
  from ..metrics.utils import Metric
  from ..triples.triples_factory import CoreTriplesFactory
  from ..typing import LABEL_HEAD, LABEL_TAIL, RANK_REALISTIC, RANK_TYPE_SYNONYMS, RANK_TYPES, SIDE_BOTH, SIDES, ExtendedTarget, MappedTriples, RankType, Target, normalize_rank_type, normalize_target
label_desc: Use image node_id 6 for calling a global function with example usage: sample_negatives(evaluation_triples, additional_filter_triples, num_samples, num_entities) and returns: negatives