Test Cases

The test_2_case() tests in Local Usage and Nonlocal Usage showcase the fact that the brute force case and the MiLoMerge package produce the same result. Selected tests are shown below, but one can also look at the source code on Git if desired.

Local Usage

import numpy as np
import pytest
import MiLoMerge
import helpers

def test_naive():
    h1 = np.zeros(30)
    h1[0] = 5
    h1[1] = 1

    h2 = np.zeros(30)
    h2[2] = 5
    h2[3] = 2
    
    h3 = np.zeros(30)
    h3[4] = 5
    h3[5] = 3

    merger = MiLoMerge.MergerLocal(
        range(31), #bin edges
        h1,
        h2,
        h3
    )
    new_edges, new_counts = merger.run(3, return_counts=True)
    assert np.count_nonzero(new_counts[0] == 6) == 1
    assert np.count_nonzero(new_counts[0] == 0) == 2
    assert np.all(new_counts[0][new_counts[1] == 7] == 0)
    assert np.all(new_counts[0][new_counts[2] == 8] == 0)

    assert np.count_nonzero(new_counts[1] == 7) == 1
    assert np.count_nonzero(new_counts[1] == 0) == 2
    assert np.all(new_counts[1][new_counts[0] == 6] == 0)
    assert np.all(new_counts[1][new_counts[2] == 8] == 0)

    assert np.count_nonzero(new_counts[2] == 8) == 1
    assert np.count_nonzero(new_counts[2] == 0) == 2
    assert np.all(new_counts[2][new_counts[0] == 6] == 0)
    assert np.all(new_counts[2][new_counts[1] == 7] == 0)

    assert np.array_equal(new_edges, [0,2,4,30])

def test_naive_comp_first():
    h1 = np.zeros(30)
    h1[0] = 5
    h1[1] = 1

    h2 = np.zeros(30)
    h2[2] = 5
    h2[3] = 2
    
    h3 = np.zeros(30)
    h3[4] = 5
    h3[5] = 3

    merger = MiLoMerge.MergerLocal(
        range(31), #bin edges
        h1,
        h2,
        h3,
        comp_to_first=True
    )
    new_edges, new_counts = merger.run(2, return_counts=True)
    
    nonzero_h1_mask = new_counts[0] == 6
    zero_h1_mask = new_counts[0] == 0
    assert new_counts[1][nonzero_h1_mask] == 0
    assert new_counts[1][zero_h1_mask] == 7

    assert new_counts[2][nonzero_h1_mask] == 0
    assert new_counts[2][zero_h1_mask] == 8

    assert np.array_equal(new_edges, [0,2,30])


def test_2_case():
    h1 = np.array([3,4,5,6,8,9,10], dtype=np.float64)
    h2 = np.array([5,4,3,8,21,9,2], dtype=np.float64)
    assert len(h1) == len(h2)

    merger = MiLoMerge.MergerLocal(
        range(len(h1) + 1), #bin edges
        h1,
        h2
    )

    _, (h1_MLM, h2_MLM) = merger.run(2, return_counts=True)

    h1_BF, h2_BF = helpers.brute_force(h1, h2, 2, local=True)

    assert np.array_equal(h1_BF.sort(), h1_MLM.sort())
    assert np.array_equal(h2_BF.sort(), h2_MLM.sort())

def test_invalid_binsize_1d():
    h1 = [1,2,3]
    h2 = [4,5,6]
    with pytest.raises(ValueError) as exc_info:
        MiLoMerge.MergerLocal(
            range(3),
            h1,
            h2
        )
    assert "len(counts) =" in str(exc_info.value)
    assert exc_info.type is ValueError

# if __name__ == "__main__":
#     test_2_case()

Nonlocal Usage

import numpy as np
import pytest
import MiLoMerge
import helpers

def test_naive():
    h1 = np.zeros(30)
    h1[0] = 5
    h1[3] = 1

    h2 = np.zeros(30)
    h2[1] = 5
    h2[4] = 2
    
    h3 = np.zeros(30)
    h3[2] = 5
    h3[5] = 3

    merger = MiLoMerge.MergerNonlocal(
        range(31), #bin edges
        h1,
        h2,
        h3
    )
    new_counts = merger.run(3)
    assert np.count_nonzero(new_counts[0] == 6) == 1
    assert np.count_nonzero(new_counts[0] == 0) == 2
    assert np.all(new_counts[0][new_counts[1] == 7] == 0)
    assert np.all(new_counts[0][new_counts[2] == 8] == 0)

    assert np.count_nonzero(new_counts[1] == 7) == 1
    assert np.count_nonzero(new_counts[1] == 0) == 2
    assert np.all(new_counts[1][new_counts[0] == 6] == 0)
    assert np.all(new_counts[1][new_counts[2] == 8] == 0)

    assert np.count_nonzero(new_counts[2] == 8) == 1
    assert np.count_nonzero(new_counts[2] == 0) == 2
    assert np.all(new_counts[2][new_counts[0] == 6] == 0)
    assert np.all(new_counts[2][new_counts[1] == 7] == 0)

