Project: s41467-025-60306-2 | Function: ap_contiguous (python)

system: You are an expert Python programmer implementing algorithms from scientific papers.
Complete the masked function based on the algorithm description.
Follow Python best practices and maintain consistent code style.
Return **only** the complete Python function code.

user: Complete this Python function:

**DESCRIPTION:** Computes Average Precision (AP) scores from relevance labels and counts. Implements the mathematical AP calculation using precision-at-k formula, calculating true positives, ranks, and precision values for each profile segment.

**KEY TERMS:** {
  "ap_scores": "Average Precision scores",
  "rel_k_list": "relevance labels (binary)",
  "tp": "true positives",
  "pr_k": "precision at rank k",
  "num_pos": "number of positive pairs"
}

**CONTEXT CODE:**     # If a string is provided, look up the corresponding function
    if isinstance(distance, str):
        if distance in similarity_functions:
            similarity_fn = similarity_functions[distance]
        elif distance in SCIPY_METRICS_NAMES:
            similarity_fn = lambda x_sample, y_sample: _cdist_diag_sim(
                x_sample, y_sample, distance
            )
        else:
            raise ValueError(
                f"Unsupported distance function: {distance}. Supported functions are: {set(similarity_functions.keys()) | set(SCIPY_METRICS_NAMES)}"
            )
    elif callable(distance):
        # If a callable is provided, use it directly
        similarity_fn = distance
    else:
        # Raise an error if neither a string nor a callable is provided
        raise ValueError("Distance must be either a string or a callable object.")

    # Wrap the distance function for efficient batch processing
    return batch_processing(similarity_fn, progress_bar=progress_bar)


def random_binary_matrix(n, m, k, rng):
    """Generate a indices of k values in 1 per row in a random binary n*m matrix.

    Args:
    n: Number of rows.
    m: Number of columns.
    k: Number of 1's per row.

    Returns
    -------
    np.ndarray
        A binary matrix of shape `(n, m)` with exactly `k` ones per row.
    """
    dtype = np.uint16 if m < 2**16 else np.uint32
    indices = np.tile(np.arange(m, dtype=dtype), (n, 1))
    rng.permuted(indices, axis=1, out=indices)
    return np.sort(indices[:, :k], axis=1)


def average_precision(rel_k) -> np.ndarray:
    """Compute average precision based on binary list indices."""
    num_pos = rel_k.shape[1]
    pr_k = np.arange(1, num_pos + 1, dtype=np.float32) / (rel_k + 1)
    ap_values = pr_k.sum(axis=1) / num_pos
    return ap_values.astype(np.float32)


def ap_contiguous(
    rel_k_list: np.ndarray, counts: np.ndarray
) -> Tuple[np.ndarray, np.ndarray]:
    # MASKED: Computes Average Precision (AP) scores from relevance labels and counts using precision-at-k formula implementation
    raise NotImplementedError("This function is masked for testing purposes.")


def random_ap(num_perm: int, num_pos: int, total: int, seed: int):
    """Generate random Average Precision (AP) scores to create a null distribution.

    This function computes multiple Average Precision (AP) scores based on randomly
    generated binary relevance lists. It is useful for generating a null distribution
    to assess the significance of observed AP scores.

    Parameters
    ----------
    num_perm : int
        Number of random permutations (i.e., how many random relevance lists to generate).
    num_pos : int
        Number of positive samples (1's) in each relevance list.
    total : int
        Total number of samples (columns) in each relevance list.
    seed : int
        Seed for the random number generator to ensure reproducibility.

    Returns
    -------
    np.ndarray
        A 1D array containing the Average Precision scores for each randomly
        generated relevance list.
    """
    # Initialize the random number generator
    rng = np.random.default_rng(seed)

    # Generate a binary matrix with `num_perm` rows and `total` columns,
    # where each row contains exactly `num_pos` ones distributed randomly
    rel_k = random_binary_matrix(num_perm, total, num_pos, rng)

    # Compute Average Precision (AP) scores for each row of the binary matrix
    null_dist = average_precision(rel_k)
    return null_dist


def null_dist_cached(
    num_pos: int, total: int, seed: int, null_size: int, cache_dir: Path
) -> np.ndarray:
    """Generate or retrieve a cached null distribution for a given configuration.

    This function calculates a null distribution for a specified number of positive
    pairs (`num_pos`) and total pairs (`total`). It uses caching to store and
    retrieve precomputed distributions, saving time and computational resources.

    Parameters
    ----------
    num_pos : int
        Number of positive pairs in the configuration.
    total : int
        Total number of pairs (positive + negative) in the configuration.
    seed : int
        Random seed for reproducibility.
    null_size : int
        Number of samples to generate in the null distribution.
    cache_dir : Path
        Directory to store or retrieve cached null distributions.

    Returns
    -------
    np.ndarray
        Null distribution for the specified configuration.
    """
    # Check if a seed is provided to enable caching
    if seed is not None:
        # Define the cache file name based on the configuration
        cache_file = cache_dir / f"n{total}_k{num_pos}.npy"

        # If the cache file exists, try to load the null distribution from it
        if cache_file.is_file():
            try:
                null_dist = np.load(cache_file)
            except ValueError as e:
                # Cache file is corrupted or incomplete, remove it and regenerate
                warnings.warn(
                    f"Failed to load cache file {cache_file}: {e}. Regenerating..."
                )
                cache_file.unlink(missing_ok=True)

                # Compute the null distribution
                null_dist = random_ap(null_size, num_pos, total, seed)

                # Save the new distribution to the cache
                np.save(cache_file, null_dist)
        else:
            # If the cache file doesn't exist, compute the null distribution
            null_dist = random_ap(null_size, num_pos, total, seed)

            # Save the computed distribution to the cache
            np.save(cache_file, null_dist)
    else:
        # If no seed is provided, compute the null distribution without caching
        null_dist = random_ap(null_size, num_pos, total, seed)


Complete the function implementation replacing "raise NotImplementedError".