import math


def compute_clipped_negative_log_likelihood(target_distr, measured_distr, epsilon=1e-9):
    """Compute the value of the clipped negative log likelihood between a target bitstring distribution 
    and a measured bitstring distribution
    See Equation (4) in https://advances.sciencemag.org/content/5/10/eaaw9918?rss=1

    Args:
        target_distr (BitstringDistribution): The target bitstring probability distribution.
        measured_distr (BitstringDistribution): The measured bitstring probability distribution.
        epsilon (float): The small parameter needed to regularize log computation when argument is zero. Default = 1e-9.
    Returns:
        float: The value of the clipped negative log likelihood
    """

    value=0.
    target_keys = target_distr.distribution_dict.keys()
    measured_keys = measured_distr.distribution_dict.keys()
    all_keys = set(target_keys).union(measured_keys)

    for bitstring in all_keys:
        target_bitstring_value = target_distr.distribution_dict.get(bitstring,0)
        measured_bitstring_value = measured_distr.distribution_dict.get(bitstring,0)

        value += target_bitstring_value * math.log(max(epsilon,measured_bitstring_value))

    return -value