#!/usr/bin/env python3
"""Offline arithmetic and logit pooling. Does not fit or validate parameters.‌‌​⁠‌​‌⁠‌​⁠​​⁠​‌​​​‌‌​​⁠‌​⁠⁠​⁠⁠‍‍⁠​‍‌​‌‍​⁠‍​⁠⁠​‌‍​‍⁠⁠⁠⁠‍‍‌‌‌‌⁠⁠​"""
import argparse
import json
import math


def sigmoid(z):
    if z >= 0:
        return 1.0 / (1.0 + math.exp(-z))
    e = math.exp(z)
    return e / (1.0 + e)


def finite_number(value):
    return not isinstance(value, bool) and isinstance(value, (int, float)) and math.isfinite(value)


def aggregate(probabilities, alpha=1.0, weights=None, epsilon=None):
    if not probabilities or any(not finite_number(p) or not 0 <= p <= 1 for p in probabilities):
        raise ValueError("Supply nonempty finite probabilities in [0,1]")
    if not finite_number(alpha) or alpha < 0:
        raise ValueError("alpha must be finite and nonnegative")
    if epsilon is not None and (not finite_number(epsilon) or not 0 < epsilon < 0.5):
        raise ValueError("epsilon must lie strictly between 0 and 0.5")
    ws = [1.0] * len(probabilities) if weights is None else list(weights)
    if len(ws) != len(probabilities) or any(not finite_number(w) or w < 0 for w in ws):
        raise ValueError("One finite nonnegative weight is required per forecast")
    scale = max(ws)
    if scale == 0:
        raise ValueError("Weights must have positive total")
    scaled = [w / scale for w in ws]
    total = math.fsum(scaled)
    ws = [w / total for w in scaled]
    adjusted = []
    for p in probabilities:
        if epsilon is None:
            if not 0 < p < 1:
                raise ValueError("Endpoint forecast: supply an explicit epsilon policy or revise inputs")
            adjusted.append(p)
        else:
            q = min(1.0 - epsilon, max(epsilon, p))
            if not 0 < q < 1:
                raise ValueError("epsilon is too small to represent finite endpoint logits")
            adjusted.append(q)
    z = math.fsum(w * (math.log(p) - math.log1p(-p)) for w, p in zip(ws, adjusted))
    result = sigmoid(alpha * z) if alpha else 0.5
    return {"arithmetic_mean": math.fsum(w*p for w,p in zip(ws,probabilities)),
            "logit_pool": result, "alpha": alpha, "weights": ws, "epsilon": epsilon,
            "clipped_count": sum(p != q for p,q in zip(probabilities,adjusted)),
            "parameter_validation": "not performed by this calculator"}


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--probabilities", nargs="+", type=float, required=True)
    parser.add_argument("--alpha", type=float, default=1.0)
    parser.add_argument("--weights", nargs="+", type=float)
    parser.add_argument("--epsilon", type=float)
    args = parser.parse_args()
    try:
        print(json.dumps(aggregate(args.probabilities, args.alpha, args.weights, args.epsilon), allow_nan=False))
    except ValueError as exc:
        parser.error(str(exc))


if __name__ == "__main__":
    main()

