function list_points(mapping, grid, mesh, shift, ir_only)
    shift = shift ./ 2  # true / 2 = 0.5, false / 2 = 0
    if ir_only
        return map(unique(mapping)) do i
            (grid[:, i] .+ shift) ./ mesh
        end
    else
        return map(eachslice(grid; dims = 2)) do point
            (point .+ shift) ./ mesh  # Add 1 because `mapping` index starts from 0
        end
    end
end

# From https://github.com/unkcpz/LibSymspg.jl/blob/53d2f6d/test/test_api.jl#L89-L99
@testset "Test reciprocal mesh" begin
    lattice = [
        -2.0 2.0 2.0
        2.0 -2.0 2.0
        2.0 2.0 -2.0
    ]
    positions = [0.0 0.0 0.0]'
    types = [1]
    mesh = [4, 4, 4]
    is_shift = [0, 0, 0]
    cell = Cell(lattice, positions, types)
    nir, mapping, grid_address = get_ir_reciprocal_mesh(
        cell,
        mesh,
        is_shift;
        is_time_reversal = true,
        symprec = 1e-5,
    )
    @test nir == length(unique(mapping)) == 8
end

# See https://spglib.github.io/spglib/python-spglib.html
@testset "Python example" begin
    lattice = [[0.0, 0.5, 0.5], [0.5, 0.0, 0.5], [0.5, 0.5, 0.0]] * 5.4
    positions = [[0.875, 0.875, 0.875], [0.125, 0.125, 0.125]]
    types = [1, 1]
    cell = Cell(lattice, positions, types)
    mesh = [8, 8, 8]
    @test get_spacegroup_type(cell).international_short == "Fd-3m"
    @test get_spacegroup_type(cell).number == 227
    @testset "No shifts" begin
        shift = [0, 0, 0]
        nir, mapping, grid_address = get_ir_reciprocal_mesh(
            cell,
            mesh,
            shift;
            is_time_reversal = true,
            symprec = 1e-5,
        )
        python_mapping = [
            0,
            1,
            2,
            3,
            4,
            3,
            2,
            1,
            1,
            9,
            10,
            11,
            12,
            13,
            14,
            15,
            2,
            10,
            18,
            19,
            20,
            21,
            22,
            14,
            3,
            11,
            19,
            27,
            28,
            29,
            21,
            13,
            4,
            12,
            20,
            28,
            36,
            28,
            20,
            12,
            3,
            13,
            21,
            29,
            28,
            27,
            19,
            11,
            2,
            14,
            22,
            21,
            20,
            19,
            18,
            10,
            1,
            15,
            14,
            13,
            12,
            11,
            10,
            9,
            1,
            9,
            10,
            11,
            12,
            13,
            14,
            15,
            9,
            1,
            15,
            14,
            13,
            12,
            11,
            10,
            10,
            15,
            10,
            83,
            84,
            85,
            84,
            83,
            11,
            14,
            83,
            19,
            92,
            93,
            94,
            84,
            12,
            13,
            84,
            92,
            28,
            101,
            93,
            85,
            13,
            12,
            85,
            93,
            101,
            28,
            92,
            84,
            14,
            11,
            84,
            94,
            93,
            92,
            19,
            83,
            15,
            10,
            83,
            84,
            85,
            84,
            83,
            10,
            2,
            10,
            18,
            19,
            20,
            21,
            22,
            14,
            10,
            15,
            10,
            83,
            84,
            85,
            84,
            83,
            18,
            10,
            2,
            14,
            22,
            21,
            20,
            19,
            19,
            83,
            14,
            11,
            84,
            94,
            93,
            92,
            20,
            84,
            22,
            84,
            20,
            93,
            166,
            93,
            21,
            85,
            21,
            94,
            93,
            29,
            93,
            94,
            22,
            84,
            20,
            93,
            166,
            93,
            20,
            84,
            14,
            83,
            19,
            92,
            93,
            94,
            84,
            11,
            3,
            11,
            19,
            27,
            28,
            29,
            21,
            13,
            11,
            14,
            83,
            19,
            92,
            93,
            94,
            84,
            19,
            83,
            14,
            11,
            84,
            94,
            93,
            92,
            27,
            19,
            11,
            3,
            13,
            21,
            29,
            28,
            28,
            92,
            84,
            13,
            12,
            85,
            93,
            101,
            29,
            93,
            94,
            21,
            85,
            21,
            94,
            93,
            21,
            94,
            93,
            29,
            93,
            94,
            21,
            85,
            13,
            84,
            92,
            28,
            101,
            93,
            85,
            12,
            4,
            12,
            20,
            28,
            36,
            28,
            20,
            12,
            12,
            13,
            84,
            92,
            28,
            101,
            93,
            85,
            20,
            84,
            22,
            84,
            20,
            93,
            166,
            93,
            28,
            92,
            84,
            13,
            12,
            85,
            93,
            101,
            36,
            28,
            20,
            12,
            4,
            12,
            20,
            28,
            28,
            101,
            93,
            85,
            12,
            13,
            84,
            92,
            20,
            93,
            166,
            93,
            20,
            84,
            22,
            84,
            12,
            85,
            93,
            101,
            28,
            92,
            84,
            13,
            3,
            13,
            21,
            29,
            28,
            27,
            19,
            11,
            13,
            12,
            85,
            93,
            101,
            28,
            92,
            84,
            21,
            85,
            21,
            94,
            93,
            29,
            93,
            94,
            29,
            93,
            94,
            21,
            85,
            21,
            94,
            93,
            28,
            101,
            93,
            85,
            12,
            13,
            84,
            92,
            27,
            28,
            29,
            21,
            13,
            3,
            11,
            19,
            19,
            92,
            93,
            94,
            84,
            11,
            14,
            83,
            11,
            84,
            94,
            93,
            92,
            19,
            83,
            14,
            2,
            14,
            22,
            21,
            20,
            19,
            18,
            10,
            14,
            11,
            84,
            94,
            93,
            92,
            19,
            83,
            22,
            84,
            20,
            93,
            166,
            93,
            20,
            84,
            21,
            94,
            93,
            29,
            93,
            94,
            21,
            85,
            20,
            93,
            166,
            93,
            20,
            84,
            22,
            84,
            19,
            92,
            93,
            94,
            84,
            11,
            14,
            83,
            18,
            19,
            20,
            21,
            22,
            14,
            2,
            10,
            10,
            83,
            84,
            85,
            84,
            83,
            10,
            15,
            1,
            15,
            14,
            13,
            12,
            11,
            10,
            9,
            15,
            10,
            83,
            84,
            85,
            84,
            83,
            10,
            14,
            83,
            19,
            92,
            93,
            94,
            84,
            11,
            13,
            84,
            92,
            28,
            101,
            93,
            85,
            12,
            12,
            85,
            93,
            101,
            28,
            92,
            84,
            13,
            11,
            84,
            94,
            93,
            92,
            19,
            83,
            14,
            10,
            83,
            84,
            85,
            84,
            83,
            10,
            15,
            9,
            10,
            11,
            12,
            13,
            14,
            15,
            1,
        ]
        @test mapping == python_mapping .+ 1
        @test nir == length(unique(mapping)) == 29
        # Irreducible k-points
        python_results = [
            [0.0, 0.0, 0.0],
            [0.125, 0.0, 0.0],
            [0.25, 0.0, 0.0],
            [0.375, 0.0, 0.0],
            [0.5, 0.0, 0.0],
            [0.125, 0.125, 0.0],
            [0.25, 0.125, 0.0],
            [0.375, 0.125, 0.0],
            [0.5, 0.125, 0.0],
            [-0.375, 0.125, 0.0],
            [-0.25, 0.125, 0.0],
            [-0.125, 0.125, 0.0],
            [0.25, 0.25, 0.0],
            [0.375, 0.25, 0.0],
            [0.5, 0.25, 0.0],
            [-0.375, 0.25, 0.0],
            [-0.25, 0.25, 0.0],
            [0.375, 0.375, 0.0],
            [0.5, 0.375, 0.0],
            [-0.375, 0.375, 0.0],
            [0.5, 0.5, 0.0],
            [0.375, 0.25, 0.125],
            [0.5, 0.25, 0.125],
            [-0.375, 0.25, 0.125],
            [0.5, 0.375, 0.125],
            [-0.375, 0.375, 0.125],
            [-0.25, 0.375, 0.125],
            [-0.375, 0.5, 0.125],
            [-0.25, 0.5, 0.25],
        ]
        @test list_points(mapping, grid_address, mesh, shift, true) == python_results
        @test length(list_points(mapping, grid_address, mesh, shift, false)) == 512
    end
    @testset "With shifts" begin
        shift = [1, 1, 1]
        nir, mapping, grid_address = get_ir_reciprocal_mesh(
            cell,
            mesh,
            shift;
            is_time_reversal = true,
            symprec = 1e-5,
        )
        python_mapping = [
            0,
            1,
            2,
            3,
            4,
            5,
            6,
            7,
            1,
            9,
            10,
            11,
            12,
            13,
            14,
            15,
            2,
            10,
            18,
            19,
            20,
            21,
            22,
            23,
            3,
            11,
            19,
            27,
            28,
            29,
            30,
            31,
            4,
            12,
            20,
            28,
            36,
            37,
            38,
            31,
            5,
            13,
            21,
            29,
            37,
            45,
            46,
            23,
            6,
            14,
            22,
            30,
            38,
            46,
            54,
            15,
            7,
            15,
            23,
            31,
            31,
            23,
            15,
            7,
            1,
            9,
            10,
            11,
            12,
            13,
            14,
            15,
            9,
            73,
            74,
            75,
            76,
            77,
            78,
            54,
            10,
            74,
            82,
            83,
            84,
            85,
            86,
            46,
            11,
            75,
            83,
            91,
            92,
            93,
            94,
            38,
            12,
            76,
            84,
            92,
            100,
            101,
            94,
            30,
            13,
            77,
            85,
            93,
            101,
            109,
            86,
            22,
            14,
            78,
            86,
            94,
            94,
            86,
            78,
            14,
            15,
            54,
            46,
            38,
            30,
            22,
            14,
            6,
            2,
            10,
            18,
            19,
            20,
            21,
            22,
            23,
            10,
            74,
            82,
            83,
            84,
            85,
            86,
            46,
            18,
            82,
            146,
            147,
            148,
            149,
            109,
            45,
            19,
            83,
            147,
            155,
            156,
            157,
            101,
            37,
            20,
            84,
            148,
            156,
            164,
            157,
            93,
            29,
            21,
            85,
            149,
            157,
            157,
            149,
            85,
            21,
            22,
            86,
            109,
            101,
            93,
            85,
            77,
            13,
            23,
            46,
            45,
            37,
            29,
            21,
            13,
            5,
            3,
            11,
            19,
            27,
            28,
            29,
            30,
            31,
            11,
            75,
            83,
            91,
            92,
            93,
            94,
            38,
            19,
            83,
            147,
            155,
            156,
            157,
            101,
            37,
            27,
            91,
            155,
            219,
            220,
            164,
            100,
            36,
            28,
            92,
            156,
            220,
            220,
            156,
            92,
            28,
            29,
            93,
            157,
            164,
            156,
            148,
            84,
            20,
            30,
            94,
            101,
            100,
            92,
            84,
            76,
            12,
            31,
            38,
            37,
            36,
            28,
            20,
            12,
            4,
            4,
            12,
            20,
            28,
            36,
            37,
            38,
            31,
            12,
            76,
            84,
            92,
            100,
            101,
            94,
            30,
            20,
            84,
            148,
            156,
            164,
            157,
            93,
            29,
            28,
            92,
            156,
            220,
            220,
            156,
            92,
            28,
            36,
            100,
            164,
            220,
            219,
            155,
            91,
            27,
            37,
            101,
            157,
            156,
            155,
            147,
            83,
            19,
            38,
            94,
            93,
            92,
            91,
            83,
            75,
            11,
            31,
            30,
            29,
            28,
            27,
            19,
            11,
            3,
            5,
            13,
            21,
            29,
            37,
            45,
            46,
            23,
            13,
            77,
            85,
            93,
            101,
            109,
            86,
            22,
            21,
            85,
            149,
            157,
            157,
            149,
            85,
            21,
            29,
            93,
            157,
            164,
            156,
            148,
            84,
            20,
            37,
            101,
            157,
            156,
            155,
            147,
            83,
            19,
            45,
            109,
            149,
            148,
            147,
            146,
            82,
            18,
            46,
            86,
            85,
            84,
            83,
            82,
            74,
            10,
            23,
            22,
            21,
            20,
            19,
            18,
            10,
            2,
            6,
            14,
            22,
            30,
            38,
            46,
            54,
            15,
            14,
            78,
            86,
            94,
            94,
            86,
            78,
            14,
            22,
            86,
            109,
            101,
            93,
            85,
            77,
            13,
            30,
            94,
            101,
            100,
            92,
            84,
            76,
            12,
            38,
            94,
            93,
            92,
            91,
            83,
            75,
            11,
            46,
            86,
            85,
            84,
            83,
            82,
            74,
            10,
            54,
            78,
            77,
            76,
            75,
            74,
            73,
            9,
            15,
            14,
            13,
            12,
            11,
            10,
            9,
            1,
            7,
            15,
            23,
            31,
            31,
            23,
            15,
            7,
            15,
            54,
            46,
            38,
            30,
            22,
            14,
            6,
            23,
            46,
            45,
            37,
            29,
            21,
            13,
            5,
            31,
            38,
            37,
            36,
            28,
            20,
            12,
            4,
            31,
            30,
            29,
            28,
            27,
            19,
            11,
            3,
            23,
            22,
            21,
            20,
            19,
            18,
            10,
            2,
            15,
            14,
            13,
            12,
            11,
            10,
            9,
            1,
            7,
            6,
            5,
            4,
            3,
            2,
            1,
            0,
        ]
        @test mapping == python_mapping .+ 1
        @test nir == length(unique(mapping)) == 60
        # Irreducible k-points
        python_results = [
            [0.0625, 0.0625, 0.0625],
            [0.1875, 0.0625, 0.0625],
            [0.3125, 0.0625, 0.0625],
            [0.4375, 0.0625, 0.0625],
            [0.5625, 0.0625, 0.0625],
            [-0.3125, 0.0625, 0.0625],
            [-0.1875, 0.0625, 0.0625],
            [-0.0625, 0.0625, 0.0625],
            [0.1875, 0.1875, 0.0625],
            [0.3125, 0.1875, 0.0625],
            [0.4375, 0.1875, 0.0625],
            [0.5625, 0.1875, 0.0625],
            [-0.3125, 0.1875, 0.0625],
            [-0.1875, 0.1875, 0.0625],
            [-0.0625, 0.1875, 0.0625],
            [0.3125, 0.3125, 0.0625],
            [0.4375, 0.3125, 0.0625],
            [0.5625, 0.3125, 0.0625],
            [-0.3125, 0.3125, 0.0625],
            [-0.1875, 0.3125, 0.0625],
            [-0.0625, 0.3125, 0.0625],
            [0.4375, 0.4375, 0.0625],
            [0.5625, 0.4375, 0.0625],
            [-0.3125, 0.4375, 0.0625],
            [-0.1875, 0.4375, 0.0625],
            [-0.0625, 0.4375, 0.0625],
            [0.5625, 0.5625, 0.0625],
            [-0.3125, 0.5625, 0.0625],
            [-0.1875, 0.5625, 0.0625],
            [-0.3125, -0.3125, 0.0625],
            [-0.1875, -0.3125, 0.0625],
            [-0.1875, -0.1875, 0.0625],
            [0.1875, 0.1875, 0.1875],
            [0.3125, 0.1875, 0.1875],
            [0.4375, 0.1875, 0.1875],
            [0.5625, 0.1875, 0.1875],
            [-0.3125, 0.1875, 0.1875],
            [-0.1875, 0.1875, 0.1875],
            [0.3125, 0.3125, 0.1875],
            [0.4375, 0.3125, 0.1875],
            [0.5625, 0.3125, 0.1875],
            [-0.3125, 0.3125, 0.1875],
            [-0.1875, 0.3125, 0.1875],
            [0.4375, 0.4375, 0.1875],
            [0.5625, 0.4375, 0.1875],
            [-0.3125, 0.4375, 0.1875],
            [-0.1875, 0.4375, 0.1875],
            [0.5625, 0.5625, 0.1875],
            [-0.3125, 0.5625, 0.1875],
            [-0.3125, -0.3125, 0.1875],
            [0.3125, 0.3125, 0.3125],
            [0.4375, 0.3125, 0.3125],
            [0.5625, 0.3125, 0.3125],
            [-0.3125, 0.3125, 0.3125],
            [0.4375, 0.4375, 0.3125],
            [0.5625, 0.4375, 0.3125],
            [-0.3125, 0.4375, 0.3125],
            [0.5625, 0.5625, 0.3125],
            [0.4375, 0.4375, 0.4375],
            [0.5625, 0.4375, 0.4375],
        ]
        @test list_points(mapping, grid_address, mesh, shift, true) == python_results
        @test length(list_points(mapping, grid_address, mesh, shift, false)) == 512
    end