def test_naive_comp_first():
    h1 = np.zeros(30)
    h1[0] = 5
    h1[3] = 1

    h2 = np.zeros(30)
    h2[1] = 5
    h2[4] = 2
    
    h3 = np.zeros(30)
    h3[2] = 5
    h3[5] = 3

    merger = MiLoMerge.MergerNonlocal(
        range(31), #bin edges
        h1,
        h2,
        h3,
        comp_to_first=True
    )
    new_counts = merger.run(2)
    
    nonzero_h1_mask = new_counts[0] == 6
    zero_h1_mask = new_counts[0] == 0
    assert new_counts[1][nonzero_h1_mask] == 0
    assert new_counts[1][zero_h1_mask] == 7

    assert new_counts[2][nonzero_h1_mask] == 0
    assert new_counts[2][zero_h1_mask] == 8

def test_2_case():
    h1 = np.array([3,4,5,6,8,9,10], dtype=np.float64)
    h2 = np.array([5,4,3,8,21,9,2], dtype=np.float64)
    assert len(h1) == len(h2)

    h1_BF, h2_BF = helpers.brute_force(h1, h2, 2)

    merger = MiLoMerge.MergerNonlocal(
        range(len(h1) + 1), #bin edges
        h1,
        h2
    )
    h1_MLM, h2_MLM = merger.run(2)
    assert np.array_equal(h1_BF.sort(), h1_MLM.sort())
    assert np.array_equal(h2_BF.sort(), h2_MLM.sort())

def test_invalid_binsize_1d():
    h1 = [1,2,3]
    h2 = [4,5,6]
    with pytest.raises(ValueError) as exc_info:
        MiLoMerge.MergerNonlocal(
            range(3),
            h1,
            h2
        )
    assert "Bin edges are of invalid size" in str(exc_info.value)
    assert exc_info.type is ValueError

def test_invalid_binsize_2d():
    h1 = np.array([
        [1,2,3],
        [4,5,6]
    ])
    h2 = np.array([
        [7,8,9],
        [10,11,12]
    ])
    with pytest.raises(ValueError) as exc_info:
        MiLoMerge.MergerNonlocal(
            (range(3), range(3)),
            h1,
            h2
        )
    assert "Bin edge for dimension 1" in str(exc_info.value)
    assert exc_info.type is ValueError

def test_invalid_number_of_bins():
    h1 = np.array([
        [1,2,3],
        [4,5,6]
    ])
    h2 = np.array([
        [7,8,9],
        [10,11,12]
    ])
    with pytest.raises(IndexError) as exc_info:
        MiLoMerge.MergerNonlocal(
            (range(3),),
            h1,
            h2
        )
    assert "No bin edges provided" in str(exc_info.value)
    assert exc_info.type is IndexError

Post-merging Bin Placement

import numpy as np
import pytest
import MiLoMerge
import helpers

def test_local_placement():
    bin_centers = (np.arange(1, 31) + np.arange(30))/2

    h1 = np.zeros(30)
    h1[0] = 5
    h1[1] = 1
    h1_data = [bin_centers[0]]*int(h1[0]) + [bin_centers[1]]*int(h1[1])

    h2 = np.zeros(30)
    h2[2] = 5
    h2[3] = 2
    h2_data = [bin_centers[2]]*int(h2[2]) + [bin_centers[3]]*int(h2[3])
    
    h3 = np.zeros(30)
    h3[4] = 5
    h3[5] = 3
    h3_data = [bin_centers[4]]*int(h3[4]) + [bin_centers[5]]*int(h3[5])

    merger = MiLoMerge.MergerLocal(
        range(31), #bin edges
        h1,
        h2,
        h3, 
        map_at=(3,)
    )
    new_edges, new_counts = merger.run(3, return_counts=True)
    for i, arr in enumerate((h1_data, h2_data, h3_data)):
        c, b = MiLoMerge.place_local(3, arr, "./", verbose=False)
        assert np.array_equal(c, new_counts[i])
        assert np.array_equal(b, new_edges)


def test_nonlocal_placement_1d():
    bin_centers = (np.arange(1, 31) + np.arange(30))/2
    h1 = np.zeros(30)
    h1[0] = 5
    h1[3] = 1
    h1_data = [bin_centers[0]]*int(h1[0]) + [bin_centers[3]]*int(h1[3])

    h2 = np.zeros(30)
    h2[1] = 5
    h2[4] = 2
    h2_data = [bin_centers[1]]*int(h2[1]) + [bin_centers[4]]*int(h2[4])
    
    h3 = np.zeros(30)
    h3[2] = 5
    h3[5] = 3
    h3_data = [bin_centers[2]]*int(h3[2]) + [bin_centers[5]]*int(h3[5])

    merger = MiLoMerge.MergerNonlocal(
        range(31), #bin edges
        h1,
        h2,
        h3,
        map_at=(3,)
    )
    new_counts = merger.run(3)
    for i, arr in enumerate((h1_data, h2_data, h3_data)):
        temp_counts = np.zeros(3)
        placements = MiLoMerge.place_array_nonlocal(3, arr, "./")
        temp_counts, _ = np.histogram(placements, range(3+1))
        assert np.array_equal(temp_counts, new_counts[i])