Determine the area under the cumulative distribution function of the active framework count, or ACD, of those
    actives ranked better than the specified False Postive Fraction.
    :param acd_structure: list [(id, best_score, best_query, status, fw smiles), ..., ]. All of the molecules in the
    list are actives.
    :param fpf: float the False Positive fraction above which the ACD is calculated
    :return: (fpf, ACD)

def calculate_acd(acd_structure, fpf):
    """
    Determine the area under the cumulative distribution function of the active framework count, or ACD, of those
    actives ranked better than the specified False Postive Fraction.
    :param acd_structure: list [(id, best_score, best_query, status, fw smiles), ..., ]. All of the molecules in the
    list are actives.
    :param fpf: float the False Positive fraction above which the ACD is calculated
    :return: (fpf, ACD)
    """

    # initialize variables
    fw_dict = defaultdict(int) # a count of the number of molecules that share a given framework

    # loop over the molecules in acd_structure and count up the molecules consistent with each unique framework
    for mol in acd_structure:
        fw_smiles = mol[4]
        fw_dict[fw_smiles] += 1

    fw_count = [x[1] for x in fw_dict.items()] # a list of the molecule count consistent with each unique framework
    fw_count.sort(reverse=True)                # always sort from high to low s.t.: 1 >= ACD >= 0.5

    n_actives = len(acd_structure) # number of active molecules above the fpf threshold
    n_active_fw = len(fw_count)    # number of unique active frameworks above the fpf threshold
    # determine the acd
    acd = (1. / n_actives) * (1. / n_active_fw) * sum(np.cumsum(fw_count))

    return (fpf, acd)