end

@testset "Test rutile structure" begin
    lattice = [
        4 0 0
        0 4 0
        0 0 3
    ]
    positions = [
        0.0 0.5 0.3 0.7 0.2 0.8
        0.0 0.5 0.3 0.7 0.8 0.2
        0.0 0.5 0.0 0.0 0.5 0.5
    ]
    types = [14, 14, 8, 8, 8, 8]
    rutile = Cell(lattice, positions, types)
    @testset "No shift" begin
        shift = [0, 0, 0]
        mesh = [6, 6, 6]
        nir, mapping, grid_address =
            get_ir_reciprocal_mesh(rutile, mesh; is_time_reversal = true, symprec = 1e-5)
        @test nir == length(unique(mapping)) == 40
        @test list_points(mapping, grid_address, mesh, shift, true) ≈ [
            [0.0, 0.0, 0.0],
            [0.16666667, 0.0, 0.0],
            [0.33333333, 0.0, 0.0],
            [0.5, 0.0, 0.0],
            [0.16666667, 0.16666667, 0.0],
            [0.33333333, 0.16666667, 0.0],
            [0.5, 0.16666667, 0.0],
            [0.33333333, 0.33333333, 0.0],
            [0.5, 0.33333333, 0.0],
            [0.5, 0.5, 0.0],
            [0.0, 0.0, 0.16666667],
            [0.16666667, 0.0, 0.16666667],
            [0.33333333, 0.0, 0.16666667],
            [0.5, 0.0, 0.16666667],
            [0.16666667, 0.16666667, 0.16666667],
            [0.33333333, 0.16666667, 0.16666667],
            [0.5, 0.16666667, 0.16666667],
            [0.33333333, 0.33333333, 0.16666667],
            [0.5, 0.33333333, 0.16666667],
            [0.5, 0.5, 0.16666667],
            [0.0, 0.0, 0.33333333],
            [0.16666667, 0.0, 0.33333333],
            [0.33333333, 0.0, 0.33333333],
            [0.5, 0.0, 0.33333333],
            [0.16666667, 0.16666667, 0.33333333],
            [0.33333333, 0.16666667, 0.33333333],
            [0.5, 0.16666667, 0.33333333],
            [0.33333333, 0.33333333, 0.33333333],
            [0.5, 0.33333333, 0.33333333],
            [0.5, 0.5, 0.33333333],
            [0.0, 0.0, 0.5],
            [0.16666667, 0.0, 0.5],
            [0.33333333, 0.0, 0.5],
            [0.5, 0.0, 0.5],
            [0.16666667, 0.16666667, 0.5],
            [0.33333333, 0.16666667, 0.5],
            [0.5, 0.16666667, 0.5],
            [0.33333333, 0.33333333, 0.5],
            [0.5, 0.33333333, 0.5],
            [0.5, 0.5, 0.5],
        ]
        @test list_points(mapping, grid_address, mesh, shift, false) ≈ [
            [0.0, 0.0, 0.0],
            [0.16666667, 0.0, 0.0],
            [0.33333333, 0.0, 0.0],
            [0.5, 0.0, 0.0],
            [-0.33333333, 0.0, 0.0],
            [-0.16666667, 0.0, 0.0],
            [0.0, 0.16666667, 0.0],
            [0.16666667, 0.16666667, 0.0],
            [0.33333333, 0.16666667, 0.0],
            [0.5, 0.16666667, 0.0],
            [-0.33333333, 0.16666667, 0.0],
            [-0.16666667, 0.16666667, 0.0],
            [0.0, 0.33333333, 0.0],
            [0.16666667, 0.33333333, 0.0],
            [0.33333333, 0.33333333, 0.0],
            [0.5, 0.33333333, 0.0],
            [-0.33333333, 0.33333333, 0.0],
            [-0.16666667, 0.33333333, 0.0],
            [0.0, 0.5, 0.0],
            [0.16666667, 0.5, 0.0],
            [0.33333333, 0.5, 0.0],
            [0.5, 0.5, 0.0],
            [-0.33333333, 0.5, 0.0],
            [-0.16666667, 0.5, 0.0],
            [0.0, -0.33333333, 0.0],
            [0.16666667, -0.33333333, 0.0],
            [0.33333333, -0.33333333, 0.0],
            [0.5, -0.33333333, 0.0],
            [-0.33333333, -0.33333333, 0.0],
            [-0.16666667, -0.33333333, 0.0],
            [0.0, -0.16666667, 0.0],
            [0.16666667, -0.16666667, 0.0],
            [0.33333333, -0.16666667, 0.0],
            [0.5, -0.16666667, 0.0],
            [-0.33333333, -0.16666667, 0.0],
            [-0.16666667, -0.16666667, 0.0],
            [0.0, 0.0, 0.16666667],
            [0.16666667, 0.0, 0.16666667],
            [0.33333333, 0.0, 0.16666667],
            [0.5, 0.0, 0.16666667],
            [-0.33333333, 0.0, 0.16666667],
            [-0.16666667, 0.0, 0.16666667],
            [0.0, 0.16666667, 0.16666667],
            [0.16666667, 0.16666667, 0.16666667],
            [0.33333333, 0.16666667, 0.16666667],
            [0.5, 0.16666667, 0.16666667],
            [-0.33333333, 0.16666667, 0.16666667],
            [-0.16666667, 0.16666667, 0.16666667],
            [0.0, 0.33333333, 0.16666667],
            [0.16666667, 0.33333333, 0.16666667],
            [0.33333333, 0.33333333, 0.16666667],
            [0.5, 0.33333333, 0.16666667],
            [-0.33333333, 0.33333333, 0.16666667],
            [-0.16666667, 0.33333333, 0.16666667],
            [0.0, 0.5, 0.16666667],
            [0.16666667, 0.5, 0.16666667],
            [0.33333333, 0.5, 0.16666667],
            [0.5, 0.5, 0.16666667],
            [-0.33333333, 0.5, 0.16666667],
            [-0.16666667, 0.5, 0.16666667],
            [0.0, -0.33333333, 0.16666667],
            [0.16666667, -0.33333333, 0.16666667],
            [0.33333333, -0.33333333, 0.16666667],
            [0.5, -0.33333333, 0.16666667],
            [-0.33333333, -0.33333333, 0.16666667],
            [-0.16666667, -0.33333333, 0.16666667],
            [0.0, -0.16666667, 0.16666667],
            [0.16666667, -0.16666667, 0.16666667],
            [0.33333333, -0.16666667, 0.16666667],
            [0.5, -0.16666667, 0.16666667],
            [-0.33333333, -0.16666667, 0.16666667],
            [-0.16666667, -0.16666667, 0.16666667],
            [0.0, 0.0, 0.33333333],
            [0.16666667, 0.0, 0.33333333],
            [0.33333333, 0.0, 0.33333333],
            [0.5, 0.0, 0.33333333],
            [-0.33333333, 0.0, 0.33333333],
            [-0.16666667, 0.0, 0.33333333],
            [0.0, 0.16666667, 0.33333333],
            [0.16666667, 0.16666667, 0.33333333],
            [0.33333333, 0.16666667, 0.33333333],
            [0.5, 0.16666667, 0.33333333],
            [-0.33333333, 0.16666667, 0.33333333],
            [-0.16666667, 0.16666667, 0.33333333],
            [0.0, 0.33333333, 0.33333333],
            [0.16666667, 0.33333333, 0.33333333],
            [0.33333333, 0.33333333, 0.33333333],
            [0.5, 0.33333333, 0.33333333],
            [-0.33333333, 0.33333333, 0.33333333],
            [-0.16666667, 0.33333333, 0.33333333],
            [0.0, 0.5, 0.33333333],
            [0.16666667, 0.5, 0.33333333],
            [0.33333333, 0.5, 0.33333333],
            [0.5, 0.5, 0.33333333],
            [-0.33333333, 0.5, 0.33333333],
            [-0.16666667, 0.5, 0.33333333],
            [0.0, -0.33333333, 0.33333333],
            [0.16666667, -0.33333333, 0.33333333],
            [0.33333333, -0.33333333, 0.33333333],
            [0.5, -0.33333333, 0.33333333],
            [-0.33333333, -0.33333333, 0.33333333],
            [-0.16666667, -0.33333333, 0.33333333],
            [0.0, -0.16666667, 0.33333333],
            [0.16666667, -0.16666667, 0.33333333],
            [0.33333333, -0.16666667, 0.33333333],
            [0.5, -0.16666667, 0.33333333],
            [-0.33333333, -0.16666667, 0.33333333],
            [-0.16666667, -0.16666667, 0.33333333],
            [0.0, 0.0, 0.5],
            [0.16666667, 0.0, 0.5],
            [0.33333333, 0.0, 0.5],
            [0.5, 0.0, 0.5],
            [-0.33333333, 0.0, 0.5],
            [-0.16666667, 0.0, 0.5],
            [0.0, 0.16666667, 0.5],
            [0.16666667, 0.16666667, 0.5],
            [0.33333333, 0.16666667, 0.5],
            [0.5, 0.16666667, 0.5],
            [-0.33333333, 0.16666667, 0.5],
            [-0.16666667, 0.16666667, 0.5],
            [0.0, 0.33333333, 0.5],
            [0.16666667, 0.33333333, 0.5],
            [0.33333333, 0.33333333, 0.5],
            [0.5, 0.33333333, 0.5],
            [-0.33333333, 0.33333333, 0.5],
            [-0.16666667, 0.33333333, 0.5],
            [0.0, 0.5, 0.5],
            [0.16666667, 0.5, 0.5],
            [0.33333333, 0.5, 0.5],
            [0.5, 0.5, 0.5],
            [-0.33333333, 0.5, 0.5],
            [-0.16666667, 0.5, 0.5],
            [0.0, -0.33333333, 0.5],
            [0.16666667, -0.33333333, 0.5],
            [0.33333333, -0.33333333, 0.5],
            [0.5, -0.33333333, 0.5],
            [-0.33333333, -0.33333333, 0.5],
            [-0.16666667, -0.33333333, 0.5],
            [0.0, -0.16666667, 0.5],
            [0.16666667, -0.16666667, 0.5],
            [0.33333333, -0.16666667, 0.5],
            [0.5, -0.16666667, 0.5],
            [-0.33333333, -0.16666667, 0.5],
            [-0.16666667, -0.16666667, 0.5],
            [0.0, 0.0, -0.33333333],
            [0.16666667, 0.0, -0.33333333],
            [0.33333333, 0.0, -0.33333333],
            [0.5, 0.0, -0.33333333],
            [-0.33333333, 0.0, -0.33333333],
            [-0.16666667, 0.0, -0.33333333],
            [0.0, 0.16666667, -0.33333333],
            [0.16666667, 0.16666667, -0.33333333],
            [0.33333333, 0.16666667, -0.33333333],
            [0.5, 0.16666667, -0.33333333],
            [-0.33333333, 0.16666667, -0.33333333],
            [-0.16666667, 0.16666667, -0.33333333],
            [0.0, 0.33333333, -0.33333333],
            [0.16666667, 0.33333333, -0.33333333],
            [0.33333333, 0.33333333, -0.33333333],
            [0.5, 0.33333333, -0.33333333],
            [-0.33333333, 0.33333333, -0.33333333],
            [-0.16666667, 0.33333333, -0.33333333],
            [0.0, 0.5, -0.33333333],
            [0.16666667, 0.5, -0.33333333],
            [0.33333333, 0.5, -0.33333333],
            [0.5, 0.5, -0.33333333],
            [-0.33333333, 0.5, -0.33333333],
            [-0.16666667, 0.5, -0.33333333],
            [0.0, -0.33333333, -0.33333333],
            [0.16666667, -0.33333333, -0.33333333],
            [0.33333333, -0.33333333, -0.33333333],
            [0.5, -0.33333333, -0.33333333],
            [-0.33333333, -0.33333333, -0.33333333],
            [-0.16666667, -0.33333333, -0.33333333],
            [0.0, -0.16666667, -0.33333333],
            [0.16666667, -0.16666667, -0.33333333],
            [0.33333333, -0.16666667, -0.33333333],
            [0.5, -0.16666667, -0.33333333],
            [-0.33333333, -0.16666667, -0.33333333],
            [-0.16666667, -0.16666667, -0.33333333],
            [0.0, 0.0, -0.16666667],
            [0.16666667, 0.0, -0.16666667],
            [0.33333333, 0.0, -0.16666667],
            [0.5, 0.0, -0.16666667],
            [-0.33333333, 0.0, -0.16666667],
            [-0.16666667, 0.0, -0.16666667],
            [0.0, 0.16666667, -0.16666667],
            [0.16666667, 0.16666667, -0.16666667],
            [0.33333333, 0.16666667, -0.16666667],
            [0.5, 0.16666667, -0.16666667],
            [-0.33333333, 0.16666667, -0.16666667],
            [-0.16666667, 0.16666667, -0.16666667],
            [0.0, 0.33333333, -0.16666667],
            [0.16666667, 0.33333333, -0.16666667],
            [0.33333333, 0.33333333, -0.16666667],
            [0.5, 0.33333333, -0.16666667],
            [-0.33333333, 0.33333333, -0.16666667],
            [-0.16666667, 0.33333333, -0.16666667],
            [0.0, 0.5, -0.16666667],
            [0.16666667, 0.5, -0.16666667],
            [0.33333333, 0.5, -0.16666667],
            [0.5, 0.5, -0.16666667],
            [-0.33333333, 0.5, -0.16666667],
            [-0.16666667, 0.5, -0.16666667],
            [0.0, -0.33333333, -0.16666667],
            [0.16666667, -0.33333333, -0.16666667],
            [0.33333333, -0.33333333, -0.16666667],
            [0.5, -0.33333333, -0.16666667],
            [-0.33333333, -0.33333333, -0.16666667],
            [-0.16666667, -0.33333333, -0.16666667],
            [0.0, -0.16666667, -0.16666667],
            [0.16666667, -0.16666667, -0.16666667],
            [0.33333333, -0.16666667, -0.16666667],
            [0.5, -0.16666667, -0.16666667],
            [-0.33333333, -0.16666667, -0.16666667],
            [-0.16666667, -0.16666667, -0.16666667],
        ]
    end
    @testset "With shift" begin
        shift = trues(3)
        @testset "6×6×6" begin
            mesh = [6, 6, 6]
            nir, mapping, grid_address = get_ir_reciprocal_mesh(
                rutile,
                mesh,
                shift;
                is_time_reversal = true,
                symprec = 1e-5,
            )
            @test nir == length(unique(mapping)) == 18
            @test list_points(mapping, grid_address, mesh, shift, true) ≈ [
                [0.08333333, 0.08333333, 0.08333333],
                [0.25, 0.08333333, 0.08333333],
                [0.41666667, 0.08333333, 0.08333333],
                [0.25, 0.25, 0.08333333],
                [0.41666667, 0.25, 0.08333333],
                [0.41666667, 0.41666667, 0.08333333],
                [0.08333333, 0.08333333, 0.25],
                [0.25, 0.08333333, 0.25],
                [0.41666667, 0.08333333, 0.25],
                [0.25, 0.25, 0.25],
                [0.41666667, 0.25, 0.25],
                [0.41666667, 0.41666667, 0.25],
                [0.08333333, 0.08333333, 0.41666667],
                [0.25, 0.08333333, 0.41666667],
                [0.41666667, 0.08333333, 0.41666667],
                [0.25, 0.25, 0.41666667],
                [0.41666667, 0.25, 0.41666667],
                [0.41666667, 0.41666667, 0.41666667],
            ]
        end
        @testset "4×4×4" begin
            mesh = [4, 4, 4]
            nir, mapping, grid_address = get_ir_reciprocal_mesh(
                rutile,
                mesh,
                shift;
                is_time_reversal = true,
                symprec = 1e-5,
            )
            @test nir == length(unique(mapping)) == 6
            @test list_points(mapping, grid_address, mesh, shift, true) == [
                [0.125, 0.125, 0.125],
                [0.375, 0.125, 0.125],
                [0.375, 0.375, 0.125],
                [0.125, 0.125, 0.375],
                [0.375, 0.125, 0.375],
                [0.375, 0.375, 0.375],
            ]
            @test list_points(mapping, grid_address, mesh, shift, false) == [
                [0.125, 0.125, 0.125],
                [0.375, 0.125, 0.125],
                [0.625, 0.125, 0.125],
                [-0.125, 0.125, 0.125],
                [0.125, 0.375, 0.125],
                [0.375, 0.375, 0.125],
                [0.625, 0.375, 0.125],
                [-0.125, 0.375, 0.125],
                [0.125, 0.625, 0.125],
                [0.375, 0.625, 0.125],
                [0.625, 0.625, 0.125],
                [-0.125, 0.625, 0.125],
                [0.125, -0.125, 0.125],
                [0.375, -0.125, 0.125],
                [0.625, -0.125, 0.125],
                [-0.125, -0.125, 0.125],
                [0.125, 0.125, 0.375],
                [0.375, 0.125, 0.375],
                [0.625, 0.125, 0.375],
                [-0.125, 0.125, 0.375],
                [0.125, 0.375, 0.375],
                [0.375, 0.375, 0.375],
                [0.625, 0.375, 0.375],
                [-0.125, 0.375, 0.375],
                [0.125, 0.625, 0.375],
                [0.375, 0.625, 0.375],
                [0.625, 0.625, 0.375],
                [-0.125, 0.625, 0.375],
                [0.125, -0.125, 0.375],
                [0.375, -0.125, 0.375],
                [0.625, -0.125, 0.375],
                [-0.125, -0.125, 0.375],
                [0.125, 0.125, 0.625],
                [0.375, 0.125, 0.625],
                [0.625, 0.125, 0.625],
                [-0.125, 0.125, 0.625],
                [0.125, 0.375, 0.625],
                [0.375, 0.375, 0.625],
                [0.625, 0.375, 0.625],
                [-0.125, 0.375, 0.625],
                [0.125, 0.625, 0.625],
                [0.375, 0.625, 0.625],
                [0.625, 0.625, 0.625],
                [-0.125, 0.625, 0.625],
                [0.125, -0.125, 0.625],
                [0.375, -0.125, 0.625],
                [0.625, -0.125, 0.625],
                [-0.125, -0.125, 0.625],
                [0.125, 0.125, -0.125],
                [0.375, 0.125, -0.125],
                [0.625, 0.125, -0.125],
                [-0.125, 0.125, -0.125],
                [0.125, 0.375, -0.125],
                [0.375, 0.375, -0.125],
                [0.625, 0.375, -0.125],
                [-0.125, 0.375, -0.125],
                [0.125, 0.625, -0.125],
                [0.375, 0.625, -0.125],
                [0.625, 0.625, -0.125],
                [-0.125, 0.625, -0.125],
                [0.125, -0.125, -0.125],
                [0.375, -0.125, -0.125],
                [0.625, -0.125, -0.125],
                [-0.125, -0.125, -0.125],
            ]
        end
        @testset "5×5×5" begin
            mesh = [5, 5, 5]
            nir, mapping, grid_address = get_ir_reciprocal_mesh(
                rutile,
                mesh,
                shift;
                is_time_reversal = true,
                symprec = 1e-5,
            )
            @test nir == length(unique(mapping)) == 18
            @test list_points(mapping, grid_address, mesh, shift, true) == [
                [0.1, 0.1, 0.1],
                [0.3, 0.1, 0.1],
                [0.5, 0.1, 0.1],
                [0.3, 0.3, 0.1],
                [0.5, 0.3, 0.1],
                [0.5, 0.5, 0.1],
                [0.1, 0.1, 0.3],
                [0.3, 0.1, 0.3],
                [0.5, 0.1, 0.3],
                [0.3, 0.3, 0.3],
                [0.5, 0.3, 0.3],
                [0.5, 0.5, 0.3],
                [0.1, 0.1, 0.5],
                [0.3, 0.1, 0.5],
                [0.5, 0.1, 0.5],
                [0.3, 0.3, 0.5],
                [0.5, 0.3, 0.5],
                [0.5, 0.5, 0.5],
            ]
            @test list_points(mapping, grid_address, mesh, shift, false) == [
                [0.1, 0.1, 0.1],
                [0.3, 0.1, 0.1],
                [0.5, 0.1, 0.1],
                [-0.3, 0.1, 0.1],
                [-0.1, 0.1, 0.1],
                [0.1, 0.3, 0.1],
                [0.3, 0.3, 0.1],
                [0.5, 0.3, 0.1],
                [-0.3, 0.3, 0.1],
                [-0.1, 0.3, 0.1],
                [0.1, 0.5, 0.1],
                [0.3, 0.5, 0.1],
                [0.5, 0.5, 0.1],
                [-0.3, 0.5, 0.1],
                [-0.1, 0.5, 0.1],
                [0.1, -0.3, 0.1],
                [0.3, -0.3, 0.1],
                [0.5, -0.3, 0.1],
                [-0.3, -0.3, 0.1],
                [-0.1, -0.3, 0.1],
                [0.1, -0.1, 0.1],
                [0.3, -0.1, 0.1],
                [0.5, -0.1, 0.1],
                [-0.3, -0.1, 0.1],
                [-0.1, -0.1, 0.1],
                [0.1, 0.1, 0.3],
                [0.3, 0.1, 0.3],
                [0.5, 0.1, 0.3],
                [-0.3, 0.1, 0.3],
                [-0.1, 0.1, 0.3],
                [0.1, 0.3, 0.3],
                [0.3, 0.3, 0.3],
                [0.5, 0.3, 0.3],
                [-0.3, 0.3, 0.3],
                [-0.1, 0.3, 0.3],
                [0.1, 0.5, 0.3],
                [0.3, 0.5, 0.3],
                [0.5, 0.5, 0.3],
                [-0.3, 0.5, 0.3],
                [-0.1, 0.5, 0.3],
                [0.1, -0.3, 0.3],
                [0.3, -0.3, 0.3],
                [0.5, -0.3, 0.3],
                [-0.3, -0.3, 0.3],
                [-0.1, -0.3, 0.3],
                [0.1, -0.1, 0.3],
                [0.3, -0.1, 0.3],
                [0.5, -0.1, 0.3],
                [-0.3, -0.1, 0.3],
                [-0.1, -0.1, 0.3],
                [0.1, 0.1, 0.5],
                [0.3, 0.1, 0.5],
                [0.5, 0.1, 0.5],
                [-0.3, 0.1, 0.5],
                [-0.1, 0.1, 0.5],
                [0.1, 0.3, 0.5],
                [0.3, 0.3, 0.5],
                [0.5, 0.3, 0.5],
                [-0.3, 0.3, 0.5],
                [-0.1, 0.3, 0.5],
                [0.1, 0.5, 0.5],
                [0.3, 0.5, 0.5],
                [0.5, 0.5, 0.5],
                [-0.3, 0.5, 0.5],
                [-0.1, 0.5, 0.5],
                [0.1, -0.3, 0.5],
                [0.3, -0.3, 0.5],
                [0.5, -0.3, 0.5],
                [-0.3, -0.3, 0.5],
                [-0.1, -0.3, 0.5],
                [0.1, -0.1, 0.5],
                [0.3, -0.1, 0.5],
                [0.5, -0.1, 0.5],
                [-0.3, -0.1, 0.5],
                [-0.1, -0.1, 0.5],
                [0.1, 0.1, -0.3],
                [0.3, 0.1, -0.3],
                [0.5, 0.1, -0.3],
                [-0.3, 0.1, -0.3],
                [-0.1, 0.1, -0.3],
                [0.1, 0.3, -0.3],
                [0.3, 0.3, -0.3],
                [0.5, 0.3, -0.3],
                [-0.3, 0.3, -0.3],
                [-0.1, 0.3, -0.3],
                [0.1, 0.5, -0.3],
                [0.3, 0.5, -0.3],
                [0.5, 0.5, -0.3],
                [-0.3, 0.5, -0.3],
                [-0.1, 0.5, -0.3],
                [0.1, -0.3, -0.3],
                [0.3, -0.3, -0.3],
                [0.5, -0.3, -0.3],
                [-0.3, -0.3, -0.3],
                [-0.1, -0.3, -0.3],
                [0.1, -0.1, -0.3],
                [0.3, -0.1, -0.3],
                [0.5, -0.1, -0.3],
                [-0.3, -0.1, -0.3],
                [-0.1, -0.1, -0.3],
                [0.1, 0.1, -0.1],
                [0.3, 0.1, -0.1],
                [0.5, 0.1, -0.1],
                [-0.3, 0.1, -0.1],
                [-0.1, 0.1, -0.1],
                [0.1, 0.3, -0.1],
                [0.3, 0.3, -0.1],
                [0.5, 0.3, -0.1],
                [-0.3, 0.3, -0.1],
                [-0.1, 0.3, -0.1],
                [0.1, 0.5, -0.1],
                [0.3, 0.5, -0.1],
                [0.5, 0.5, -0.1],
                [-0.3, 0.5, -0.1],
                [-0.1, 0.5, -0.1],
                [0.1, -0.3, -0.1],
                [0.3, -0.3, -0.1],
                [0.5, -0.3, -0.1],
                [-0.3, -0.3, -0.1],
                [-0.1, -0.3, -0.1],
                [0.1, -0.1, -0.1],
                [0.3, -0.1, -0.1],
                [0.5, -0.1, -0.1],
                [-0.3, -0.1, -0.1],
                [-0.1, -0.1, -0.1],
            ]
        end
    end
