Calculate the `deterministic mixture weights` according to
    [Cor+12]_ given ``samples``, standard ``weights`` and their ``proposals`` for a
    number of steps in which importance samples are computed for the same target
    density but different proposals.

    Return the weights as a :class:`pypmc.tools.History` such that the
    weights for each proposal are easily accessible.

    :param samples:

        Iterable of matrix-like arrays; the weighted samples whose importance
        weights shall be combined. One sample per row in each array, one array
        for each step, or different proposal.

    :param weights:

        Iterable of 1D arrays; the standard importance weights
        :math:`P(x_i^t)/q_t(x_i^t)`. Each array in the iterable contains all
        weights of the samples of step ``t``, they array's size has to match the
        ``t``-th entry in samples.

    :param proposals:

        Iterable of :py:class:`pypmc.density.base.ProbabilityDensity` instances;
        the proposal densities from which the ``samples`` have been
        drawn.

def combine_weights(samples, weights, proposals):
    """Calculate the `deterministic mixture weights` according to
    [Cor+12]_ given ``samples``, standard ``weights`` and their ``proposals`` for a
    number of steps in which importance samples are computed for the same target
    density but different proposals.

    Return the weights as a :class:`pypmc.tools.History` such that the
    weights for each proposal are easily accessible.

    :param samples:

        Iterable of matrix-like arrays; the weighted samples whose importance
        weights shall be combined. One sample per row in each array, one array
        for each step, or different proposal.

    :param weights:

        Iterable of 1D arrays; the standard importance weights
        :math:`P(x_i^t)/q_t(x_i^t)`. Each array in the iterable contains all
        weights of the samples of step ``t``, they array's size has to match the
        ``t``-th entry in samples.

    :param proposals:

        Iterable of :py:class:`pypmc.density.base.ProbabilityDensity` instances;
        the proposal densities from which the ``samples`` have been
        drawn.

    """
    # shallow copy --> can safely modify (need numpy arrays --> can overwrite with np.asarray)
    samples = list(samples)
    weights = list(weights)

    assert len(samples) == len(weights), \
    "Got %i importance-sampling runs but %i weights" % (len(samples), len(weights))

    assert len(samples) == len(proposals), \
    "Got %i importance-sampling runs but %i proposal densities" % (len(samples), len(proposals))

    # number of samples from each proposal
    N = _np.empty(len(proposals))
    N_total = 0

    # basic consistency checks, conversion to numpy array and counting of the total number of samples
    for i in range(len(N)):
        samples[i] = _np.asarray(samples[i])
        assert len(samples[i].shape) == 2, '``samples[%i]`` is not matrix like.' % i
        dim = samples[0].shape[-1]
        assert samples[i].shape[-1] == dim, \
            "Dimension of samples[0] (%i) does not match the dimension of samples[%i] (%i)" \
            % (dim, i, samples[i].shape[-1])
        N[i] = len(samples[i])
        N_total += N[i]

        weights[i] = _np.asarray(weights[i])
        assert N[i] == len(weights[i]), \
            'Length of weights[%i] (%i) does not match length of samples[%i] (%i)' \
            % (i, N[i], i, len(weights[i]))

    combined_weights_history = _History(1, N_total)

    # if all weights positive => prefer log scale
    all_positive = True
    for w in weights:
        all_positive &= (w[:] > 0.0).all()
        if not all_positive:
            break
    if all_positive:
        combined_weights_history = _combine_weights_log(samples, weights, proposals, combined_weights_history, N_total, N)
    else:
        combined_weights_history = _combine_weights_linear(samples, weights, proposals, combined_weights_history, N_total, N)

    assert _np.isfinite(combined_weights_history[:][:,0]).all(), 'Encountered inf or nan mixture weights'
    return combined_weights_history