end

@testset "Test distorted silicon structure" begin
    lattice = [
        4.01 0 0
        0 4 0
        0 0 3.99
    ]
    positions = [
        0.001 0 0
        0 0.5 0.5
        0.5 0 0.5
        0.5 0.5 0
        0.25 0.25 0.251
        0.25 0.75 0.75
        0.75 0.25 0.75
        0.75 0.75 0.25
    ]
    types = [14, 14, 14, 14, 14, 14, 14, 14]
    silicon_dist = Cell(lattice, positions, types)
    mesh = [6, 6, 6]
    nir, mapping, grid_address =
        get_ir_reciprocal_mesh(silicon_dist, mesh; is_time_reversal = true, symprec = 1e-5)
    @test nir == length(unique(mapping)) == 112
    @test list_points(mapping, grid_address, mesh, [0, 0, 0], true) ≈ [
        [0.0, 0.0, 0.0],
        [0.16666667, 0.0, 0.0],
        [0.33333333, 0.0, 0.0],
        [0.5, 0.0, 0.0],
        [0.0, 0.16666667, 0.0],
        [0.16666667, 0.16666667, 0.0],
        [0.33333333, 0.16666667, 0.0],
        [0.5, 0.16666667, 0.0],
        [-0.33333333, 0.16666667, 0.0],
        [-0.16666667, 0.16666667, 0.0],
        [0.0, 0.33333333, 0.0],
        [0.16666667, 0.33333333, 0.0],
        [0.33333333, 0.33333333, 0.0],
        [0.5, 0.33333333, 0.0],
        [-0.33333333, 0.33333333, 0.0],
        [-0.16666667, 0.33333333, 0.0],
        [0.0, 0.5, 0.0],
        [0.16666667, 0.5, 0.0],
        [0.33333333, 0.5, 0.0],
        [0.5, 0.5, 0.0],
        [0.0, 0.0, 0.16666667],
        [0.16666667, 0.0, 0.16666667],
        [0.33333333, 0.0, 0.16666667],
        [0.5, 0.0, 0.16666667],
        [-0.33333333, 0.0, 0.16666667],
        [-0.16666667, 0.0, 0.16666667],
        [0.0, 0.16666667, 0.16666667],
        [0.16666667, 0.16666667, 0.16666667],
        [0.33333333, 0.16666667, 0.16666667],
        [0.5, 0.16666667, 0.16666667],
        [-0.33333333, 0.16666667, 0.16666667],
        [-0.16666667, 0.16666667, 0.16666667],
        [0.0, 0.33333333, 0.16666667],
        [0.16666667, 0.33333333, 0.16666667],
        [0.33333333, 0.33333333, 0.16666667],
        [0.5, 0.33333333, 0.16666667],
        [-0.33333333, 0.33333333, 0.16666667],
        [-0.16666667, 0.33333333, 0.16666667],
        [0.0, 0.5, 0.16666667],
        [0.16666667, 0.5, 0.16666667],
        [0.33333333, 0.5, 0.16666667],
        [0.5, 0.5, 0.16666667],
        [-0.33333333, 0.5, 0.16666667],
        [-0.16666667, 0.5, 0.16666667],
        [0.0, -0.33333333, 0.16666667],
        [0.16666667, -0.33333333, 0.16666667],
        [0.33333333, -0.33333333, 0.16666667],
        [0.5, -0.33333333, 0.16666667],
        [-0.33333333, -0.33333333, 0.16666667],
        [-0.16666667, -0.33333333, 0.16666667],
        [0.0, -0.16666667, 0.16666667],
        [0.16666667, -0.16666667, 0.16666667],
        [0.33333333, -0.16666667, 0.16666667],
        [0.5, -0.16666667, 0.16666667],
        [-0.33333333, -0.16666667, 0.16666667],
        [-0.16666667, -0.16666667, 0.16666667],
        [0.0, 0.0, 0.33333333],
        [0.16666667, 0.0, 0.33333333],
        [0.33333333, 0.0, 0.33333333],
        [0.5, 0.0, 0.33333333],
        [-0.33333333, 0.0, 0.33333333],
        [-0.16666667, 0.0, 0.33333333],
        [0.0, 0.16666667, 0.33333333],
        [0.16666667, 0.16666667, 0.33333333],
        [0.33333333, 0.16666667, 0.33333333],
        [0.5, 0.16666667, 0.33333333],
        [-0.33333333, 0.16666667, 0.33333333],
        [-0.16666667, 0.16666667, 0.33333333],
        [0.0, 0.33333333, 0.33333333],
        [0.16666667, 0.33333333, 0.33333333],
        [0.33333333, 0.33333333, 0.33333333],
        [0.5, 0.33333333, 0.33333333],
        [-0.33333333, 0.33333333, 0.33333333],
        [-0.16666667, 0.33333333, 0.33333333],
        [0.0, 0.5, 0.33333333],
        [0.16666667, 0.5, 0.33333333],
        [0.33333333, 0.5, 0.33333333],
        [0.5, 0.5, 0.33333333],
        [-0.33333333, 0.5, 0.33333333],
        [-0.16666667, 0.5, 0.33333333],
        [0.0, -0.33333333, 0.33333333],
        [0.16666667, -0.33333333, 0.33333333],
        [0.33333333, -0.33333333, 0.33333333],
        [0.5, -0.33333333, 0.33333333],
        [-0.33333333, -0.33333333, 0.33333333],
        [-0.16666667, -0.33333333, 0.33333333],
        [0.0, -0.16666667, 0.33333333],
        [0.16666667, -0.16666667, 0.33333333],
        [0.33333333, -0.16666667, 0.33333333],
        [0.5, -0.16666667, 0.33333333],
        [-0.33333333, -0.16666667, 0.33333333],
        [-0.16666667, -0.16666667, 0.33333333],
        [0.0, 0.0, 0.5],
        [0.16666667, 0.0, 0.5],
        [0.33333333, 0.0, 0.5],
        [0.5, 0.0, 0.5],
        [0.0, 0.16666667, 0.5],
        [0.16666667, 0.16666667, 0.5],
        [0.33333333, 0.16666667, 0.5],
        [0.5, 0.16666667, 0.5],
        [-0.33333333, 0.16666667, 0.5],
        [-0.16666667, 0.16666667, 0.5],
        [0.0, 0.33333333, 0.5],
        [0.16666667, 0.33333333, 0.5],
        [0.33333333, 0.33333333, 0.5],
        [0.5, 0.33333333, 0.5],
        [-0.33333333, 0.33333333, 0.5],
        [-0.16666667, 0.33333333, 0.5],
        [0.0, 0.5, 0.5],
        [0.16666667, 0.5, 0.5],
        [0.33333333, 0.5, 0.5],
        [0.5, 0.5, 0.5],
    ]
end

@testset "Test MgB₂ structure" begin
    lattice = [[3.07, 0.0, 0.0], [-1.535, 2.65869799, 0.0], [0.0, 0.0, 3.52]]
    positions = [[0, 0, 0], [0.33333333, 0.66666667, 0.5], [0.66666667, 0.33333333, 0.5]]
    types = [12, 5, 5]
    MgB₂ = Cell(lattice, positions, types)
    mesh = [7, 7, 7]
    nir, mapping, grid_address =
        get_ir_reciprocal_mesh(MgB₂, mesh; is_time_reversal = true, symprec = 1e-5)
    @test nir == length(unique(mapping)) == 32
    @test list_points(mapping, grid_address, mesh, [0, 0, 0], true) ≈ [
        [0.0, 0.0, 0.0],
        [0.14285714, 0.0, 0.0],
        [0.28571429, 0.0, 0.0],
        [0.42857143, 0.0, 0.0],
        [0.14285714, 0.14285714, 0.0],
        [0.28571429, 0.14285714, 0.0],
        [0.42857143, 0.14285714, 0.0],
        [0.28571429, 0.28571429, 0.0],
        [0.0, 0.0, 0.14285714],
        [0.14285714, 0.0, 0.14285714],
        [0.28571429, 0.0, 0.14285714],
        [0.42857143, 0.0, 0.14285714],
        [0.14285714, 0.14285714, 0.14285714],
        [0.28571429, 0.14285714, 0.14285714],
        [0.42857143, 0.14285714, 0.14285714],
        [0.28571429, 0.28571429, 0.14285714],
        [0.0, 0.0, 0.28571429],
        [0.14285714, 0.0, 0.28571429],
        [0.28571429, 0.0, 0.28571429],
        [0.42857143, 0.0, 0.28571429],
        [0.14285714, 0.14285714, 0.28571429],
        [0.28571429, 0.14285714, 0.28571429],
        [0.42857143, 0.14285714, 0.28571429],
        [0.28571429, 0.28571429, 0.28571429],
        [0.0, 0.0, 0.42857143],
        [0.14285714, 0.0, 0.42857143],
        [0.28571429, 0.0, 0.42857143],
        [0.42857143, 0.0, 0.42857143],
        [0.14285714, 0.14285714, 0.42857143],
        [0.28571429, 0.14285714, 0.42857143],
        [0.42857143, 0.14285714, 0.42857143],
        [0.28571429, 0.28571429, 0.42857143],
    ]
    @test length(list_points(mapping, grid_address, mesh, [0, 0, 0], false)) == 343
end


## extended methods for computing covariance and scatter matrix

# auxiliary functions

function _symmetrize!(a::DenseMatrix)
    m, n = size(a)
    m == n || error("a must be a square matrix.")
    for j = 1:n
        @inbounds for i = j+1:n
            vl = a[i,j]
            vr = a[j,i]
            a[i,j] = a[j,i] = middle(vl, vr)
        end
    end
    return a
end

function _scalevars(x::DenseMatrix, s::DenseVector, dims::Int)
    dims == 1 ? Diagonal(s) * x :
    dims == 2 ? x * Diagonal(s) :
    error("dims should be either 1 or 2.")
end

## scatter matrix

_unscaled_covzm(x::DenseMatrix, dims::Colon)   = unscaled_covzm(x)
_unscaled_covzm(x::DenseMatrix, dims::Integer) = unscaled_covzm(x, dims)

_unscaled_covzm(x::DenseMatrix, wv::AbstractWeights, dims::Colon)   =
    _symmetrize!(unscaled_covzm(x, _scalevars(x, convert(Vector, wv))))
_unscaled_covzm(x::DenseMatrix, wv::AbstractWeights, dims::Integer) =
    _symmetrize!(unscaled_covzm(x, _scalevars(x, convert(Vector, wv), dims), dims))

"""
    scattermat(X, [wv::AbstractWeights]; mean=nothing, dims=1)

Compute the scatter matrix, which is an unnormalized covariance matrix.
A weighting vector `wv` can be specified to weight
the estimate.

# Arguments
* `mean=nothing`: a known mean value. `nothing` indicates that the mean is
  unknown, and the function will compute the mean. Specifying `mean=0` indicates
  that the data are centered and hence there's no need to subtract the mean.
* `dims=1`: the dimension along which the variables are organized.
  When `dims = 1`, the variables are considered columns with observations in rows;
  when `dims = 2`, variables are in rows with observations in columns.
"""
function scattermat end

"""
    cov(X, w::AbstractWeights, vardim=1; mean=nothing,  corrected=false)

Compute the weighted covariance matrix. Similar to `var` and `std` the biased covariance
matrix (`corrected=false`) is computed by multiplying `scattermat(X, w)` by
``\\frac{1}{\\sum{w}}`` to normalize. However, the unbiased covariance matrix
(`corrected=true`) is dependent on the type of weights used:
* `AnalyticWeights`: ``\\frac{1}{\\sum w - \\sum {w^2} / \\sum w}``
* `FrequencyWeights`: ``\\frac{1}{\\sum{w} - 1}``
* `ProbabilityWeights`: ``\\frac{n}{(n - 1) \\sum w}`` where ``n`` equals `count(!iszero, w)`
* `Weights`: `ArgumentError` (bias correction not supported)
"""
cov

"""
    mean_and_cov(x, [wv::AbstractWeights,] vardim=1; corrected=false) -> (mean, cov)

Return the mean and covariance matrix as a tuple. A weighting
vector `wv` can be specified. `vardim` that designates whether
the variables are columns in the matrix (`1`) or rows (`2`).
Finally, bias correction is applied to the covariance calculation if
`corrected=true`. See [`cov`](@ref) documentation for more details.
"""
function mean_and_cov end

scattermat(x::DenseMatrix; mean=nothing, dims::Int=1) =
    _scattermatm(x, mean, dims)
_scattermatm(x::DenseMatrix, ::Nothing, dims::Int) =
    _unscaled_covzm(x .- mean(x, dims=dims), dims)
_scattermatm(x::DenseMatrix, mean, dims::Int=1) =
    _unscaled_covzm(x .- mean, dims)

scattermat(x::DenseMatrix, wv::AbstractWeights; mean=nothing, dims::Int=1) =
    _scattermatm(x, wv, mean, dims)
_scattermatm(x::DenseMatrix, wv::AbstractWeights, ::Nothing, dims::Int) =
    _unscaled_covzm(x .- mean(x, wv, dims=dims), wv, dims)
_scattermatm(x::DenseMatrix, wv::AbstractWeights, mean, dims::Int) =
    _unscaled_covzm(x .- mean, wv, dims)

## weighted cov
covm(x::DenseMatrix, mean, w::AbstractWeights, dims::Int=1;
     corrected::DepBool=nothing) =
    rmul!(scattermat(x, w, mean=mean, dims=dims), varcorrection(w, depcheck(:covm, corrected)))

cov(x::DenseMatrix, w::AbstractWeights, dims::Int=1; corrected::DepBool=nothing) =
    covm(x, mean(x, w, dims=dims), w, dims; corrected=depcheck(:cov, corrected))

function corm(x::DenseMatrix, mean, w::AbstractWeights, vardim::Int=1)
    c = covm(x, mean, w, vardim; corrected=false)
    s = stdm(x, w, mean, vardim; corrected=false)
    cov2cor!(c, s)
end

"""
    cor(X, w::AbstractWeights, dims=1)

Compute the Pearson correlation matrix of `X` along the dimension
`dims` with a weighting `w` .
"""
cor(x::DenseMatrix, w::AbstractWeights, dims::Int=1) =
    corm(x, mean(x, w, dims=dims), w, dims)

function mean_and_cov(x::DenseMatrix, dims::Int=1; corrected::Bool=true)
    m = mean(x, dims=dims)
    return m, covm(x, m, dims, corrected=corrected)
end
function mean_and_cov(x::DenseMatrix, wv::AbstractWeights, dims::Int=1;
                      corrected::DepBool=nothing)
    m = mean(x, wv, dims=dims)
    return m, cov(x, wv, dims; corrected=depcheck(:mean_and_cov, corrected))
end

"""
    cov2cor(C, s)

Compute the correlation matrix from the covariance matrix `C` and a vector of standard
deviations `s`. Use `StatsBase.cov2cor!` for an in-place version.
"""
cov2cor(C::AbstractMatrix, s::AbstractArray) = cov2cor!(copy(C), s)

"""
    cor2cov(C, s)

Compute the covariance matrix from the correlation matrix `C` and a vector of standard
deviations `s`. Use `StatsBase.cor2cov!` for an in-place version.
"""
cor2cov(C::AbstractMatrix, s::AbstractArray) = cor2cov!(copy(C), s)

"""
    cor2cov!(C, s)

Converts the correlation matrix `C` to a covariance matrix in-place using a vector of
standard deviations `s`.
"""
function cor2cov!(C::AbstractMatrix, s::AbstractArray)
    n = length(s)
    size(C) == (n, n) || throw(DimensionMismatch("inconsistent dimensions"))
    for i in CartesianIndices(size(C))
        @inbounds C[i] *= s[i[1]] * s[i[2]]
    end
    return C
end

"""
    CovarianceEstimator

Abstract type for covariance estimators.
"""
abstract type CovarianceEstimator end

"""
    cov(ce::CovarianceEstimator, x::AbstractVector; mean=nothing)

Compute a variance estimate from the observation vector `x` using the  estimator `ce`.
"""
cov(ce::CovarianceEstimator, x::AbstractVector; mean=nothing) =
    error("cov is not defined for $(typeof(ce)) and $(typeof(x))")

"""
    cov(ce::CovarianceEstimator, x::AbstractVector, y::AbstractVector)

Compute the covariance of the vectors `x` and `y` using estimator `ce`.
"""
cov(ce::CovarianceEstimator, x::AbstractVector, y::AbstractVector) =
    error("cov is not defined for $(typeof(ce)), $(typeof(x)) and $(typeof(y))")

"""
    cov(ce::CovarianceEstimator, X::AbstractMatrix, [w::AbstractWeights]; mean=nothing, dims::Int=1)

Compute the covariance matrix of the matrix `X` along dimension `dims`
using estimator `ce`. A weighting vector `w` can be specified.
The keyword argument `mean` can be:

* `nothing` (default) in which case the mean is estimated and subtracted
  from the data `X`,
* a precalculated mean in which case it is subtracted from the data `X`.
  Assuming `size(X)` is `(N,M)`, `mean` can either be:
  * when `dims=1`, an `AbstractMatrix` of size `(1,M)`,
  * when `dims=2`, an `AbstractVector` of length `N` or an `AbstractMatrix`
    of size `(N,1)`.
"""
cov(ce::CovarianceEstimator, X::AbstractMatrix; mean=nothing, dims::Int=1) =
    error("cov is not defined for $(typeof(ce)) and $(typeof(X))")

cov(ce::CovarianceEstimator, X::AbstractMatrix, w::AbstractWeights; mean=nothing, dims::Int=1) =
    error("cov is not defined for $(typeof(ce)), $(typeof(X)) and $(typeof(w))")

"""
    SimpleCovariance(;corrected::Bool=false)

Simple covariance estimator. Estimation calls `cov(x; corrected=corrected)`,
`cov(x, y; corrected=corrected)` or `cov(X, w, dims; corrected=corrected)`
where `x`, `y` are vectors, `X` is a matrix and `w` is a weighting vector.
"""
struct SimpleCovariance <: CovarianceEstimator
    corrected::Bool
    SimpleCovariance(;corrected::Bool=false) = new(corrected)
end

cov(sc::SimpleCovariance, x::AbstractVector) =
    cov(x; corrected=sc.corrected)

cov(sc::SimpleCovariance, x::AbstractVector, y::AbstractVector) =
    cov(x, y; corrected=sc.corrected)

function cov(sc::SimpleCovariance, X::AbstractMatrix; dims::Int=1, mean=nothing)
    dims ∈ (1, 2) || throw(ArgumentError("Argument dims can only be 1 or 2 (given: $dims)"))
    if mean === nothing
        return cov(X; dims=dims, corrected=sc.corrected)
    else
        return covm(X, mean, dims, corrected=sc.corrected)
    end
end

function cov(sc::SimpleCovariance, X::AbstractMatrix, w::AbstractWeights; dims::Int=1, mean=nothing)
    dims ∈ (1, 2) || throw(ArgumentError("Argument dims can only be 1 or 2 (given: $dims)"))
    if mean === nothing
        return cov(X, w, dims, corrected=sc.corrected)
    else
        return covm(X, mean, w, dims, corrected=sc.corrected)
    end
end


using PATHSolver
using ForwardDiff, SparseArrays, Random
using Test

include("sparse_matrix.jl")
include("mcp.jl")
include("lcp.jl")
include("custom_nnz.jl")


using ConcaveHull
using Test

# Triangle
p = [[-1.0,0.0],[1.0,0.0],[0.0,1.0]]
h = concave_hull(p)
@test Set(h.vertices) == Set(p)
@test h.converged
@test area(h) == 0.5*2

# Triangle enclosing point
p = [[-1.0,0.0],[1.0,0.0],[0.0,1.0],[0.0,0.5]]
h = concave_hull(p)
@test h.converged
@test Set(p[1:3]) == Set(h.vertices)

# Square
p = [[-1.0,0.0],[1.0,0.0],[1.0,2.0],[-1.0,2.0]]
h = concave_hull(p)
@test Set(h.vertices) == Set(p)
@test area(h) == 4.0
@test h.converged

# Square with multiple points on one side
p = [[-1.0,0.0], [1.0,0.0], [1.0,0.25], [1.0,0.5], [1.0,0.75],
     [1.0,1.0], [1.0,1.25], [1.0,1.5], [1.0,1.75], [1.0,2.0], [-1.0,2.0]]
h = concave_hull(p)
@test area(h) == 4.0
@test h.converged

# Square enclosing point
p = [[-1.0,0.0],[1.0,0.0],[1.0,2.0],[-1.0,2.0],[0.0,1.0]]
h = concave_hull(p)
@test Set(p[1:4]) == Set(h.vertices)
@test h.converged

# Square enclosing many points
p = [[-1.0,0.0],[1.0,0.0],[1.0,2.0],[-1.0,2.0]]
pr = [[-0.99,0.01] + 1.98*rand(2) for i=1:100]
h = concave_hull(vcat(p,pr))
@test issubset(Set(p), Set(h.vertices))
@test h.converged

# Test non-convergence case
p = [[-1.0, -1.0], [0.0, 0.0], [1.0, 1.0], [2.0, 2.0]]
h = concave_hull(p)
@test h.converged == false


using Compat
using LinearOperators

"Abstract type for statistics returned by a solver"
@compat abstract type KrylovStats end;

"Type for statistics returned by non-Lanczos solvers"
mutable struct SimpleStats <: KrylovStats
  solved :: Bool
  inconsistent :: Bool
  residuals :: Array{Float64,1}
  Aresiduals :: Array{Float64,1}
  status :: String
end

export cgTN
"""A standard implementation of the Conjugate Gradient method.
The only non-standard point about it is that it does not check
that the operator is definite.
It is possible to check that the system is inconsistent by
monitoring ‖p‖, which would cost an extra norm computation per
iteration.

Dominique Orban, <dominique.orban@gerad.ca>
Salt Lake City, UT, March 2015.

Try to adapt a stopping criterion based on regulα for the ARCq.

First reformulate the TR case using the characteristic function to confirm it is
equivalent to the usual implementation. A must be symmetric to define a quadratic objective
q(x) = 0.5*x'*A*x - b'*x

  JPD february 09 2017, Montréal"""
cgTN(A::Array{TA, 2}, b::Array{Tb, 1}; atol::Float64 = 1e-08, rtol::Float64 = 1e-06, itmax::Int = 0, verbose::Bool = false) where {TA <: Number, Tb <: Number} =
  cgTN(LinearOperator(A), b, atol=atol, rtol=rtol, itmax=itmax, verbose=verbose);

cgTN(A::SparseMatrixCSC{TA, IA}, b::Array{Tb, 1}; atol::Float64 = 1e-08, rtol::Float64 = 1e-06, itmax::Int = 0, verbose::Bool = false) where {TA <: Number, Tb <: Number, IA <: Integer} =
  cgTN(LinearOperator(A), b, atol=atol, rtol=rtol, itmax=itmax, verbose=verbose);

"""The conjugate gradient method to solve the symmetric linear system Ax=b.

The method does _not_ abort if A is not definite.
"""
function cgTN(A::LinearOperator, b::Array{T, 1}; atol::Float64 = 1e-08, rtol::Float64 = 1e-06, itmax::Int = 0, verbose::Bool = false) where T <: Real
    n = size(b, 1);
    (size(A, 1) == n & size(A, 2) == n) || error("Inconsistent problem size");
    #isequal(triu(A)',tril(A)) || error("Must supply Hermitian matrix")

    verbose && @printf("CG: system of %d equations in %d variables\n", n, n);

    # Initial state.
    x = zeros(n)
    # x = zero(n)
    x̂ = copy(x)

    γ = dot(b, b);
    γ == 0 && return x;
    r = copy(b);
    p = copy(r);

    σ = 0.0

    iter = 0;
    itmax == 0 && (itmax = 2 * n);

    rNorm = sqrt(γ);
    rNorms = [rNorm;];
    ε = atol + rtol * rNorm;
    verbose && @printf("%5d  %8.1e ", iter, rNorm)

    solved = rNorm <= ε;
    tired = iter >= itmax;
    neg_curv = false;
    status = "unknown";

    #q = s ->  0.5 * dot(s, copy(A * s)) - dot(b, s)

    #m = s ->  q(s) + norm(s)^3/(3*regulα)
    #hO = α -> m(x+α*p)
    Ap = copy(A * p);  # Bug in LinearOperators? A side effect spoils the computation without the copy.
    # pAp = BLAS.dot(n, p, 1, Ap, 1);
    pAp = p ⋅ Ap
    if pAp<=0
        neg_curv = true
        status = "gradient negative curvature"
        stats = SimpleStats(solved, false, rNorms, [], status);
        return (p, stats);
    else
        α = γ / pAp;
        # BLAS.axpy!(n,  α,  p, 1, x, 1);  # Faster than x = x + σ * p;
        @. x = x + σ * p;
        # BLAS.axpy!(n, -α, Ap, 1, r, 1);  # Faster than r = r - α * Ap;
        @. r = r - α * Ap;
        γ_next = BLAS.dot(n, r, 1, r, 1);
        rNorm = sqrt(γ);
        push!(rNorms, rNorm);

        solved = (rNorm <= ε) ;
        tired = iter >= itmax;

        if !solved
            β = γ_next / γ;
            γ = γ_next;
            BLAS.scal!(n, β, p, 1)
            # BLAS.axpy!(n, 1.0, r, 1, p, 1);  # Faster than p = r + β * p;
            @. p = r + β * n
        end
        iter = iter + 1;
    end

    while ! (solved || tired || neg_curv)
        Ap = copy(A * p);  # Bug in LinearOperators? A side effect spoils the computation without the copy.
        pAp = BLAS.dot(n, p, 1, Ap, 1);

        α = γ / pAp;

        verbose && @printf("%8.1e  %7.1e  %7.1e\n", pAp, α, σ);

        # Move along p from x to the min if either
        # the next step leads farther than the min of the regularized model or
        # we have nonpositive curvature.
        if (pAp <= 0.0)
            α = 0.0
            neg_curv = true
            verbose &&println("negative curvature encountered ",iter," CG iterations.")
        else
            verbose && @printf("    %8.1e  %7.1e  %7.1e\n", pAp, α, σ);
            BLAS.axpy!(n,  α,  p, 1, x, 1);  # Faster than x = x + σ * p;
            # @. x = x + σ * p;
            BLAS.axpy!(n, -α, Ap, 1, r, 1);  # Faster than r = r - α * Ap;
            # @. r = r - α * Ap;
            γ_next = BLAS.dot(n, r, 1, r, 1);
            rNorm = sqrt(γ);
            push!(rNorms, rNorm);

            solved = (rNorm <= ε) ;
            tired = iter >= itmax;
        end

        if !(solved || neg_curv)
            β = γ_next / γ;
            γ = γ_next;
            BLAS.scal!(n, β, p, 1)
            BLAS.axpy!(n, 1.0, r, 1, p, 1);  # Faster than p = r + β * p;
            # @. p = r + β * p;
        end
        iter = iter + 1;
        verbose && @printf("%5d  %8.1e ", iter, rNorm);
    end
    verbose && @printf("\n");

  status = neg_curv ? "negative curvature avoided" : (tired ? "maximum number of iterations exceeded" : "solution good enough given atol and rtol")
  stats = SimpleStats(solved, false, rNorms, [], status);
  return (x, stats);
end


# <md>
# strong chimeras in two-cluster networks

# In[]
# loading necessary packages
#using DifferentialEquations
using Plots; pyplot()
using LightGraphs
#using GraphPlot
using LinearAlgebra
using Statistics
using BenchmarkTools
using ProgressMeter
using LaTeXStrings
using DelimitedFiles

# In[]
n = 3     # size of the clusters
c = .2    # intra- and inter-coupling strength ratio
r = 1.2   # parameter of the optoelectronic map
K = 1.2   # overall coupling strength

# In[]
# discrete map model of optoelectronic oscillators
function f(x)
    return sin(x+π/4)^2
end

# In[]
# reduce the two-cluster system to a two-oscillator system
# simulate the two-oscillator reduced system to study the two-cluster coherent state
T = 100000 # total simulation time
x = zeros(2,T) # oscillator states
x[:,1] = rand(2,1) # random initial condition
p = Progress(T, barglyphs=BarGlyphs("[=> ]"), barlen=50, color=:yellow) # progress bar
@time for t in 2:T
    x[1,t] = r*f(x[1,t-1]) + c*n*K*(f(x[2,t-1])-f(x[1,t-1]))
    x[2,t] = r*f(x[2,t-1]) + c*n*K*(f(x[1,t-1])-f(x[2,t-1]))
    next!(p)
end

# In[]
# plot showing the states of the two oscillators in the reduced system
scatter(1:T,x[1,:],marker=(:circle,4,.6,:blue,stroke(0,.2,:black)),background_color=RGB(0.2, 0.2, 0.2),legend=false)
scatter!(1:T,x[2,:],marker=(:circle,3,.6,:green,stroke(0,.2,:black)),background_color=RGB(0.2, 0.2, 0.2),legend=false)
title!("Reduced System for Two-Cluster Coherent States")
xaxis!(L"t",font(15),(T-99,T))
yaxis!(L"x_i",font(15),(1,1.1))
#yticks!([-π,0,π],[L"\pi",L"0",L"\pi"])

# In[]
# plot showing the effective input from one cluster to the other
scatter(1:T,c*n*K*(f.(x[2,:])-f.(x[1,:])),marker=(:circle,3,.6,:red,stroke(0,.2,:black)),background_color=RGB(0.2, 0.2, 0.2),legend=false)
title!("Reduced System for Two-Cluster Coherent States")
xaxis!(L"t",font(15),(T-99,T))
yaxis!("Input",font(15),(-.05,.05))

# In[]
# create the quotient network that corresponds to chimera states
function quotient_net(c)
    ring = watts_strogatz(n, 2, 0)
    adj = Matrix(adjacency_matrix(ring))
    return adj = [adj c*n*ones(n,1); c*ones(1,n) 0]
end

# In[]
# simulate the (n+1)-oscillator reduced system to study chimera states
function chimera(adj,T,IC)
    x = zeros(n+1,T) # oscillator states
    driving = zeros(T) # effective input from the incoherent cluster to the coherent cluster
    x[:,1] = IC # initial condition
    for t in 2:T
        for i in 1:n # updating oscillators in the incoherent cluster
            x[i,t] = r*f(x[i,t-1]) + K*adj[i,:]⋅(f.(x[:,t-1]).-f(x[i,t-1]))
        end
        driving[t] = K*adj[n+1,:]⋅(f.(x[:,t-1]).-f(x[n+1,t-1]))
        x[n+1,t] = r*f(x[n+1,t-1]) + driving[t] # updating the oscillator representing the coherent cluster
    end
    # return the mean and the s.t.d. of the effective input from the incoherent cluster to the coherent cluster, and the final oscillator states
    return mean(driving[T-10000:T]), std(driving[T-10000:T]), x[:,T]
end

# In[]
# calculate the mean and the s.t.d. of the effective input for different values of c
m = 100 # number of different values of c
T = 100000 # total simulation time for each c
μ = zeros(m) # mean of the effective input
σ = zeros(m) # s.t.d. of the effective input
IC = r*rand(n+1) # random initial conditions
c_range = range(0,stop=.4,length=m)
p = Progress(m, barglyphs=BarGlyphs("[=> ]"), barlen=50, color=:yellow)
for i = 1:m
    c = c_range[i]
    adj = quotient_net(c)
    μ[i], σ[i], IC1 = chimera(adj,T,IC)
    next!(p)
end

# In[]
writedlm("input.txt",[μ,σ],',')

# In[]
# plot showing the mean and the s.t.d. of the effective input for different values of c
scatter(c_range,μ,marker=(:circle,3,.6,:green,stroke(0,.2,:black)),background_color=RGB(0.2, 0.2, 0.2),label = "μ")
scatter!(c_range,σ,marker=(:circle,3,.6,:blue,stroke(0,.2,:black)),background_color=RGB(0.2, 0.2, 0.2),label = "σ")
title!("Reduced System for Chimera States")
xaxis!(L"c",font(15))
yaxis!(L"\mu,\sigma",font(15))

# In[]
# simulate the (n+1)-oscillator reduced system to obtain a typical time series of the effective input
c = .2
adj = quotient_net(c)
T = 1000000
x = zeros(n+1,T) # oscillator states
driving = zeros(T) # effective input
x[:,1] = rand(n+1,1) # random initial condition
p = Progress(T, barglyphs=BarGlyphs("[=> ]"), barlen=50, color=:yellow)
for t in 2:T
    for i in 1:n
        x[i,t] = r*f(x[i,t-1]) + K*adj[i,:]⋅(f.(x[:,t-1]).-f(x[i,t-1]))
    end
    driving[t] = K*adj[n+1,:]⋅(f.(x[:,t-1]).-f(x[n+1,t-1]))
    x[n+1,t] = r*f(x[n+1,t-1]) + driving[t]
    next!(p)
end
writedlm("driving.txt",driving,',') # time series of the effective input
@show mean(driving[T-100000:T]), std(driving[T-100000:T])

# In[]
# plot showing states of the (n+1) oscillators in the reduced system (coherent cluster: blue; incoherent cluster: green, red, magenta)
scatter(T-199:T,x[n+1,T-199:T],marker=(:circle,3,.6,:blue,stroke(1,.2,:white)),background_color=RGB(0.2, 0.2, 0.2),legend=false)
for i = 1:n
    scatter!(T-199:T,x[i,T-199:T],marker=(:circle,3,.6,stroke(0,.2,:black)))
end
title!("Reduced System for Chimera States")
xaxis!(L"t",font(15))
yaxis!(L"x_i",font(15))

# In[]
# plot showing a typical time series of the effective input
scatter(T-1000:T,driving[T-1000:T],marker=(:circle,3,.6,:red,stroke(0,.2,:black)),background_color=RGB(0.2, 0.2, 0.2),legend=false)
title!("Reduced System for Chimera States")
xaxis!(L"t",font(15))
yaxis!("Input",font(15))


v = 1:999
println(sum(v[(v .% 3 .== 0) .| (v .% 5 .== 0)]))


using Test, WrappedR

X = randn(100,5)
y = 7.0 .+ X[:,1] .+ 2.0 .* X[:,2] .* X[:,3] + 3*randn(100)

@test size(X) == (100, 5)
@test size(y) == (100,)

m  = fitsprintr(X, y)
pr = predictsprintr(m, X)

@test m[:n] == 100
@test m[:p] == 5
#@test m[:a0] #changes. setseed
#@test m[:compact] #changes. setseed
#@test m[:fit]


module Day08
export part1, part2

parse_image(image) = [parse(Int, c) for c in image]

layer(image, n, i) = view(image, n*(i-1)+1:n*i)

layers(image, n) = let (nLayers, rem) = divrem(length(image), n)
    @assert rem == 0
    (layer(image, n, i) for i in 1:nLayers)
end

function checksum(image, n)
    image = parse_image(image)

    (cmin, imin) = findmin(count.(x->x==0, layers(image, n)))

    layerMin = layer(image, n, imin)
    @assert count(x->x==0, layerMin) == cmin

    count(x->x==1, layerMin) * count(x->x==2, layerMin)
end

function decode(image, a, b)
    image = parse_image(image)
    n = a*b

    im = fill(2, (a, b))
    for l in layers(image, n)
        for i in eachindex(im)
            im[i]==2 && (im[i]=l[i])
        end
    end

    im
end

part1() = checksum(readline("input08.dat"), 25*6)

function part2()
    image = decode(readline("input08.dat"), 25, 6)
    image = map(image) do x
        x==0 ? ' ' : 'O'
    end
    reduce(*, String(image[:,i])*"\n" for i in 1:size(image, 2))
end

end


# FizzBuzzの値を返す関数を定義 --- (*1)
function fizzbuzz(n)
  if n % 3 == 0 && n % 5 == 0
    return "FizzBuzz"
  elseif n % 3 == 0
    return "Fizz"
  elseif n % 5 == 0
    return "Buzz"
  else
    return n
  end
end

# 繰り返し関数を実行 --- (*2)
for i = 1:100
  println(fizzbuzz(i))
end


using Random
using LinearAlgebra
using Statistics
using Optim
using DataFrames
using DataFramesMeta
using CSV
using HTTP
using GLM

# 1. read in and reshape
cd("C:/Users/jakek/Documents/econ_phd/structural_modeling_course/forked_class/ProblemSets/PS5-ddc/")
include("create_grids.jl")
df = CSV.read("busdataBeta0.csv")
df = @transform(df, bus_id = 1:size(df,1))
dfy = @select(df, :bus_id,:Y1,:Y2,:Y3,:Y4,:Y5,:Y6,:Y7,:Y8,:Y9,:Y10,:Y11,:Y12,:Y13,:Y14,:Y15,:Y16,:Y17,:Y18,:Y19,:Y20,:RouteUsage,:Branded)
dfy_long = DataFrames.stack(dfy, Not([:bus_id,:RouteUsage,:Branded]))
rename!(dfy_long, :value => :Y)
dfy_long = @transform(dfy_long, time = kron(collect([1:20]...),ones(size(df,1))))
select!(dfy_long, Not(:variable))

# next reshape the odometer variable
dfx = @select(df, :bus_id,:Odo1,:Odo2,:Odo3,:Odo4,:Odo5,:Odo6,:Odo7,:Odo8,:Odo9,:Odo10,:Odo11,:Odo12,:Odo13,:Odo14,:Odo15,:Odo16,:Odo17,:Odo18,:Odo19,:Odo20)
dfx_long = DataFrames.stack(dfx, Not([:bus_id]))
rename!(dfx_long, :value => :Odometer)
dfx_long = @transform(dfx_long, time = kron(collect([1:20]...),ones(size(df,1))))
select!(dfx_long, Not(:variable))

# join reshaped df's back together
df_long = leftjoin(dfy_long, dfx_long, on = [:bus_id,:time])
sort!(df_long,[:bus_id,:time])

##### 2. static model.
beta_glm = glm(@formula(Y ~ Odometer+Branded ), df_long, Binomial(), LogitLink())
println(beta_glm)

###### 3.
beta = 0.9
data = CSV.read("busdata.csv")
Y = @select(data, :Y1,:Y2,:Y3,:Y4,:Y5,:Y6,:Y7,:Y8,:Y9,:Y10,:Y11,:Y12,:Y13,:Y14,:Y15,:Y16,:Y17,:Y18,:Y19,:Y20)
Y = convert(Matrix, Y)
O = @select(df, :Odo1,:Odo2,:Odo3,:Odo4,:Odo5,:Odo6,:Odo7,:Odo8,:Odo9,:Odo10,:Odo11,:Odo12,:Odo13,:Odo14,:Odo15,:Odo16,:Odo17,:Odo18,:Odo19,:Odo20)
O = convert(Matrix, O)
X = data[:, 43:62]
Z = Vector(df[:,:RouteUsage])
X = convert(Matrix, X)
Zst = convert(Vector, data[:,:Zst])
B = convert(Vector, data[:,:Branded])
# create xst

## b
zval,zbin,xval,xbin,xtran = create_grids()

### c.
@views @inbounds function zurcher(theta, Y, X, O,Zst,B,beta,zval,zbin,xval,xbin,xtran)
	T = promote_type(eltype(X),eltype(theta), eltype(Y), eltype(O), eltype(B), eltype(Zst),eltype(beta))
	Fut = repeat(zeros(T,size(xtran,1),2 ),1,1,21)
	for t=reverse(1:20)
		for b=0:1
			for z=1:zbin
				for x=1:xbin
					row = x+(z-1)*xbin
					v1 = theta[1]+theta[2]*xval[x] + theta[3]*b+(xtran[row,:]'*Fut[(z-1)*xbin+1:z*xbin,b+1,t+1])
					v0 = xtran[1+(z-1)*xbin,:]'*Fut[(z-1)*xbin+1:z*xbin,b+1,t+1]
					Fut[row,b+1,t] = beta*log(exp(v1)+exp(v0))
				end
			end
		end
	end
	loglik = 0
	for i=1:size(Y,1)
		for t=1:20
			row0 = 1+(Zst[i]-1)*xbin
			row1 = X[i,t]+(Zst[i]-1)*xbin
			vflow = theta[1]+theta[2]*O[i,t]+ theta[3]*B[i]
			vdiff = vflow + (xtran[row1,:].-xtran[row0,:])'*Fut[row0:row0+xbin-1,B[i]+1,t+1]
			loglik = loglik + (Y[i,t]==1)*vdiff - log(1+exp(vdiff))
		end
	end
	return -loglik
end

starting = vec(coef(beta_glm))
td = TwiceDifferentiable(b -> zurcher(b, Y, X, O, Zst,B,beta,zval,zbin,xval,xbin,xtran), θ_true; autodiff = :forward)
beta_zurcher = optimize(td,θ_true, Optim.Options(g_tol=1e-5, iterations=100_000, show_trace=true))
H  = Optim.hessian!(td, beta_zurcher.minimizer)
se= sqrt.(diag(inv(H)))
res = vcat(beta_zurcher.minimizer', se' )
CSV.write("q5.csv", convert(DataFrame,res'))


### TAKE TWO

using Primes: primes, factor, isprime

### Helpers

nroot(x::Number, n::Number) = x^(1/n)

function factors(n::Integer)
    f = [one(n)]

    for (p,e) in factor(n)
        f = reduce(vcat, [f*p^j for j in 1:e], init=f)
    end

    return ifelse(isone(length(f)), [one(n), n], sort!(f))
end

"""
    ord(q::Integer, n::Integer) -> Integer

Computes the multaplicative order, ord_q n, of a number n mod q.
"""
function ord(q::Integer, n::Integer)
    isone(gcd(n, q)) || throw(error("$n and $q are not coprime"))
    res = one(q)

    for (p, e) in factor(q)
        m = p^e
        t = div(q, p) * (p-1)
        for f in factors(t)
            if isone(powermod(n, f, q))
                res = lcm(res, f)
                break
            end
        end
    end

    return res
end

### Main code
#=
\begin{enumerate} \item For each prime power $q$ such that $2^q\leq n$, write down a positive integer $r_q$ such that if $n$ is a $q$\th power, then $n=r^q_q$.\item Find a finite coprime set $P$ of integers larger than 1 such that each of $n,r_2,r_3,r_4, r_5,r_7,\ldots$ is a product of powers of elements of $P$.  (In this paper, ``coprime'' means ``pairwise coprime.'')\item Factor $n$ as $\Pi_{p\in P}p^{n_p}$, and compute $k=\text{gcd}n_p:p\in P$. \end{enumerate}=#

#=
From \texttt{Powers2.pdf}:\begin{quotation}
	Define $r_q$ as an integer 2-adically close to $n^{1/q}$, as explained in \texttt{Powers.pdf}, section 21.\end{quotation}

From \texttt{Powers.pdf}, section 21:\begin{quotation}
	In this section, I describe a \emph{2-adic variant} of Algorithm X.  With this variant, I can work with integers rather than floating point numbers; I no longer need to guard bits; I can jump directly into Newton's method without a preliminary binary search; and a proper error analysis takes a few lines rather than several pages.\par
	It will be convenient to restrict attention to odd $n$.  See section 22 to handle even $n$.\par
	\textbf{Motivation.}  To check if $n$ is a $k$\th power, I compute a tentative $k$\th root of $n$\emdash an integer x such that no integer other than $x$ can possibly be the $k$\th root of $n$.  Then I test whether $x^k=n$.\par
	To find $x$ in Part II and Part III, I constructed a number that was \emph{close} to a $k$\th root of $n$ in the usual metric.  I used the same metric again to check whether $x^k=n$: I computed $x^k$ in low precision to see whether it was close to $n$.\par
	Nothing in the original problem suggests this metric.  The 2-adic variant uses a different metric, where $i$ and $j$ are close if $i-j$ is divisible by a high power of 2.\par
	\textbf{Notation.}  This section deviates from the notation of parts I, II, and III: $r$, $y$, and $z$ are odd integers rather than positive floating-point numbers.\par
	\textbf{Lemma 21.1.}  \emph{If $2i\equiv 2j \mod 2^{b+1}$, and $b\geq 1$, then $i^2\equiv j^2\mod w^{b+1}.$}\par
	\textbf{2-adoc approximate powers.}  Fix positive integers $k$ and $b$.  For any integer $m$ define $\text{pow}_{2,b}(m,k)=m^k\mod 2^b$.  See section 6 for methods of computing $\text{pow}_{2, b}$ without many multiplications.  As I compute $\text{pow}_{2,b}(m, k)$, I keep track of an ``overflow bit'' to figure out whether $m^k\mod 2^b=m^k$.
\end{quotation}

From \texttt{Powers.pdf}, section 6:\begin{quotation}
	Let $r$ be a positive floating-point number, and let $k$ be positive integers.  Then $\text{pow}_{b}(r, k)$, the \textbf{$\mathbf{b}$-bit approximate $\mathbf{k}th$ power of $\mathbf{r}$} is a floating-point approximation to $r^k$.  In this section, I show how to compute $\text{pow}_b(r,k)$ in $M$-time at most $P(k)M(b), where $P(k)\leq 2\lfloor\text{lg} k\rfloor$.\par
	Define $P(k)$ for $k\geq 1$ as follows: $P(1)=0;P(2k)=P(k)+1;P(2k+1)=P(2k)+1$.\par
	\textbf{Lemma 6.1.}  $P(k)\leq 2\lfloor\text{lg} k\rfloor$.\par
	\emph{Proof.} $\ldots$\par
	\textbf{Lemma 6.2} $\text{pow}_b(r,k)\leq r^k<\text{pow}_b(r,k)(1+2^{1-b})^{2k-1}.$\par
	\emph{Proof.} $\ldots$\par
	\emph{Algorithm P.} $\ldots$\par
	\textbf{Lemma 6.3.}  Algorithm P computes $\text{pow}_b(r, k)$ in $M$-time at most $P(k)M(b)$.\par
	\emph{Proof.} $\ldots$\par
\end{quotation}

From \texttt{Powers.pdf}, section 22:\begin{quotation}

\end{quotation}
=#

lg(x) = log2(x)

#=
From Knuths The Art of Computer Programming, semi-numerical algorithms, 2nd edition, 4.3.1, exercise 16.

Design an algorithm that divides a nonnegative n-place integer $(u_1u_2\ldots u_n)_b$ by $v$, where $v$ is a single-precision number (i.e., $0 < v < b$), producing the quotient $(w_1w_2\ldots 2_n)_b$ and remainder $r$.
=#

"""
	divrem(b::Integer, u::Integer, v::Real)

This algorithm divides a non-negative n-place integer (u1 u2 ... un) by v, where v is a single-precision number (i.e., 0 < v < b), producing the quotient (w1 w2 ... wn) and remainder r.

This algorithm was written by Donald Knuth, in The Art of Computer Programming, semi-numerical algorithms, 2nd edition, exercise 4.3.1, 16.
"""
function Base.divrem(b::Integer, u::Integer, v::Real)
	r, j = 0, 1
	n = ndigits(u) #; base = b
	u̲ = round.(reverse(digits(u)), base = b)
	w̲ = Vector{Int}(undef, n)

	while j ≤ n
		w̲[j] = floor(Int, (r*b + u̲[j]) / v)
		r = mod(r*b + u̲[j], v)
		j += 1
	end

	# w = sum(w̲[i] * 10 ^ (length(w̲) - i) for i in eachindex(w̲))
	w = parse(Int, join(string.(w̲)))
	# w = foldr((i, j) -> 10 * j + i, reverse(w̲))
	#
	# n = 0
	# reduce(w̲) do i, j
	# 	10 * j + i
	# 	# global n = n + ndigits(w̲[i])
	# end
	# n = 0
	# w = sum(eachindex(w̲)) do i
	# 	w̲[i] * 10 ^ (length(w̲) - i + n)
	# 	global n = n + ndigits(w̲[i])
	# end
	# n = 0

	return w, r
end

"""
	div(b::Integer, u::Integer, v::Real)

This algorithm divides a non-negative n-place integer (u1 u2 ... un) by v, where v is a single-precision number (i.e., 0 < v < b), producing the quotient (w1 w2 ... wn) and ommiting the remained.  To return the quotient and the remainder, see `divrem`.
"""
Base.div(b::Integer, u::Integer, v::Real) = first(divrem(b, u, v))

"""
	rem(b::Integer, u::Integer, v::Real)

This algorithm divides a non-negative n-place integer (u1 u2 ... un) by v, where v is a single-precision number (i.e., 0 < v < b), producing a remainder r.  To return the quotient and the remainder, see `divrem`.
"""
Base.rem(b::Integer, u::Integer, v::Real) = last(divrem(b, u, v))

#=
In this section, I define truncation to $b$ bits, written $\text{trunc}_b$, and show that $\frac{r}{\text{trunc}_br}$ is between $1$ and $1+2^{1-b}$.  More generally, for any positive integer $k$, I define $\text{div}_b(r,k)$ as a floating point approximation to $\frac{r}{k}$, so that $\frac{r}{k\text{div}_b(r,k)}$ is between $1$ and $1+2^{1-b}$.\\ Fix $b\geq 1$.  Set $\text{div}_b(a,n,k)=(a+f-\left\lceil\text{lg} k\right\rceil - b, \left\lfloor\frac{n}{2^{f-\left\lceil\text{lg} k\right\rceil-b}k}\right\rfloor)$, where $2^{f-1}\leq n<w^f$.  (Note that $f-\left\lceil \text{lg} k\right\rceil - b$ may be negative.)  This map induces a map, also denoted $\text{div}_b$, upon positive floating-point numbers: \begin{equation}\text{div}_b(2^an,k)=2^{a+f-\left\lceil\text{lg} k\right\rceil - b}\left\lfloor\frac{n}{2^{f-\left\lceil\text{lg}k\right\rceil - b}k}\right\rfloor\quad\quad\quad\text{if }2^{f-1}\leq n<2^f.\end{equation}\\ To compute $\text{div}_b(r,k)$ I also use an algorithm designed for dividing by small integers; time spent computing $\text{div}_b$ is not $M$-time.
=#

"""
	trunc(b::Integer, n::Real)

Truncates a number `n` to `b` bits.
"""
function Base.trunc(b::Integer, n::Real)
	return n & (1 << b - 1)
	# return div(b, n, 1)
end

#=
Given a positive floating-point number r and two positive integers b, k, to prime pow_b(r, k).  Let $\text{pow}_b(r,k)$ be the $b$-bit approximate $k$th power of $r$.
\begin{equation}\begin{aligned}
\text{pow}_b(r,1) &= \text{trunc}_br; \\
\text{pow}_b(r,2k) &= \text{trunc}_b(\text{pow}_b(r,k)^2); \\
\text{pow}_b(r,2k+1) &= \text{trunc}_b(\text{pow}_b(r,2k)\text{pow}_b(r,1))
\end{aligned}\end{equation}
=#

"""
	pow(b::Integer, r::Real, k::Integer)

Given a positive floating-point number r and two positive integers b and k, pow(b, r, k) computes the b-bit approximate kth power of r.  pow_b(r, k) ≤ r^k < pow_b(r, k)(1 + 2^(1 - b))^(2k - 1).
"""
function pow(b::Integer, r::Real, k::Integer)
	if r ≤ 0 || b ≤ 0 || k ≤ 0
		throw(error("r, b, and k must be positive numbers to compute pow_b(r, k)."))
	end
	isone(k) && return trunc(b, r)
	iszero(mod(k, 2)) && return trunc(b, pow(b, r, k ÷ 2)^2) # use \div because k is even
	return trunc(b, pow(b, r, k - 1) * trunc(b, r))
end

"""
	pow2(b::Integer, r::Real, k::Integer)

Given a positive floating-point number r and two positive integers b and k, pow2(b, r, k) computes the 2-adic b-bit approximate kth power of r.
"""
function pow2(b::Integer, m::Integer, k::Integer)
	if k ≤ 0 || b ≤ 0
		throw(error("k, and b must be positive integers to find a 2-adic approximate power."))
	end

	return mod(m^k, 2^b)
end

"""
	tentative_check_kth_roots(n::Integer, x::Integer, k::Integer)

A straight forward algorithm for checking whether x^k = n.
"""
function tentative_check_kth_roots(n::Integer, x::Integer, k::Integer)
	if n ≤ 0 || x ≤ 0 || k ≤ 0
		throw(error("n, x, and k must be positive integers to compute check whether n=x^k."))
	end

	f = floor(Integer, lg(2*n))

	isone(x) && return ifelse(isone(n), true, false) # return ifelse(isone(n), 0, 2)

	b = 1
	while true
		# r = pow(x, b, k) # 2, b
		r = pow2(b, x, k)
		isequal(mod(n, 2^b), r) || return false # 2
		b ≥ f && return ifelse(isequal(r, x^k), true, false) # ifelse(isequal(r, x^k), 0, 2)

		b = min(2*b, f)
	end
end

#=
\textbf{Lemma 21.2}.  Algorithm C2 prints 0 if and only if n=x^k.\par
\emph{Proof.} $\ldots$\par
\textbf{2-adic approximate multiplication and division}.  Fix $b\geq 1$.  For $m$ an integer and $k$ a positive integer write $\text{mul}_{2, b}(m, k) = km \mod 2^b.$\par
If $k$ is odd, write $\text{div}_{2, b}(m, k)$ for the unique integer between 0 inclusive and $2^b$ exclusive such that $m\equiv k \text{div}_{2, b}(m, k) \mod 2^b$.
=#

#=
Write a MIX program that multiplies $(u_1u_2\ldots u_n)_b$ by $v$, where $v$ is a single-precision number (i.e., $0 \leq v < b$), producing the answer $(w_0w_1\ldots w_n)_b$.  How much running time is required?

		ENTX	N		1	ADD		CARRY	N
		JOV		OFLO	1	JNOV	*+2		N
		ENTX	0		1	INCX	1		K
	2H	STX		CARRY	N	STA		W,1		N
		LDA		U,1		N	DEC1	1		N
		MUL		V		N	J1P		2B		N
		SLC		5		N	STX		W		1

The running time is 23N + K + 5 cycles, and K is rougly (1/2)N.
=#

"""
	mul2(b::Integer, m::Integer, k::Integer)

The notation mul(r, k) is used in the Bernstein paper to denote k * r.  The notation mul_{2, b}(m, k) represents 2-adic multiplication.
"""
function mul2(b::Integer, m::Integer, k::Integer)
	if k ≤ 0
		throw(error("Cannot compute mul(m, k) when k is negative using the 2-adic method."))
	end

	return mod(k*m, 2^b)
end

"""
	div2(b::Integer, m::Integer, k::Integer)
div_b is defined as a floating-point approximation to r/k, so that r/k div_b(r, k) is between 1 and 1 + 2^(1 - b).  div2 is the 2-adic approximation of this.
"""
function div2(b::Integer, m::Integer, k::Integer)
	if k ≤ 0
		throw(error("Cannot compute mul(m, k) when k is negative using the 2-adic method."))
	end

	for m in 0:2^b
		if isequal(m, mod(div(b, m, k), 2^b))
			return div(b, m, k)
		end
	end
end

"""
	odd_nroot2(b::Integer, y::Integer, k::Integer)

Fix an odd integer y and a positive odd integer k.  odd_nroot2 finds an approximate negative kth root of y by Newton's method.
"""
function odd_nroot2(b::Integer, y::Integer, k::Integer)
	if k ≤ 0 || b ≤ 0
		throw(error("Cannot use this method for negative number k and b."))
	end

	if iseven(y) || iseven(k)
		throw(error("Cannot use this method for even y and k."))
	end

	b′ = ceil(Integer, b / 2)

	isone(b) && return 1
	z = odd_nroot2(b′, y, k)
	r₂ = mul2(b, z, k + 1)
	r₃ = mod(y * pow2(b, z, k + 1), 2^b)
	return r₄ = div2(b, r₂ - r₃, k)
end

#=
\textbf{Lemma 21.3.}  If $k$ is odd and $r=\text{nroot}_{2,b}(y, k)$, then $r^k y\mod 2^b = 1$.\par
\emph{Proof.} $\ldots$\par
=#

"""
	sqrt_nroot2(b::Integer, y::Integer)

Fix odd integer y.  For each b ≥ 1, define and construct nroot_(2, b)(y, 2).
"""
function sqrt_nroot2(b::Integer, y::Integer)
	# if b ≤ 0
	# 	throw(error("Cannot use this method for negative number b."))
	# end
	#
	# if iseven(y)
	# 	throw(error("Cannot use this method for even y."))
	# end

	b′ = ceil(Integer, (b + 1) / 2)

	isone(b) && return ifelse(isone(mod(y, 4)), 1, 0)
	isequal(b, 2) && return ifelse(isone(mod(y, 8)), 1, 0)
	iszero(z) && return 0

	r₂ = mul2(b + 1, z, 3)
	r₃ = mod(y * pow2(b + 1, z, 3), 2^(b + 1))
	return r₄ = mod(r₂ - r₃, 2^b)
end

#=
\textbf{Lemma 21.4.}  Set $r = \text{nroot}_{2, b}(y, 2)$.  If $i^2y\mod 2^{b + 1}=1$ for some odd integer $i$, then $r\neq 0$.  If $r\neq 0$ then $r^2y\mod 2^{b+1} = 1$.\par
\emph{Proof.} $\ldots$\par
=#

"""
	perfect_power_decomp_odd(n::Integer, k::Integer, y::Integer)

Given a positive odd integer n, an integer k ≥ 2 such that either k = 2 or k is odd, and an odd integer y, `perfect_power_decomp_odd` checks if n is a kth power.
"""
function perfect_power_decomp_odd(n::Integer, k::Integer, y::Integer)
	if iseven(n) && !isequal(k, 2)
		throw(error("Cannot use this algorithm on even n."))
	end

	if k < 2
		throw(error("k must be ≥ 2."))
	else
		if iseven(k) && !isequal(k, 2)
			throw(error("k must be 2 or odd."))
		end
	end

	f = floor(Integer, lg(2*n))
	b = ceil(Integer, f / k)

	r = odd_nroot2(b, y, k)
	(isequal(k, 2) && iszero(r)) && return 0
	tentative_check_kth_roots(n, r, k) && return r
	(isequal(k, 2) && tentative_check_kth_roots(n, 2^b - r, k)) && return 2^b - r
	return 0
end

#=
\textbf{Lemma 21.5.}  Set $f=\lfloor\text{lg}2n\rfloor$ and $b=\lceilf/k\rceil$.  Assume that $yn\mod 2^{b+1}=1$.  If $n$ is a $k$\th power, Algorithm K2 (above) prints $n^{1/k}$.  If $n$ is not a $k$\th power, Algorithm K2 prints 0.\par
\emph{Proof.} $\ldots$\par
=#

"""
	odd_decomp_perf_pow(n::Integer)

Given an odd integer n ≥ 2, `odd_decomp_perf_pow` decomposes n as a perfect power if possible.
"""
function perfectpower(n::Integer)
    # isprime(n) && return n, 1

	# if iseven(n) && !isequal(n, 2)
	# 	throw(error("This method must have odd n."))
	# end

	if n < 2
		throw(error("n must be ≥ 2."))
	end

    isprime(n) && return n, 1

    # sqrt_val = sqrt(n)
    # isinteger(sqrt_val) && return round(Integer, sqrt_val), round(Integer, sqrt_val)

    if iseven(n)
        if ispow2(n)
            return 2, round(Integer, log2(n))
        else
            return n, 1
        end
    end

	f = floor(Integer, lg(2*n))

	y = odd_nroot2(ceil(Integer, f / 2) + 1, n, 1)

	for p in primes(f - 1)
		x = perfect_power_decomp_odd(n, p, y)
		x > 0 && return x, p
	end

	return n, 1
end

#=
\textbf{Lemma 21.6.}  If $n$ is a perfect power, the above algorithm (Algorithm X2) prints a prime number $p$ and a positive integer $x$ such that $x^p=n$.  If $n$ is not a perfect power, Algorithm X2 print (n, 1).
\emph{Proof.} $\ldots$\par
=#

#=
As usual, fix $n\geq 2$.  In this section I discuss several tricks based on computing $n$ mod $q$ for on or more primes $q$.\par
\textbf{If $n$ has no small prime divisors, lower the exponent bound.}  If $n$ is odd and $n=x^k$, then $x$ is also odd.  So $x\geq 3$ and $k\leq \log_3 n$.  More generally, one may compute $n\mod q$ for all primes $p < T$, for some bound $T$.  If $n\mod q$ is always nonzero, one need not check exponents past $\log_t n$.\par
\textbf{If n has a prime divisor, find its order.}  What if $n\mmod 1 = 0$?  First compute the number $\text{ord}_q n$ of factors $q$ in $n$, together with $n/q^{\text{ord}_qn}$.  Then check, for each $p$ dividing $\text{ord}_qn$ whether $n/q^{\text{ord}_qn}$ is a $p$\th power.  Otherwise, $n$ cannot be a perfect power.  (Note that $n/q^{\text{ord}_qn}$ may be 1, in which case no trsting is necessary.)\par Recall that the 2-adic method in section 21 requires $n$ to be odd.  This is not a serious restriction.  If $n$ is even, the method here ends up checking whether $n/2^{\text{ord}_2n}$ is a $p$th power, for various primes $p$; and $n/2^{\text{ord}_2n}$ is odd.\par There are several plausible ways to compute the number of factors $q$ in $n$.\par If $q=2$ then $\text{ord}_qn$ is the number of 0 buts at the bottom of $n$'s binary expansion.  \par If $q>2$, I do a binary search upon the number of factors.  The idea is to compute $n\mod q^c$ and $\lfloor n/q^c\rfloor$ for some integers $c\approx (\log_q n)/2$.  If $n\mod q^c\neq 0$ then $\text{ord}_qn=\text{ord}_q(n\mod q^c)$; if $n\mod q^c=0$ then $\text{ord}_qn=c+\text{ord}_q(n/q^c)$.  Chop $c$ in half and repeat.  This method takes essentially linear time given fast multiplication and the algorithms from section 8.\par I could instead do a linear search; this amounts to always taking $c=1$ in the above description.  This will be faster than a binary search on average.  I could compromise with a sequence of $c$ that is at first optimistic but backs off quickly if necessary.  For example: begin with $c=1$, double $c$ if $n\mod q^c = 0$, and chop $c$ in half if $n\mod q^c\neq 0$.\par
\textbf{Check the character of residues of n}.  If $n$ is a $k$\th power, and $q$ is a prime with $q\mod k=1$, then $n^{(q - 1) / k}\mod q$ is either 0 or 1.  A non-$k$\th power has roughly a $1/k$ chance of passing this test.\par
\textbf{Check the residues of tentative roots.}  If $n=x^k$ then $n\mod q=x^k\mod q$.  So, given $n\mod q$, I can try to weed out a tentative root $x$ by calculating the $k\th$ power modulo $q$ of $x\mod q$.  In practice this test is quite powerful: fi $n\neq x^k$ then very few primes $q$ divide $n-x^k$.\par In this test $q$ need not be prime.  It might be convenient to check whether $n$ agrees with $x^k$ modulo $2^{32}$, for example, though this is redundant if $x$ was constructed by 2-adic methods.\par One could develop a fast randomized power-testing algorithm along these lines.  Start from a tentative root $x$.  First check if $x^k\leq 2n$.  Then check if $n\mod q$ equals $x^k\mod q$ for a set of ``random'' primes $q$ with product larger than $n$.  This test will succeed if and only if $n=x^k$.  If $n\neq x^k$ then one will, on average, test very few $q$'s.\par
\textbf{Check for small divisors of tentative roots}.  If $n$ is not divisible by any primes $q<T$, and $n=x^k$, then $x$ is not divisible by any primes $q<T$.  So I can throw away any tentative root $x$ that has prime factors smaller than $T$.  This is much weaker than testing whether $n\mod d=x^k\mod q$ for each $q<T$, but it is also much faster.
=#

function lower_exp_bound(n::Integer, x::Integer, k::Integer)
    # if isodd(n)
        # continue
    # end
end


using CSV
using DataFrames
# df = CSV.read("parkinsons_updrs", header=false, delim=',')
# df = readtable("~/test/parkinsons_updrs.csv")
medData = CSV.read("/home/puranjay/ubc/term3/SGDBabysitter.jl/test/parkinsons_updrs.csv", delim=',')

# deletecols!(medData, :motor_UPDRS)

y1 = medData[[:total_UPDRS]]
y2 = medData[[:motor_UPDRS]]
deletecols!(medData, [:motor_UPDRS, :total_UPDRS])
xtrain = convert(Array,first(medData, 4000))
ytrain1 = convert(Array,first(y1, 4000))
ytrain2 = convert(Array,first(y2, 4000))
xvalid = convert(Array,last(medData, 1875))
yvalid1 = convert(Array,last(y1, 1875))
yvalid2 = convert(Array,last(y2, 1875))

n2 = size(xtrain,1)
xtrain = [ones(n2,1) xtrain]
d2 = size(xtrain,2)

# Choose network structure and randomly initialize weights
include("NeuralNet.jl")
nHidden = [3,3,4,3]
# nHidden = [5,5,5,5]
nParams = NeuralNet_nParams(d2,nHidden)
w = randn(nParams,1)

# Train with stochastic gradient
include("sgdfunc.jl")
maxIter = 10000

#----Uncomment below for hyperparameter search:----#

aTest = [0.1, 0.01, 0.001, 0.0001, 0.00001]
BTest = [1, 5, 10, 20, 100] #200 500 1000

bestvalid = Inf
bestw = []
bestparams2 = [0,0]
for B in BTest
    for a in aTest
        global bestvalid, bestw, bestparams2
        wb, validerr = VanillaSGD(NeuralNet_backprop, NeuralNet_predict, maxIter, nHidden, nParams, xtrain, ytrain1, xvalid, yvalid1, a, B)
        if (validerr[end]<bestvalid)
            bestvalid=validerr[end]
            bestw=wb
            bestparams2 = [a,B]
        end
    end
end

#best hyperparams found to be [0.1, 10]

wb_park, valid_park = SGDBabysitter(NeuralNet_backprop, NeuralNet_predict,
maxIter, nHidden, nParams, xtrain, ytrain1, xvalid, yvalid1)
savefig("SGDB_Parkinsons.pdf")

wb_vpark, valid_vpark = VanillaSGD(NeuralNet_backprop, NeuralNet_predict, maxIter, nHidden,nParams, xtrain, ytrain1, xvalid, yvalid1, bestparams2[1], convert(Int64, bestparams2[2]))
savefig("Vanilla_Parkinsons.pdf")