/*
 * GraphBLAS Template Library (GBTL), Version 3.0
 *
 * Copyright 2020 Carnegie Mellon University, Battelle Memorial Institute, and
 * Authors.
 *
 * THIS MATERIAL WAS PREPARED AS AN ACCOUNT OF WORK SPONSORED BY AN AGENCY OF
 * THE UNITED STATES GOVERNMENT.  NEITHER THE UNITED STATES GOVERNMENT NOR THE
 * UNITED STATES DEPARTMENT OF ENERGY, NOR THE UNITED STATES DEPARTMENT OF
 * DEFENSE, NOR CARNEGIE MELLON UNIVERSITY, NOR BATTELLE, NOR ANY OF THEIR
 * EMPLOYEES, NOR ANY JURISDICTION OR ORGANIZATION THAT HAS COOPERATED IN THE
 * DEVELOPMENT OF THESE MATERIALS, MAKES ANY WARRANTY, EXPRESS OR IMPLIED, OR
 * ASSUMES ANY LEGAL LIABILITY OR RESPONSIBILITY FOR THE ACCURACY, COMPLETENESS,
 * OR USEFULNESS OR ANY INFORMATION, APPARATUS, PRODUCT, SOFTWARE, OR PROCESS
 * DISCLOSED, OR REPRESENTS THAT ITS USE WOULD NOT INFRINGE PRIVATELY OWNED
 * RIGHTS.
 *
 * Released under a BSD-style license, please see LICENSE file or contact
 * permission@sei.cmu.edu for full terms.
 *
 * [DISTRIBUTION STATEMENT A] This material has been approved for public release
 * and unlimited distribution.  Please see Copyright notice for non-US
 * Government use and distribution.
 *
 * This Software includes and/or makes use of the following Third-Party Software
 * subject to its own license:
 *
 * 1. Boost Unit Test Framework
 * (https://www.boost.org/doc/libs/1_45_0/libs/test/doc/html/utf.html)
 * Copyright 2001 Boost software license, Gennadiy Rozental.
 *
 * DM20-0442
 */

//#define GRAPHBLAS_LOGGING_LEVEL 2

#include <graphblas/graphblas.hpp>

#define BOOST_TEST_MAIN
#define BOOST_TEST_MODULE kronecker_ATB_test_suite

#include <boost/test/included/unit_test.hpp>

using namespace grb;

BOOST_AUTO_TEST_SUITE(BOOST_TEST_MODULE)

//****************************************************************************

namespace
{
    static std::vector<std::vector<double> > A_sparse_3x3 =
    {{1,  0,  0},
     {0,  2,  0},
     {3,  0,  4}};

    static std::vector<std::vector<double> > Ar_sparse_3x3 =
    {{1,  0,  0},
     {0,  2,  0},
     {0,  0,  0}};

    static std::vector<std::vector<double> > Ac_sparse_3x3 =
    {{0,  0,  0},
     {0,  2,  0},
     {0,  0,  4}};

    static std::vector<std::vector<double> > AT_sparse_3x3 =
    {{1,  0,  3},
     {0,  2,  0},
     {0,  0,  4}};

    static std::vector<std::vector<double> > B_sparse_3x4 =
    {{1,  1,  0,  0},
     {0,  2,  2,  0},
     {3,  0,  0,  3}};

    static std::vector<std::vector<double> > Br_sparse_3x4 =
    {{0,  0,  0,  0},
     {0,  2,  2,  0},
     {3,  0,  0,  3}};

    static std::vector<std::vector<double> > Bc_sparse_3x4 =
    {{1,  0,  0,  0},
     {0,  0,  2,  0},
     {3,  0,  0,  3}};

    static std::vector<std::vector<double> > BT_sparse_3x4 =
    {{1,  0,  3},
     {1,  2,  0},
     {0,  2,  0},
     {0,  0,  3}};

    static std::vector<std::vector<double> > AA_sparse_9x9 =
    {{1,  0,  0,   0,  0,  0,   0,  0,  0},
     {0,  2,  0,   0,  0,  0,   0,  0,  0},
     {3,  0,  4,   0,  0,  0,   0,  0,  0},
     {0,  0,  0,   2,  0,  0,   0,  0,  0},
     {0,  0,  0,   0,  4,  0,   0,  0,  0},
     {0,  0,  0,   6,  0,  8,   0,  0,  0},
     {3,  0,  0,   0,  0,  0,   4,  0,  0},
     {0,  6,  0,   0,  0,  0,   0,  8,  0},
     {9,  0, 12,   0,  0,  0,  12,  0, 16}
    };

    static std::vector<std::vector<double> > ATA_sparse_9x9 =
    {{1,  0,  0,   0,  0,  0,   3,  0,  0},
     {0,  2,  0,   0,  0,  0,   0,  6,  0},
     {3,  0,  4,   0,  0,  0,   9,  0, 12},
     {0,  0,  0,   2,  0,  0,   0,  0,  0},
     {0,  0,  0,   0,  4,  0,   0,  0,  0},
     {0,  0,  0,   6,  0,  8,   0,  0,  0},
     {0,  0,  0,   0,  0,  0,   4,  0,  0},
     {0,  0,  0,   0,  0,  0,   0,  8,  0},
     {0,  0,  0,   0,  0,  0,  12,  0, 16}
    };

    static std::vector<std::vector<double> > Answer_rr_sparse_9x12 =
    {{0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0},
     {0, 2, 2, 0,  0, 0, 0, 0,  0, 0, 0, 0},
     {3, 0, 0, 3,  0, 0, 0, 0,  0, 0, 0, 0},

     {0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0},
     {0, 0, 0, 0,  0, 4, 4, 0,  0, 0, 0, 0},
     {0, 0, 0, 0,  6, 0, 0, 6,  0, 0, 0, 0},

     {0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0},
     {0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0},
     {0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0}
    };

    static std::vector<std::vector<double> > Answer_sparse_9x12 =
    {{1, 1, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0},
     {0, 2, 2, 0,  0, 0, 0, 0,  0, 0, 0, 0},
     {3, 0, 0, 3,  0, 0, 0, 0,  0, 0, 0, 0},

     {0, 0, 0, 0,  2, 2, 0, 0,  0, 0, 0, 0},
     {0, 0, 0, 0,  0, 4, 4, 0,  0, 0, 0, 0},
     {0, 0, 0, 0,  6, 0, 0, 6,  0, 0, 0, 0},

     {3, 3, 0, 0,  0, 0, 0, 0,  4, 4, 0, 0},
     {0, 6, 6, 0,  0, 0, 0, 0,  0, 8, 8, 0},
     {9, 0, 0, 9,  0, 0, 0, 0, 12, 0, 0,12}
    };

    static std::vector<std::vector<double> > Answer_sparse_9x12_Lower =
    {{1, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0},
     {0, 2, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0},
     {3, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0},

     {0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0},
     {0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0},
     {0, 0, 0, 0,  6, 0, 0, 0,  0, 0, 0, 0},

     {3, 3, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0},
     {0, 6, 6, 0,  0, 0, 0, 0,  0, 0, 0, 0},
     {9, 0, 0, 9,  0, 0, 0, 0, 12, 0, 0, 0}
    };

    static std::vector<std::vector<double> > Answer_sparse_9x12_Lower_Ones =
    {{1, 1, 1, 1,  1, 1, 1, 1,  1, 1, 1, 1},
     {0, 2, 1, 1,  1, 1, 1, 1,  1, 1, 1, 1},
     {3, 0, 0, 1,  1, 1, 1, 1,  1, 1, 1, 1},

     {0, 0, 0, 0,  1, 1, 1, 1,  1, 1, 1, 1},
     {0, 0, 0, 0,  0, 1, 1, 1,  1, 1, 1, 1},
     {0, 0, 0, 0,  6, 0, 1, 1,  1, 1, 1, 1},

     {3, 3, 0, 0,  0, 0, 0, 1,  1, 1, 1, 1},
     {0, 6, 6, 0,  0, 0, 0, 0,  1, 1, 1, 1},
     {9, 0, 0, 9,  0, 0, 0, 0, 12, 1, 1, 1}
    };

    static std::vector<std::vector<double> > Answer_sparse_9x12_NotLower
    {{0, 1, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0},
     {0, 0, 2, 0,  0, 0, 0, 0,  0, 0, 0, 0},
     {0, 0, 0, 3,  0, 0, 0, 0,  0, 0, 0, 0},

     {0, 0, 0, 0,  2, 2, 0, 0,  0, 0, 0, 0},
     {0, 0, 0, 0,  0, 4, 4, 0,  0, 0, 0, 0},
     {0, 0, 0, 0,  0, 0, 0, 6,  0, 0, 0, 0},

     {0, 0, 0, 0,  0, 0, 0, 0,  4, 4, 0, 0},
     {0, 0, 0, 0,  0, 0, 0, 0,  0, 8, 8, 0},
     {0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0,12}
    };

    static std::vector<std::vector<double> > Answer_sparse_9x12_NotLower_Ones
    {{1, 1, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0},
     {1, 1, 2, 0,  0, 0, 0, 0,  0, 0, 0, 0},
     {1, 1, 1, 3,  0, 0, 0, 0,  0, 0, 0, 0},

     {1, 1, 1, 1,  2, 2, 0, 0,  0, 0, 0, 0},
     {1, 1, 1, 1,  1, 4, 4, 0,  0, 0, 0, 0},
     {1, 1, 1, 1,  1, 1, 0, 6,  0, 0, 0, 0},

     {1, 1, 1, 1,  1, 1, 1, 0,  4, 4, 0, 0},
     {1, 1, 1, 1,  1, 1, 1, 1,  0, 8, 8, 0},
     {1, 1, 1, 1,  1, 1, 1, 1,  1, 0, 0,12}
    };

    static std::vector<std::vector<double> > Answer_rc_sparse_9x12 =
    {{1, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0},
     {0, 0, 2, 0,  0, 0, 0, 0,  0, 0, 0, 0},
     {3, 0, 0, 3,  0, 0, 0, 0,  0, 0, 0, 0},

     {0, 0, 0, 0,  2, 0, 0, 0,  0, 0, 0, 0},
     {0, 0, 0, 0,  0, 0, 4, 0,  0, 0, 0, 0},
     {0, 0, 0, 0,  6, 0, 0, 6,  0, 0, 0, 0},

     {0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0},
     {0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0},
     {0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0}
    };

    static std::vector<std::vector<double> > Answer_cr_sparse_9x12 =
    {{0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0},
     {0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0},
     {0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0},

     {0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0},
     {0, 0, 0, 0,  0, 4, 4, 0,  0, 0, 0, 0},
     {0, 0, 0, 0,  6, 0, 0, 6,  0, 0, 0, 0},

     {0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0},
     {0, 0, 0, 0,  0, 0, 0, 0,  0, 8, 8, 0},
     {0, 0, 0, 0,  0, 0, 0, 0, 12, 0, 0,12}
    };

    static std::vector<std::vector<double> > Ones_4x4 =
    {{1, 1, 1, 1},
     {1, 1, 1, 1},
     {1, 1, 1, 1},
     {1, 1, 1, 1}};

    static std::vector<std::vector<double> > Ones_3x4 =
    {{1, 1, 1, 1},
     {1, 1, 1, 1},
     {1, 1, 1, 1}};

    static std::vector<std::vector<double> > Ones_3x3 =
    {{1, 1, 1},
     {1, 1, 1},
     {1, 1, 1}};

    static std::vector<std::vector<double> > Ones_9x9 =
    {{1, 1, 1, 1, 1, 1, 1, 1, 1},
     {1, 1, 1, 1, 1, 1, 1, 1, 1},
     {1, 1, 1, 1, 1, 1, 1, 1, 1},
     {1, 1, 1, 1, 1, 1, 1, 1, 1},
     {1, 1, 1, 1, 1, 1, 1, 1, 1},
     {1, 1, 1, 1, 1, 1, 1, 1, 1},
     {1, 1, 1, 1, 1, 1, 1, 1, 1},
     {1, 1, 1, 1, 1, 1, 1, 1, 1},
     {1, 1, 1, 1, 1, 1, 1, 1, 1}};

    static std::vector<std::vector<double> > Ones_9x12 =
    {{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
     {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
     {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
     {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
     {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
     {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
     {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
     {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
     {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}};

    static std::vector<std::vector<double> > Identity_3x3 =
    {{1, 0, 0},
     {0, 1, 0},
     {0, 0, 1}};

    static std::vector<std::vector<double> > Lower_3x3 =
    {{1, 0, 0},
     {1, 1, 0},
     {1, 1, 1}};

    static std::vector<std::vector<double> > Lower_3x4 =
    {{1, 0, 0, 0},
     {1, 1, 0, 0},
     {1, 1, 1, 0}};

    static std::vector<std::vector<double> > Lower_4x4 =
    {{1, 0, 0, 0},
     {1, 1, 0, 0},
     {1, 1, 1, 0},
     {1, 1, 1, 1}};

    static std::vector<std::vector<double> > NotLower_3x3 =
    {{0, 1, 1},
     {0, 0, 1},
     {0, 0, 0}};

    static std::vector<std::vector<double> > NotLower_3x4 =
    {{0, 1, 1, 1},
     {0, 0, 1, 1},
     {0, 0, 0, 1}};

    static std::vector<std::vector<double> > NotLower_4x4 =
    {{0, 1, 1, 1},
     {0, 0, 1, 1},
     {0, 0, 0, 1},
     {0, 0, 0, 0}};

    static std::vector<std::vector<double> > LowerMask_3x4 =
    {{1, 0,    0,   0},
     {1, 0.5,  0,   0},
     {1, -1.0, 1.5, 0}};

    static std::vector<std::vector<bool> > LowerBool_3x4 =
    {{true, false, false, false},
     {true, true,  false, false},
     {true, true,  true,  false}};

    static std::vector<std::vector<bool> > LowerBool_3x3 =
    {{true, false, false},
     {true, true,  false},
     {true, true,  true}};

    static std::vector<std::vector<bool> > NotLowerBool_3x3 =
    {{false,  true, true},
     {false, false, true},
     {false, false, false}};

    static std::vector<std::vector<double> > Lower_9x9 =
    {{1, 0, 0, 0,  0, 0, 0, 0,  0},
     {1, 1, 0, 0,  0, 0, 0, 0,  0},
     {1, 1, 1, 0,  0, 0, 0, 0,  0},
     {1, 1, 1, 1,  0, 0, 0, 0,  0},
     {1, 1, 1, 1,  1, 0, 0, 0,  0},
     {1, 1, 1, 1,  1, 1, 0, 0,  0},
     {1, 1, 1, 1,  1, 1, 1, 0,  0},
     {1, 1, 1, 1,  1, 1, 1, 1,  0},
     {1, 1, 1, 1,  1, 1, 1, 1,  1}};

    static std::vector<std::vector<double> > NotLower_9x9 =
    {{0, 1, 1, 1,  1, 1, 1, 1,  1},
     {0, 0, 1, 1,  1, 1, 1, 1,  1},
     {0, 0, 0, 1,  1, 1, 1, 1,  1},
     {0, 0, 0, 0,  1, 1, 1, 1,  1},
     {0, 0, 0, 0,  0, 1, 1, 1,  1},
     {0, 0, 0, 0,  0, 0, 1, 1,  1},
     {0, 0, 0, 0,  0, 0, 0, 1,  1},
     {0, 0, 0, 0,  0, 0, 0, 0,  1},
     {0, 0, 0, 0,  0, 0, 0, 0,  0}};

    static std::vector<std::vector<double> > Lower_9x12 =
    {{1, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0},
     {1, 1, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0},
     {1, 1, 1, 0,  0, 0, 0, 0,  0, 0, 0, 0},
     {1, 1, 1, 1,  0, 0, 0, 0,  0, 0, 0, 0},
     {1, 1, 1, 1,  1, 0, 0, 0,  0, 0, 0, 0},
     {1, 1, 1, 1,  1, 1, 0, 0,  0, 0, 0, 0},
     {1, 1, 1, 1,  1, 1, 1, 0,  0, 0, 0, 0},
     {1, 1, 1, 1,  1, 1, 1, 1,  0, 0, 0, 0},
     {1, 1, 1, 1,  1, 1, 1, 1,  1, 0, 0, 0}};

    static std::vector<std::vector<double> > NotLower_9x12 =
    {{0, 1, 1, 1,  1, 1, 1, 1,  1, 1, 1, 1},
     {0, 0, 1, 1,  1, 1, 1, 1,  1, 1, 1, 1},
     {0, 0, 0, 1,  1, 1, 1, 1,  1, 1, 1, 1},
     {0, 0, 0, 0,  1, 1, 1, 1,  1, 1, 1, 1},
     {0, 0, 0, 0,  0, 1, 1, 1,  1, 1, 1, 1},
     {0, 0, 0, 0,  0, 0, 1, 1,  1, 1, 1, 1},
     {0, 0, 0, 0,  0, 0, 0, 1,  1, 1, 1, 1},
     {0, 0, 0, 0,  0, 0, 0, 0,  1, 1, 1, 1},
     {0, 0, 0, 0,  0, 0, 0, 0,  0, 1, 1, 1}};

    static std::vector<std::vector<double> > M_9x9 =
        {{1, 0, 0, 1, 0, 0, 1, 0, 0},
         {1, 1, 0, 1, 1, 0, 1, 1, 0},
         {1, 1, 1, 1, 1, 1, 1, 1, 1},
         {1, 0, 0, 1, 0, 0, 1, 0, 0},
         {1, 1, 0, 1, 1, 0, 1, 1, 0},
         {1, 1, 1, 1, 1, 1, 1, 1, 1},
         {1, 0, 0, 1, 0, 0, 1, 0, 0},
         {1, 1, 0, 1, 1, 0, 1, 1, 0},
         {1, 1, 1, 1, 1, 1, 1, 1, 1}};

    static std::vector<std::vector<double> > M_9x12 =
        {{1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0},
         {1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0},
         {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
         {1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0},
         {1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0},
         {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
         {1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0},
         {1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0},
         {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}};

    static std::vector<std::vector<double> > M_10x12 =
        {{1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0},
         {1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0},
         {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
         {1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0},
         {1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0},
         {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
         {1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0},
         {1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0},
         {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
         {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}};
}

//****************************************************************************
// API error tests
//****************************************************************************

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_ATB_bad_dimensions)
{
    Matrix<double> A(AT_sparse_3x3, 0.); // 3x3
    Matrix<double> B(B_sparse_3x4, 0.); // 3x4
    Matrix<double> ones9x12(Ones_9x12, 0.);

    Matrix<double> M9x9(M_9x9, 0.);
    Matrix<double> M9x12(M_9x12, 0.);
    Matrix<double> M10x12(M_10x12, 0.);

    Matrix<double> result9x12(9, 12);
    Matrix<double> result9x9(9, 9);
    Matrix<double> result12x12(12, 12);

    // NoMask_NoAccum_AB

    // dim(C) != dim(A)*dim(B)
    BOOST_CHECK_THROW(
        (kronecker(result9x9, NoMask(), NoAccumulate(),
                   Times<double>(), transpose(A), B)),
        DimensionException);

    BOOST_CHECK_THROW(
        (kronecker(result12x12, NoMask(), NoAccumulate(),
                   Times<double>(), transpose(A), B)),
        DimensionException);

    // NoMask_Accum_AB

    // dim(C) != dim(A)*dim(B)
    BOOST_CHECK_THROW(
        (kronecker(result9x9, NoMask(), Plus<double>(),
                   Times<double>(), transpose(A), B)),
        DimensionException);

    BOOST_CHECK_THROW(
        (kronecker(result12x12, NoMask(), Plus<double>(),
                   Times<double>(), transpose(A), B)),
        DimensionException);

    // Mask_NoAccum

    // incompatible mask matrix dimensions
    // nrows(C) != nrows(M)
    BOOST_CHECK_THROW(
        (kronecker(result9x12, M10x12, NoAccumulate(),
                   Times<double>(), transpose(A), B)),
        DimensionException);

    // ncols(C) != ncols(M)
    BOOST_CHECK_THROW(
        (kronecker(result9x12, M9x9, NoAccumulate(),
                   Times<double>(), transpose(A), B)),
        DimensionException);

    BOOST_CHECK_THROW(
        (kronecker(ones9x12, M9x12, NoAccumulate(),
                   Times<double>(), transpose(A), A,
                   REPLACE)),
        DimensionException);

    // Mask_Accum (replace and merge)

    // incompatible mask matrix dimensions
    BOOST_CHECK_THROW(
        (kronecker(ones9x12, M10x12, Second<double>(),
                   Times<double>(), transpose(A), B, REPLACE)),
        DimensionException);

    // incompatible mask matrix dimensions
    BOOST_CHECK_THROW(
        (kronecker(ones9x12, M9x9, Second<double>(),
                   Times<double>(), transpose(A), B, MERGE)),
        DimensionException);

    // CompMask_NoAccum (replace and merge)

    // incompatible mask matrix dimensions
    BOOST_CHECK_THROW(
        (kronecker(ones9x12, complement(M10x12), NoAccumulate(),
                   Times<double>(), transpose(A), B, REPLACE)),
        DimensionException);

    // incompatible mask matrix dimensions
    BOOST_CHECK_THROW(
        (kronecker(ones9x12, complement(M9x9), NoAccumulate(),
                   Times<double>(), transpose(A), B, MERGE)),
        DimensionException);

    // CompMask_Accum (replace and merge)

    // incompatible mask matrix dimensions
    BOOST_CHECK_THROW(
        (kronecker(ones9x12, complement(M10x12), Plus<double>(),
                   Times<double>(), transpose(A), B, REPLACE)),
        DimensionException);

    // incompatible mask matrix dimensions
    BOOST_CHECK_THROW(
        (kronecker(ones9x12, complement(M9x9), Plus<double>(),
                   Times<double>(), transpose(A), B, MERGE)),
        DimensionException);
}

//****************************************************************************
// NoMask_NoAccum
//****************************************************************************

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_NoMask_NoAccum_ATB)
{
    Matrix<double> C(9, 12);
    Matrix<double> A(AT_sparse_3x3, 0.);
    Matrix<double> B(B_sparse_3x4, 0.);

    Matrix<double> answer(Answer_sparse_9x12, 0.);

    kronecker(C, NoMask(), NoAccumulate(), Times<double>(), transpose(A), B);

    BOOST_CHECK_EQUAL(C, answer);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_NoMask_NoAccum_ATB_empty)
{
    Matrix<double> Zero(3, 3);
    Matrix<double> Ones(Ones_3x3, 0.);
    Matrix<double> C(Ones_9x9, 0.);
    Matrix<double> mD(Ones_9x9, 0.);
    Matrix<double> Zero9x9(9, 9);

    kronecker(C, NoMask(), NoAccumulate(), Times<double>(), transpose(Zero), Ones);
    BOOST_CHECK_EQUAL(C, Zero9x9);

    kronecker(mD, NoMask(), NoAccumulate(), Times<double>(), transpose(Ones), Zero);
    BOOST_CHECK_EQUAL(mD, Zero9x9);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_NoMask_NoAccum_ATB_dense)
{
    Matrix<double> Ones3x3(Ones_3x3, 0.);
    Matrix<double> Ones3x4(Ones_3x4, 0.);
    Matrix<double> Ones9x12(Ones_9x12, 0.);
    Matrix<double> result(9, 12);

    kronecker(result, NoMask(), NoAccumulate(), Times<double>(), transpose(Ones3x3), Ones3x4);

    BOOST_CHECK_EQUAL(result, Ones9x12);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_NoMask_NoAccum_ATB_empty_rows)
{
    Matrix<double> C(9, 12);
    Matrix<double> A(Ar_sparse_3x3, 0.);
    Matrix<double> B(Br_sparse_3x4, 0.);

    Matrix<double> answer(Answer_rr_sparse_9x12, 0.);

    kronecker(C, NoMask(), NoAccumulate(), Times<double>(), transpose(A), B);

    BOOST_CHECK_EQUAL(C, answer);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_NoMask_NoAccum_ATB_emptyRowA_emptyColB)
{
    Matrix<double> result(9, 12);
    Matrix<double> A(Ar_sparse_3x3, 0.);
    Matrix<double> B(Bc_sparse_3x4, 0.);
    Matrix<double> answer(Answer_rc_sparse_9x12, 0.);

    kronecker(result, NoMask(), NoAccumulate(),
              Times<double>(), transpose(A), B);
    BOOST_CHECK_EQUAL(result, answer);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_NoMask_NoAccum_ATB_emptyColA_emptyRowB)
{
    Matrix<double> result(9, 12);
    Matrix<double> A(Ac_sparse_3x3, 0.);
    Matrix<double> B(Br_sparse_3x4, 0.);
    Matrix<double> answer(Answer_cr_sparse_9x12, 0.);

    kronecker(result, NoMask(), NoAccumulate(),
              Times<double>(), transpose(A), B);
    BOOST_CHECK_EQUAL(result, answer);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_NoMask_NoAccum_ATB_ABdup)
{
    // Build some matrices.
    Matrix<double, DirectedMatrixTag> mat(A_sparse_3x3, 0.);
    Matrix<double, DirectedMatrixTag> m3(9, 9);
    Matrix<double, DirectedMatrixTag> answer(ATA_sparse_9x9, 0.);

    kronecker(m3, NoMask(), NoAccumulate(),
              Times<double>(), transpose(mat), mat);

    BOOST_CHECK_EQUAL(m3, answer);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_NoMask_NoAccum_ATB_ACdup)
{
    Matrix<double> C(A_sparse_3x3, 0.);
    Matrix<double> B(1, 1);
    B.setElement(0, 0, 1.0);

    Matrix<double> answer(AT_sparse_3x3, 0.);

    kronecker(C, NoMask(), NoAccumulate(),
              Times<double>(), transpose(C), B);

    BOOST_CHECK_EQUAL(C, answer);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_NoMask_NoAccum_ATB_BCdup)
{
    Matrix<double> A(1, 1);
    Matrix<double> C(A_sparse_3x3, 0.);
    A.setElement(0, 0, 1.0);

    Matrix<double> answer(A_sparse_3x3, 0.);

    kronecker(C, NoMask(), NoAccumulate(),
                   Times<double>(), transpose(A), C);

    BOOST_CHECK_EQUAL(C, answer);
}

//****************************************************************************
// NoMask_Accum
//****************************************************************************

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_NoMask_Accum_ATB)
{
    Matrix<double> C(Ones_9x12, 0.);
    Matrix<double> A(AT_sparse_3x3, 0.); // 3x3
    Matrix<double> B(B_sparse_3x4, 0.); // 3x4

    Matrix<double> answer(Answer_sparse_9x12, 0.);
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(), answer, C);

    kronecker(C,
              NoMask(),
              Plus<double>(),
              Times<double>(), transpose(A), B);
    BOOST_CHECK_EQUAL(C, answer);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_NoMask_Accum_ATB_empty)
{
    Matrix<double> Zero(3, 3);
    Matrix<double> Ones(Ones_3x3, 0.);
    Matrix<double> C(Ones_9x9, 0.);
    Matrix<double> mD(Ones_9x9, 0.);
    Matrix<double> answer(Ones_9x9, 0.);

    kronecker(C, NoMask(), Plus<double>(), Times<double>(), transpose(Zero), Ones);
    BOOST_CHECK_EQUAL(C, answer);

    kronecker(mD, NoMask(), Plus<double>(), Times<double>(), transpose(Ones), Zero);
    BOOST_CHECK_EQUAL(mD, answer);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_NoMask_Accum_ATB_dense)
{
    Matrix<double> Ones3x3(Ones_3x3, 0.);
    Matrix<double> Ones3x4(Ones_3x4, 0.);
    Matrix<double> Ones9x12(Ones_9x12, 0.);
    Matrix<double> result(Ones_9x12, 0.);
    Matrix<double> answer(9, 12);
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(),
             Ones9x12, Ones9x12);

    kronecker(result, NoMask(), Plus<double>(),
              Times<double>(), transpose(Ones3x3), Ones3x4);

    BOOST_CHECK_EQUAL(result, answer);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_NoMask_Accum_ATB_empty_rows)
{
    Matrix<double> C(Ones_9x12, 0.);
    Matrix<double> A(Ar_sparse_3x3, 0.);
    Matrix<double> B(Br_sparse_3x4, 0.);

    Matrix<double> answer(Answer_rr_sparse_9x12, 0.);
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(), C, answer);

    kronecker(C, NoMask(), Plus<double>(), Times<double>(), transpose(A), B);

    BOOST_CHECK_EQUAL(C, answer);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_NoMask_Accum_ATB_emptyRowA_emptyColB)
{
    Matrix<double> result(Ones_9x12, 0.);
    Matrix<double> A(Ar_sparse_3x3, 0.);
    Matrix<double> B(Bc_sparse_3x4, 0.);
    Matrix<double> answer(Answer_rc_sparse_9x12, 0.);
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(), answer, result);

    kronecker(result, NoMask(), Plus<double>(),
              Times<double>(), transpose(A), B);
    BOOST_CHECK_EQUAL(result, answer);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_NoMask_Accum_ATB_emptyColA_emptyRowB)
{
    Matrix<double> result(Ones_9x12, 0.);
    Matrix<double> A(Ac_sparse_3x3, 0.);
    Matrix<double> B(Br_sparse_3x4, 0.);
    Matrix<double> answer(Answer_cr_sparse_9x12, 0.);
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(), answer, result);

    kronecker(result, NoMask(), Plus<double>(),
              Times<double>(), transpose(A), B);
    BOOST_CHECK_EQUAL(result, answer);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_NoMask_Accum_ATB_ABdup)
{
    // Build some matrices.
    Matrix<double, DirectedMatrixTag> mat(A_sparse_3x3, 0.);
    Matrix<double, DirectedMatrixTag> m3(Ones_9x9, 0.);
    Matrix<double, DirectedMatrixTag> answer(ATA_sparse_9x9, 0.);
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(), answer, m3);

    kronecker(m3, NoMask(), Plus<double>(), Times<double>(), transpose(mat), mat);

    BOOST_CHECK_EQUAL(m3, answer);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_NoMask_Accum_ATB_ACdup)
{
    Matrix<double> C(A_sparse_3x3, 0.);
    Matrix<double> B(1, 1);  B.setElement(0, 0, 1.0);
    Matrix<double> answer(AT_sparse_3x3, 0.);
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(), answer, transpose(answer));

    kronecker(C, NoMask(), Plus<double>(), Times<double>(), transpose(C), B);

    BOOST_CHECK_EQUAL(C, answer);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_NoMask_Accum_ATB_BCdup)
{
    Matrix<double> A(1, 1);  A.setElement(0, 0, 1.0);
    Matrix<double> C(A_sparse_3x3, 0.);
    Matrix<double> answer(A_sparse_3x3, 0.);
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(), answer, answer);

    kronecker(C, NoMask(), Plus<double>(), Times<double>(), transpose(A), C);

    BOOST_CHECK_EQUAL(C, answer);
}

// ****************************************************************************
// Mask_NoAccum
// ****************************************************************************

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_Mask_NoAccum_ATB)
{
    Matrix<double> C(9, 12);
    Matrix<double> A(AT_sparse_3x3, 0.); // 1 0 0 / 0 2 0 / 3 0 4
    Matrix<double> B(B_sparse_3x4, 0.); // 1 1 0 0 / 0 2 2 0 / 3 0 0 3

    Matrix<double> Identity(Identity_3x3, 0.0);

    Matrix<double> Empty(9, 12);
    Matrix<double> Ones(Ones_9x12, 0.);

    Matrix<double> MLower(Lower_9x12, 0.);
    Matrix<double> MNotLower(NotLower_9x12, 0.);

    Matrix<double> Answer_9x12(Answer_sparse_9x12, 0.);
    Matrix<double> Answer_9x12_Lower(Answer_sparse_9x12_Lower, 0.);
    Matrix<double> Answer_9x12_NotLower(Answer_sparse_9x12_NotLower, 0.);
    Matrix<double> Answer_9x12_Lower_Ones(Answer_sparse_9x12_Lower_Ones, 0.);
    Matrix<double> Answer_9x12_NotLower_Ones(Answer_sparse_9x12_NotLower_Ones, 0.);

    // Merge
    // Mempty vs Mfull vs Mlower

    C = Ones;
    kronecker(C, Empty, NoAccumulate(), Times<double>(), transpose(A), B);
    BOOST_CHECK_EQUAL(C, Ones);

    C = Ones;
    kronecker(C, Ones,  NoAccumulate(), Times<double>(), transpose(A), B);
    BOOST_CHECK_EQUAL(C, Answer_9x12);

    C = Ones;
    kronecker(C, MLower, NoAccumulate(), Times<double>(), transpose(A), B);
    BOOST_CHECK_EQUAL(C, Answer_9x12_Lower_Ones);

    C = Ones;
    kronecker(C, MNotLower, NoAccumulate(), Times<double>(), transpose(A), B);
    BOOST_CHECK_EQUAL(C, Answer_9x12_NotLower_Ones);

    // Replace
    // Mempty vs Mfull vs Mlower

    C = Ones;
    kronecker(C, Empty, NoAccumulate(), Times<double>(), transpose(A), B, REPLACE);
    BOOST_CHECK_EQUAL(C, Empty);

    C = Ones;
    kronecker(C, Ones,  NoAccumulate(), Times<double>(), transpose(A), B, REPLACE);
    BOOST_CHECK_EQUAL(C, Answer_9x12);

    C = Ones;
    kronecker(C, MLower, NoAccumulate(), Times<double>(), transpose(A), B, REPLACE);
    BOOST_CHECK_EQUAL(C, Answer_9x12_Lower);

    C = Ones;
    kronecker(C, MNotLower, NoAccumulate(), Times<double>(), transpose(A), B, REPLACE);
    BOOST_CHECK_EQUAL(C, Answer_9x12_NotLower);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_Mask_NoAccum_ATBM_empty)
{
    Matrix<double> C(9, 12);
    Matrix<double> A(AT_sparse_3x3, 0.); // 1 0 0 / 0 2 0 / 3 0 4
    Matrix<double> B(B_sparse_3x4, 0.); // 1 1 0 0 / 0 2 2 0 / 3 0 0 3

    Matrix<double> Ones(Ones_9x12, 0.);
    Matrix<double> Empty(9, 12);
    Matrix<double> Empty3x3(3, 3);
    Matrix<double> Empty3x4(3, 4);

    Matrix<double> MLower(Lower_9x12, 0.);
    Matrix<double> MNotLower(NotLower_9x12, 0.);

    // Merge
    C = Ones;
    kronecker(C, Empty, NoAccumulate(), Times<double>(), transpose(A), B);
    BOOST_CHECK_EQUAL(C, Ones);

    C = Ones;
    kronecker(C, MLower, NoAccumulate(), Times<double>(), transpose(Empty3x3), B);
    BOOST_CHECK_EQUAL(C, MNotLower);

    C = Ones;
    kronecker(C, MLower, NoAccumulate(), Times<double>(), transpose(A), Empty3x4);
    BOOST_CHECK_EQUAL(C, MNotLower);

    // Replace
    C = Ones;
    kronecker(C, Empty, NoAccumulate(), Times<double>(), transpose(A), B, REPLACE);
    BOOST_CHECK_EQUAL(C, Empty);

    C = Ones;
    kronecker(C, MLower, NoAccumulate(), Times<double>(), transpose(Empty3x3), B, REPLACE);
    BOOST_CHECK_EQUAL(C, Empty);

    C = Ones;
    kronecker(C, MLower, NoAccumulate(), Times<double>(), transpose(A), Empty3x4, REPLACE);
    BOOST_CHECK_EQUAL(C, Empty);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_Mask_NoAccum_ATBM_dense)
{
    Matrix<double> A(Ones_3x3, 0.);
    Matrix<double> B(Ones_3x4, 0.);

    Matrix<double> MLower(Lower_9x12, 0.);
    Matrix<double> MNotLower(NotLower_9x12, 0.);

    Matrix<double> Ones(Ones_9x12, 0.);
    Matrix<double> C(9, 12);

    C = Ones;
    kronecker(C, MLower, NoAccumulate(), Times<double>(), transpose(A), B);
    BOOST_CHECK_EQUAL(C, Ones);

    C = Ones;
    kronecker(C, MLower, NoAccumulate(), Times<double>(), transpose(A), B, REPLACE);
    BOOST_CHECK_EQUAL(C, MLower);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_Mask_NoAccum_ATB_empty_rows)
{
    Matrix<double> Ones(Ones_9x12, 0.);
    Matrix<double> A(Ar_sparse_3x3, 0.);
    Matrix<double> B(Br_sparse_3x4, 0.);
    Matrix<double> answer(Answer_rr_sparse_9x12, 0.);
    Matrix<double> C(9, 12);

    Matrix<double> MLower(Lower_9x12, 0.);
    Matrix<double> MNotLower(NotLower_9x12, 0.);

    C = Ones;
    eWiseMult(answer, NoMask(), NoAccumulate(), Times<double>(), MLower, answer);
    kronecker(C, MLower, NoAccumulate(), Times<double>(), transpose(A), B, REPLACE);
    BOOST_CHECK_EQUAL(C, answer);

    C = Ones;
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(), MNotLower, answer);
    kronecker(C, MLower, NoAccumulate(), Times<double>(), transpose(A), B);
    BOOST_CHECK_EQUAL(C, answer);

}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_Mask_NoAccum_ATB_emptyRowA_emptyColB)
{
    Matrix<double> C(9,12);
    Matrix<double> Ones(Ones_9x12, 0.);
    Matrix<double> A(Ar_sparse_3x3, 0.);
    Matrix<double> B(Bc_sparse_3x4, 0.);
    Matrix<double> answer(Answer_rc_sparse_9x12, 0.);

    Matrix<double> MLower(Lower_9x12, 0.);
    Matrix<double> MNotLower(NotLower_9x12, 0.);

    // REPLACE
    C = Ones;
    eWiseMult(answer, NoMask(), NoAccumulate(), Times<double>(), MLower, answer);
    kronecker(C, MLower, NoAccumulate(), Times<double>(), transpose(A), B, REPLACE);
    BOOST_CHECK_EQUAL(C, answer);

    // Merge
    C = Ones;
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(), MNotLower, answer);
    kronecker(C, MLower, NoAccumulate(), Times<double>(), transpose(A), B);
    BOOST_CHECK_EQUAL(C, answer);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_Mask_NoAccum_ATB_emptyColA_emptyRowB)
{
    Matrix<double> C(9,12);
    Matrix<double> Ones(Ones_9x12, 0.);
    Matrix<double> A(Ac_sparse_3x3, 0.);
    Matrix<double> B(Br_sparse_3x4, 0.);
    Matrix<double> answer(Answer_cr_sparse_9x12, 0.);

    Matrix<double> MLower(Lower_9x12, 0.);
    Matrix<double> MNotLower(NotLower_9x12, 0.);

    // REPLACE
    C = Ones;
    eWiseMult(answer, NoMask(), NoAccumulate(), Times<double>(), MLower, answer);
    kronecker(C, MLower, NoAccumulate(), Times<double>(), transpose(A), B, REPLACE);
    BOOST_CHECK_EQUAL(C, answer);

    // Merge
    C = Ones;
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(), MNotLower, answer);
    kronecker(C, MLower, NoAccumulate(), Times<double>(), transpose(A), B);
    BOOST_CHECK_EQUAL(C, answer);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_Mask_NoAccum_ATB_emptyRowM)
{
    Matrix<double> C(9,12);
    Matrix<double> Ones(Ones_9x12, 0.);
    Matrix<double> A(AT_sparse_3x3, 0.);
    Matrix<double> B(B_sparse_3x4, 0.);
    Matrix<double> answer(Answer_sparse_9x12, 0.);

    Matrix<double> MLower(Lower_9x12, 0.);
    Matrix<double> MNotLower(NotLower_9x12, 0.);

    // Replace
    C = Ones;
    eWiseMult(answer, NoMask(), NoAccumulate(), Times<double>(), MLower, answer);
    kronecker(C, MLower, NoAccumulate(), Times<double>(), transpose(A), B, REPLACE);
    BOOST_CHECK_EQUAL(C, answer);

    // Merge
    C = Ones;
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(), MNotLower, answer);
    kronecker(C, MLower, NoAccumulate(), Times<double>(), transpose(A), B);
    BOOST_CHECK_EQUAL(C, answer);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_Mask_NoAccum_ATB_ABdup)
{
    Matrix<double> C(9,9);
    Matrix<double> Ones(Ones_9x9, 0.);
    Matrix<double> A(A_sparse_3x3, 0.);
    Matrix<double> answer(ATA_sparse_9x9, 0.);

    Matrix<double> MLower(Lower_9x9, 0.);
    Matrix<double> MNotLower(NotLower_9x9, 0.);

    // Replace
    C = Ones;
    eWiseMult(answer, NoMask(), NoAccumulate(), Times<double>(), MLower, answer);
    kronecker(C, MLower, NoAccumulate(), Times<double>(), transpose(A), A, REPLACE);

    BOOST_CHECK_EQUAL(C, answer);

    // Merge
    C = Ones;
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(), MNotLower, answer);
    kronecker(C, MLower, NoAccumulate(), Times<double>(), transpose(A), A);
    BOOST_CHECK_EQUAL(C, answer);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_Mask_NoAccum_ATB_ACdup)
{
    Matrix<double> mat(A_sparse_3x3, 0.);
    Matrix<double> C(3, 3);
    Matrix<double> B(1, 1);    B.setElement(0, 0, 1.0);
    Matrix<double> answer(AT_sparse_3x3, 0.);

    Matrix<double> MLower(Lower_3x3, 0.);
    Matrix<double> MNotLower(NotLower_3x3, 0.);

    // Build some matrices.

    // Replace
    C = mat;
    eWiseMult(answer, NoMask(), NoAccumulate(), Times<double>(), MLower, answer);
    kronecker(C, MLower, NoAccumulate(), Times<double>(), transpose(C), B, REPLACE);

    BOOST_CHECK_EQUAL(C, answer);

    // Merge
    C = mat;
    kronecker(C, MLower, NoAccumulate(), Times<double>(), transpose(C), B);

    BOOST_CHECK_EQUAL(C, answer);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_Mask_NoAccum_ATB_BCdup)
{
    // Build some matrices.
    Matrix<double> mat(A_sparse_3x3, 0.);
    Matrix<double> C(3,3);
    Matrix<double> A(1, 1);    A.setElement(0, 0, 1.0);
    Matrix<double> answer(mat);

    Matrix<double> MLower(Lower_3x3, 0.);
    Matrix<double> MNotLower(NotLower_3x3, 0.);

    // Replace
    C = mat;
    eWiseMult(answer, NoMask(), NoAccumulate(), Times<double>(), MLower, answer);
    kronecker(C, MLower, NoAccumulate(), Times<double>(), transpose(A), C, REPLACE);

    BOOST_CHECK_EQUAL(C, answer);

    // Merge
    C = mat;
    kronecker(C, MLower, NoAccumulate(), Times<double>(), transpose(A), C);

    BOOST_CHECK_EQUAL(C, mat);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_Mask_NoAccum_ATB_MCdup)
{
    // Build some matrices.
    Matrix<double> C(9,12);
    Matrix<double> Ones(Ones_9x12, 0.);
    Matrix<double> A(AT_sparse_3x3, 0.);
    Matrix<double> B(B_sparse_3x4, 0.);
    Matrix<double> answer(Answer_sparse_9x12, 0.);

    Matrix<double> MLower(Lower_9x12, 0.);
    Matrix<double> MNotLower(NotLower_9x12, 0.);

    // Replace
    C = MLower;
    eWiseMult(answer, NoMask(), NoAccumulate(), Times<double>(), MLower, answer);
    kronecker(C, C, NoAccumulate(), Times<double>(), transpose(A), B, REPLACE);

    BOOST_CHECK_EQUAL(C, answer);

    // Merge
    C = MLower;
    kronecker(C, C, NoAccumulate(), Times<double>(), transpose(A), B);

    BOOST_CHECK_EQUAL(C, answer);
}

//****************************************************************************
// Mask_Accum
//****************************************************************************

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_Mask_Accum_ATB)
{
    Matrix<double> C(9, 12);
    Matrix<double> A(AT_sparse_3x3, 0.); // 1 0 0 / 0 2 0 / 3 0 4
    Matrix<double> B(B_sparse_3x4, 0.); // 1 1 0 0 / 0 2 2 0 / 3 0 0 3

    Matrix<double> Identity(Identity_3x3, 0.0);

    Matrix<double> answer(9, 12);
    Matrix<double> Empty(9, 12);
    Matrix<double> Ones(Ones_9x12, 0.);

    Matrix<double> MLower(Lower_9x12, 0.);
    Matrix<double> MNotLower(NotLower_9x12, 0.);

    Matrix<double> Answer_9x12(Answer_sparse_9x12, 0.);
    Matrix<double> Answer_9x12_Lower(Answer_sparse_9x12_Lower, 0.);
    Matrix<double> Answer_9x12_NotLower(Answer_sparse_9x12_NotLower, 0.);
    Matrix<double> Answer_9x12_Lower_Ones(Answer_sparse_9x12_Lower_Ones, 0.);
    Matrix<double> Answer_9x12_NotLower_Ones(Answer_sparse_9x12_NotLower_Ones, 0.);

    // Merge
    // Mempty vs Mfull vs Mlower

    C = Ones;
    kronecker(C, Empty, Plus<double>(), Times<double>(), transpose(A), B);
    BOOST_CHECK_EQUAL(C, Ones);

    C = Ones;
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(),
             Ones, Answer_9x12);
    kronecker(C, Ones,  Plus<double>(), Times<double>(), transpose(A), B);
    BOOST_CHECK_EQUAL(C, answer); //Answer_9x12);

    C = Ones;
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(),
             Ones, Answer_9x12_Lower);
    kronecker(C, MLower, Plus<double>(), Times<double>(), transpose(A), B);
    BOOST_CHECK_EQUAL(C, answer);

    C = Ones;
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(),
             Ones, Answer_9x12_NotLower);
    kronecker(C, MNotLower, Plus<double>(), Times<double>(), transpose(A), B);
    BOOST_CHECK_EQUAL(C, answer);

    // Replace
    // Mempty vs Mfull vs Mlower

    C = Ones;
    kronecker(C, Empty, Plus<double>(), Times<double>(), transpose(A), B, REPLACE);
    BOOST_CHECK_EQUAL(C, Empty);

    C = Ones;
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(),
             Ones, Answer_9x12);
    kronecker(C, Ones,  Plus<double>(), Times<double>(), transpose(A), B, REPLACE);
    BOOST_CHECK_EQUAL(C, answer);

    C = Ones;
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(),
             MLower, Answer_9x12_Lower);
    kronecker(C, MLower, Plus<double>(), Times<double>(), transpose(A), B, REPLACE);
    BOOST_CHECK_EQUAL(C, answer);

    C = Ones;
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(),
             MNotLower, Answer_9x12_NotLower);
    kronecker(C, MNotLower, Plus<double>(), Times<double>(), transpose(A), B, REPLACE);
    BOOST_CHECK_EQUAL(C, answer);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_Mask_Accum_ATBM_empty)
{
    Matrix<double> C(9, 12);
    Matrix<double> A(A_sparse_3x3, 0.); // 1 0 0 / 0 2 0 / 3 0 4
    Matrix<double> B(B_sparse_3x4, 0.); // 1 1 0 0 / 0 2 2 0 / 3 0 0 3

    Matrix<double> Ones(Ones_9x12, 0.);
    Matrix<double> Empty(9, 12);
    Matrix<double> Empty3x3(3, 3);
    Matrix<double> Empty3x4(3, 4);

    Matrix<double> MLower(Lower_9x12, 0.);

    // Merge
    C = Ones;
    kronecker(C, Empty, Plus<double>(), Times<double>(), transpose(A), B);
    BOOST_CHECK_EQUAL(C, Ones);

    C = Ones;
    kronecker(C, MLower, Plus<double>(), Times<double>(), transpose(Empty3x3), B);
    BOOST_CHECK_EQUAL(C, Ones);

    C = Ones;
    kronecker(C, MLower, Plus<double>(), Times<double>(), transpose(A), Empty3x4);
    BOOST_CHECK_EQUAL(C, Ones);

    // Replace
    C = Ones;
    kronecker(C, Empty, Plus<double>(), Times<double>(), transpose(A), B, REPLACE);
    BOOST_CHECK_EQUAL(C, Empty);

    C = Ones;
    kronecker(C, MLower, Plus<double>(), Times<double>(), transpose(Empty3x3), B, REPLACE);
    BOOST_CHECK_EQUAL(C, MLower);

    C = Ones;
    kronecker(C, MLower, Plus<double>(), Times<double>(), transpose(A), Empty3x4, REPLACE);
    BOOST_CHECK_EQUAL(C, MLower);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_Mask_Accum_ATBM_dense)
{
    Matrix<double> A(Ones_3x3, 0.);
    Matrix<double> B(Ones_3x4, 0.);

    Matrix<double> MLower(Lower_9x12, 0.);
    Matrix<double> MNotLower(NotLower_9x12, 0.);

    Matrix<double> Ones(Ones_9x12, 0.);
    Matrix<double> C(9, 12);
    Matrix<double> answer(9, 12);

    C = Ones;
    kronecker(C, MLower, Plus<double>(), Times<double>(), transpose(A), B);
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(),
             Ones, MLower);
    BOOST_CHECK_EQUAL(C, answer);

    C = Ones;
    kronecker(C, MLower, Plus<double>(), Times<double>(), transpose(A), B, REPLACE);
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(),
             MLower, MLower);
    BOOST_CHECK_EQUAL(C, answer);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_Mask_Accum_ATB_empty_rows)
{
    Matrix<double> Ones(Ones_9x12, 0.);
    Matrix<double> A(Ar_sparse_3x3, 0.);
    Matrix<double> B(Br_sparse_3x4, 0.);
    Matrix<double> answer(Answer_rr_sparse_9x12, 0.);
    Matrix<double> C(9, 12);

    Matrix<double> MLower(Lower_9x12, 0.);
    Matrix<double> MNotLower(NotLower_9x12, 0.);

    C = Ones;
    eWiseMult(answer, NoMask(), NoAccumulate(), Times<double>(), MLower, answer);
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(), MLower, answer);
    kronecker(C, MLower, Plus<double>(), Times<double>(), transpose(A), B, REPLACE);
    BOOST_CHECK_EQUAL(C, answer);

    C = Ones;
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(), MNotLower, answer);
    kronecker(C, MLower, Plus<double>(), Times<double>(), transpose(A), B);
    BOOST_CHECK_EQUAL(C, answer);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_Mask_Accum_ATB_emptyRowA_emptyColB)
{
    Matrix<double> C(9,12);
    Matrix<double> Ones(Ones_9x12, 0.);
    Matrix<double> A(Ar_sparse_3x3, 0.);
    Matrix<double> B(Bc_sparse_3x4, 0.);
    Matrix<double> answer(Answer_rc_sparse_9x12, 0.);

    Matrix<double> MLower(Lower_9x12, 0.);
    Matrix<double> MNotLower(NotLower_9x12, 0.);

    // REPLACE
    C = Ones;
    eWiseMult(answer, NoMask(), NoAccumulate(), Times<double>(), MLower, answer);
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(), MLower, answer);
    kronecker(C, MLower, Plus<double>(), Times<double>(), transpose(A), B, REPLACE);
    BOOST_CHECK_EQUAL(C, answer);

    // Merge
    C = Ones;
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(), MNotLower, answer);
    kronecker(C, MLower, Plus<double>(), Times<double>(), transpose(A), B);
    BOOST_CHECK_EQUAL(C, answer);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_Mask_Accum_ATB_emptyColA_emptyRowB)
{
    Matrix<double> C(9,12);
    Matrix<double> Ones(Ones_9x12, 0.);
    Matrix<double> A(Ac_sparse_3x3, 0.);
    Matrix<double> B(Br_sparse_3x4, 0.);
    Matrix<double> answer(Answer_cr_sparse_9x12, 0.);

    Matrix<double> MLower(Lower_9x12, 0.);
    Matrix<double> MNotLower(NotLower_9x12, 0.);

    // REPLACE
    C = Ones;
    eWiseMult(answer, NoMask(), NoAccumulate(), Times<double>(), MLower, answer);
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(), MLower, answer);
    kronecker(C, MLower, Plus<double>(), Times<double>(), transpose(A), B, REPLACE);
    BOOST_CHECK_EQUAL(C, answer);

    // Merge
    C = Ones;
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(), MNotLower, answer);
    kronecker(C, MLower, Plus<double>(), Times<double>(), transpose(A), B);
    BOOST_CHECK_EQUAL(C, answer);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_Mask_Accum_ATB_emptyRowM)
{
    Matrix<double> C(9,12);
    Matrix<double> Ones(Ones_9x12, 0.);
    Matrix<double> A(AT_sparse_3x3, 0.);
    Matrix<double> B(B_sparse_3x4, 0.);
    Matrix<double> answer(Answer_sparse_9x12, 0.);

    Matrix<double> MLower(Lower_9x12, 0.);
    Matrix<double> MNotLower(NotLower_9x12, 0.);

    // Replace
    C = Ones;
    eWiseMult(answer, NoMask(), NoAccumulate(), Times<double>(), MLower, answer);
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(), MLower, answer);
    kronecker(C, MLower, Plus<double>(), Times<double>(), transpose(A), B, REPLACE);
    BOOST_CHECK_EQUAL(C, answer);

    // Merge
    C = Ones;
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(), MNotLower, answer);
    kronecker(C, MLower, Plus<double>(), Times<double>(), transpose(A), B);
    BOOST_CHECK_EQUAL(C, answer);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_Mask_Accum_ATB_ABdup)
{
    Matrix<double> C(9,9);
    Matrix<double> Ones(Ones_9x9, 0.);
    Matrix<double> A(A_sparse_3x3, 0.);
    Matrix<double> answer(ATA_sparse_9x9, 0.);

    Matrix<double> MLower(Lower_9x9, 0.);
    Matrix<double> MNotLower(NotLower_9x9, 0.);

    // Replace
    C = Ones;
    eWiseMult(answer, NoMask(), NoAccumulate(), Times<double>(), MLower, answer);
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(), MLower, answer);
    kronecker(C, MLower, Plus<double>(), Times<double>(), transpose(A), A, REPLACE);

    BOOST_CHECK_EQUAL(C, answer);

    // Merge
    C = Ones;
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(), MNotLower, answer);
    kronecker(C, MLower, Plus<double>(), Times<double>(), transpose(A), A);
    BOOST_CHECK_EQUAL(C, answer);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_Mask_Accum_ATB_ACdup)
{
    // Build some matrices.
    Matrix<double> mat(A_sparse_3x3, 0.);
    Matrix<double> C(3, 3);
    Matrix<double> B(1, 1);    B.setElement(0, 0, 1.0);
    Matrix<double> answer(AT_sparse_3x3, 0.);

    Matrix<double> MLower(Lower_3x3, 0.);
    Matrix<double> MNotLower(NotLower_3x3, 0.);

    // Replace
    C = mat;
    eWiseMult(answer, NoMask(), NoAccumulate(), Times<double>(), MLower, transpose(mat));
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(), mat, answer);
    kronecker(C, MLower, Plus<double>(), Times<double>(), transpose(C), B, REPLACE);

    BOOST_CHECK_EQUAL(C, answer);

    // Merge
    C = mat;
    kronecker(C, MLower, Plus<double>(), Times<double>(), transpose(C), B);
    BOOST_CHECK_EQUAL(C, answer);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_Mask_Accum_ATB_BCdup)
{
    // Build some matrices.
    Matrix<double> mat(B_sparse_3x4, 0.);
    Matrix<double> C(3,4);
    Matrix<double> A(1, 1);    A.setElement(0, 0, 1.0);
    Matrix<double> answer(mat);

    Matrix<double> MLower(Lower_3x4, 0.);
    Matrix<double> MNotLower(NotLower_3x4, 0.);

    // Replace
    C = mat;
    eWiseMult(answer, NoMask(), NoAccumulate(), Times<double>(), MLower, mat);
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(), answer, answer);
    kronecker(C, MLower, Plus<double>(), Times<double>(), transpose(A), C, REPLACE);

    BOOST_CHECK_EQUAL(C, answer);

    // Merge
    C = mat;
    eWiseMult(answer, NoMask(), NoAccumulate(), Times<double>(), MLower, mat);
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(), answer, mat);
    kronecker(C, MLower, Plus<double>(), Times<double>(), transpose(A), C);

    BOOST_CHECK_EQUAL(C, answer);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_Mask_Accum_ATB_MCdup)
{
    // Build some matrices.
    Matrix<double> C(9,12);
    Matrix<double> Ones(Ones_9x12, 0.);
    Matrix<double> A(AT_sparse_3x3, 0.);
    Matrix<double> B(B_sparse_3x4, 0.);
    Matrix<double> answer(Answer_sparse_9x12, 0.);

    Matrix<double> MLower(Lower_9x12, 0.);
    Matrix<double> MNotLower(NotLower_9x12, 0.);

    // Replace
    C = MLower;
    eWiseMult(answer, NoMask(), NoAccumulate(), Times<double>(), MLower, answer);
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(), MLower, answer);
    kronecker(C, C, Plus<double>(), Times<double>(), transpose(A), B, REPLACE);

    BOOST_CHECK_EQUAL(C, answer);

    // Merge
    C = MLower;
    kronecker(C, C, Plus<double>(), Times<double>(), transpose(A), B);

    BOOST_CHECK_EQUAL(C, answer);
}

//****************************************************************************
// CompMask_NoAccum
//****************************************************************************

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_CompMask_NoAccum_ATB)
{
    Matrix<double> C(9, 12);
    Matrix<double> A(AT_sparse_3x3, 0.); // 1 0 0 / 0 2 0 / 3 0 4
    Matrix<double> B(B_sparse_3x4, 0.); // 1 1 0 0 / 0 2 2 0 / 3 0 0 3

    Matrix<double> Identity(Identity_3x3, 0.0);

    Matrix<double> Empty(9, 12);
    Matrix<double> Ones(Ones_9x12, 0.);

    Matrix<double> MLower(Lower_9x12, 0.);
    Matrix<double> MNotLower(NotLower_9x12, 0.);

    Matrix<double> Answer_9x12(Answer_sparse_9x12, 0.);
    Matrix<double> Answer_9x12_Lower(Answer_sparse_9x12_Lower, 0.);
    Matrix<double> Answer_9x12_NotLower(Answer_sparse_9x12_NotLower, 0.);
    Matrix<double> Answer_9x12_Lower_Ones(Answer_sparse_9x12_Lower_Ones, 0.);
    Matrix<double> Answer_9x12_NotLower_Ones(Answer_sparse_9x12_NotLower_Ones, 0.);

    // Merge
    // Mempty vs Mfull vs Mlower

    C = Ones;
    kronecker(C, complement(Ones), NoAccumulate(), Times<double>(), transpose(A), B);
    BOOST_CHECK_EQUAL(C, Ones);

    C = Ones;
    kronecker(C, complement(Empty),  NoAccumulate(), Times<double>(), transpose(A), B);
    BOOST_CHECK_EQUAL(C, Answer_9x12);

    C = Ones;
    kronecker(C, complement(MNotLower), NoAccumulate(), Times<double>(), transpose(A), B);
    BOOST_CHECK_EQUAL(C, Answer_9x12_Lower_Ones);

    C = Ones;
    kronecker(C, complement(MLower), NoAccumulate(), Times<double>(), transpose(A), B);
    BOOST_CHECK_EQUAL(C, Answer_9x12_NotLower_Ones);

    // Replace
    // Mempty vs Mfull vs Mlower

    C = Ones;
    kronecker(C, complement(Ones), NoAccumulate(), Times<double>(), transpose(A), B, REPLACE);
    BOOST_CHECK_EQUAL(C, Empty);

    C = Ones;
    kronecker(C, complement(Empty),  NoAccumulate(), Times<double>(), transpose(A), B, REPLACE);
    BOOST_CHECK_EQUAL(C, Answer_9x12);

    C = Ones;
    kronecker(C, complement(MNotLower), NoAccumulate(), Times<double>(), transpose(A), B, REPLACE);
    BOOST_CHECK_EQUAL(C, Answer_9x12_Lower);

    C = Ones;
    kronecker(C, complement(MLower), NoAccumulate(), Times<double>(), transpose(A), B, REPLACE);
    BOOST_CHECK_EQUAL(C, Answer_9x12_NotLower);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_CompMask_NoAccum_ATBM_empty)
{
    Matrix<double> C(9, 12);
    Matrix<double> A(AT_sparse_3x3, 0.); // 1 0 0 / 0 2 0 / 3 0 4
    Matrix<double> B(B_sparse_3x4, 0.); // 1 1 0 0 / 0 2 2 0 / 3 0 0 3

    Matrix<double> Ones(Ones_9x12, 0.);
    Matrix<double> Empty(9, 12);
    Matrix<double> Empty3x3(3, 3);
    Matrix<double> Empty3x4(3, 4);

    Matrix<double> MLower(Lower_9x12, 0.);
    Matrix<double> MNotLower(NotLower_9x12, 0.);

    // Merge
    C = Ones;
    kronecker(C, complement(Ones), NoAccumulate(), Times<double>(), transpose(A), B);
    BOOST_CHECK_EQUAL(C, Ones);

    C = Ones;
    kronecker(C, complement(MNotLower), NoAccumulate(), Times<double>(), transpose(Empty3x3), B);
    BOOST_CHECK_EQUAL(C, MNotLower);

    C = Ones;
    kronecker(C, complement(MNotLower), NoAccumulate(), Times<double>(), transpose(A), Empty3x4);
    BOOST_CHECK_EQUAL(C, MNotLower);

    // Replace
    C = Ones;
    kronecker(C, complement(Ones), NoAccumulate(), Times<double>(), transpose(A), B, REPLACE);
    BOOST_CHECK_EQUAL(C, Empty);

    C = Ones;
    kronecker(C, complement(MNotLower), NoAccumulate(), Times<double>(), transpose(Empty3x3), B, REPLACE);
    BOOST_CHECK_EQUAL(C, Empty);

    C = Ones;
    kronecker(C, complement(MNotLower), NoAccumulate(), Times<double>(), transpose(A), Empty3x4, REPLACE);
    BOOST_CHECK_EQUAL(C, Empty);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_CompMask_NoAccum_ATBM_dense)
{
    Matrix<double> A(Ones_3x3, 0.);
    Matrix<double> B(Ones_3x4, 0.);

    Matrix<double> MLower(Lower_9x12, 0.);
    Matrix<double> MNotLower(NotLower_9x12, 0.);

    Matrix<double> Ones(Ones_9x12, 0.);
    Matrix<double> C(9, 12);

    C = Ones;
    kronecker(C, complement(MNotLower), NoAccumulate(), Times<double>(), transpose(A), B);
    BOOST_CHECK_EQUAL(C, Ones);

    C = Ones;
    kronecker(C, complement(MNotLower), NoAccumulate(), Times<double>(), transpose(A), B, REPLACE);
    BOOST_CHECK_EQUAL(C, MLower);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_CompMask_NoAccum_ATB_empty_rows)
{
    Matrix<double> Ones(Ones_9x12, 0.);
    Matrix<double> A(Ar_sparse_3x3, 0.);
    Matrix<double> B(Br_sparse_3x4, 0.);
    Matrix<double> answer(Answer_rr_sparse_9x12, 0.);
    Matrix<double> C(9, 12);

    Matrix<double> MLower(Lower_9x12, 0.);
    Matrix<double> MNotLower(NotLower_9x12, 0.);

    C = Ones;
    eWiseMult(answer, NoMask(), NoAccumulate(), Times<double>(), MLower, answer);
    kronecker(C, complement(MNotLower), NoAccumulate(), Times<double>(), transpose(A), B, REPLACE);
    BOOST_CHECK_EQUAL(C, answer);

    C = Ones;
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(), MNotLower, answer);
    kronecker(C, complement(MNotLower), NoAccumulate(), Times<double>(), transpose(A), B);
    BOOST_CHECK_EQUAL(C, answer);

}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_CompMask_NoAccum_ATB_emptyRowA_emptyColB)
{
    Matrix<double> C(9,12);
    Matrix<double> Ones(Ones_9x12, 0.);
    Matrix<double> A(Ar_sparse_3x3, 0.);
    Matrix<double> B(Bc_sparse_3x4, 0.);
    Matrix<double> answer(Answer_rc_sparse_9x12, 0.);

    Matrix<double> MLower(Lower_9x12, 0.);
    Matrix<double> MNotLower(NotLower_9x12, 0.);

    // REPLACE
    C = Ones;
    eWiseMult(answer, NoMask(), NoAccumulate(), Times<double>(), MLower, answer);
    kronecker(C, complement(MNotLower), NoAccumulate(), Times<double>(), transpose(A), B, REPLACE);
    BOOST_CHECK_EQUAL(C, answer);

    // Merge
    C = Ones;
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(), MNotLower, answer);
    kronecker(C, complement(MNotLower), NoAccumulate(), Times<double>(), transpose(A), B);
    BOOST_CHECK_EQUAL(C, answer);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_CompMask_NoAccum_ATB_emptyColA_emptyRowB)
{
    Matrix<double> C(9,12);
    Matrix<double> Ones(Ones_9x12, 0.);
    Matrix<double> A(Ac_sparse_3x3, 0.);
    Matrix<double> B(Br_sparse_3x4, 0.);
    Matrix<double> answer(Answer_cr_sparse_9x12, 0.);

    Matrix<double> MLower(Lower_9x12, 0.);
    Matrix<double> MNotLower(NotLower_9x12, 0.);

    // REPLACE
    C = Ones;
    eWiseMult(answer, NoMask(), NoAccumulate(), Times<double>(), MLower, answer);
    kronecker(C, complement(MNotLower), NoAccumulate(), Times<double>(), transpose(A), B, REPLACE);
    BOOST_CHECK_EQUAL(C, answer);

    // Merge
    C = Ones;
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(), MNotLower, answer);
    kronecker(C, complement(MNotLower), NoAccumulate(), Times<double>(), transpose(A), B);
    BOOST_CHECK_EQUAL(C, answer);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_CompMask_NoAccum_ATB_emptyRowM)
{
    Matrix<double> C(9,12);
    Matrix<double> Ones(Ones_9x12, 0.);
    Matrix<double> A(AT_sparse_3x3, 0.);
    Matrix<double> B(B_sparse_3x4, 0.);
    Matrix<double> answer(Answer_sparse_9x12, 0.);

    Matrix<double> MLower(Lower_9x12, 0.);
    Matrix<double> MNotLower(NotLower_9x12, 0.);

    // Replace
    C = Ones;
    eWiseMult(answer, NoMask(), NoAccumulate(), Times<double>(), MLower, answer);
    kronecker(C, complement(MNotLower), NoAccumulate(), Times<double>(), transpose(A), B, REPLACE);
    BOOST_CHECK_EQUAL(C, answer);

    // Merge
    C = Ones;
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(), MNotLower, answer);
    kronecker(C, complement(MNotLower), NoAccumulate(), Times<double>(), transpose(A), B);
    BOOST_CHECK_EQUAL(C, answer);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_CompMask_NoAccum_ATB_ABdup)
{
    Matrix<double> C(9,9);
    Matrix<double> Ones(Ones_9x9, 0.);
    Matrix<double> A(A_sparse_3x3, 0.);
    Matrix<double> answer(ATA_sparse_9x9, 0.);

    Matrix<double> MLower(Lower_9x9, 0.);
    Matrix<double> MNotLower(NotLower_9x9, 0.);

    // Replace
    C = Ones;
    eWiseMult(answer, NoMask(), NoAccumulate(), Times<double>(), MLower, answer);
    kronecker(C, complement(MNotLower), NoAccumulate(), Times<double>(), transpose(A), A, REPLACE);

    BOOST_CHECK_EQUAL(C, answer);

    // Merge
    C = Ones;
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(), MNotLower, answer);
    kronecker(C, complement(MNotLower), NoAccumulate(), Times<double>(), transpose(A), A);
    BOOST_CHECK_EQUAL(C, answer);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_CompMask_NoAccum_ATB_ACdup)
{
    Matrix<double> mat(A_sparse_3x3, 0.);
    Matrix<double> C(3, 3);
    Matrix<double> B(1, 1);    B.setElement(0, 0, 1.0);
    Matrix<double> answer(AT_sparse_3x3, 0.);

    Matrix<double> MLower(Lower_3x3, 0.);
    Matrix<double> MNotLower(NotLower_3x3, 0.);

    // Build some matrices.

    // Replace
    C = mat;
    eWiseMult(answer, NoMask(), NoAccumulate(), Times<double>(), MLower, transpose(mat));
    kronecker(C, complement(MNotLower), NoAccumulate(), Times<double>(), transpose(C), B, REPLACE);

    BOOST_CHECK_EQUAL(C, answer);

    // Merge
    C = mat;
    kronecker(C, complement(MNotLower), NoAccumulate(), Times<double>(), transpose(C), B);

    BOOST_CHECK_EQUAL(C, answer);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_CompMask_NoAccum_ATB_BCdup)
{
    // Build some matrices.
    Matrix<double> mat(B_sparse_3x4, 0.);
    Matrix<double> C(3,4);
    Matrix<double> A(1, 1);    A.setElement(0, 0, 1.0);
    Matrix<double> answer(mat);

    Matrix<double> MLower(Lower_3x4, 0.);
    Matrix<double> MNotLower(NotLower_3x4, 0.);

    // Replace
    C = mat;
    eWiseMult(answer, NoMask(), NoAccumulate(), Times<double>(), MLower, answer);
    kronecker(C, complement(MNotLower), NoAccumulate(), Times<double>(), transpose(A), C, REPLACE);

    BOOST_CHECK_EQUAL(C, answer);

    // Merge
    C = mat;
    kronecker(C, complement(MNotLower), NoAccumulate(), Times<double>(), transpose(A), C);

    BOOST_CHECK_EQUAL(C, mat);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_CompMask_NoAccum_ATB_MCdup)
{
    // Build some matrices.
    Matrix<double> C(9,12);
    Matrix<double> Ones(Ones_9x12, 0.);
    Matrix<double> A(AT_sparse_3x3, 0.);
    Matrix<double> B(B_sparse_3x4, 0.);
    Matrix<double> answer(Answer_sparse_9x12, 0.);

    Matrix<double> MLower(Lower_9x12, 0.);
    Matrix<double> MNotLower(NotLower_9x12, 0.);

    // Replace
    C = MLower;
    eWiseMult(answer, NoMask(), NoAccumulate(), Times<double>(), MNotLower, answer);
    kronecker(C, complement(C), NoAccumulate(), Times<double>(), transpose(A), B, REPLACE);

    BOOST_CHECK_EQUAL(C, answer);

    // Merge
    C = MLower;
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(), MLower, answer);
    kronecker(C, complement(C), NoAccumulate(), Times<double>(), transpose(A), B);

    BOOST_CHECK_EQUAL(C, answer);
}

//****************************************************************************
// CompMask_Accum
//****************************************************************************

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_CompMask_Accum_ATB)
{
    Matrix<double> C(9, 12);
    Matrix<double> A(AT_sparse_3x3, 0.); // 1 0 0 / 0 2 0 / 3 0 4
    Matrix<double> B(B_sparse_3x4, 0.); // 1 1 0 0 / 0 2 2 0 / 3 0 0 3

    Matrix<double> Identity(Identity_3x3, 0.0);

    Matrix<double> answer(9, 12);
    Matrix<double> Empty(9, 12);
    Matrix<double> Ones(Ones_9x12, 0.);

    Matrix<double> MLower(Lower_9x12, 0.);
    Matrix<double> MNotLower(NotLower_9x12, 0.);

    Matrix<double> Answer_9x12(Answer_sparse_9x12, 0.);
    Matrix<double> Answer_9x12_Lower(Answer_sparse_9x12_Lower, 0.);
    Matrix<double> Answer_9x12_NotLower(Answer_sparse_9x12_NotLower, 0.);
    Matrix<double> Answer_9x12_Lower_Ones(Answer_sparse_9x12_Lower_Ones, 0.);
    Matrix<double> Answer_9x12_NotLower_Ones(Answer_sparse_9x12_NotLower_Ones, 0.);

    // Merge
    // Mempty vs Mfull vs Mlower

    C = Ones;
    kronecker(C, complement(Ones), Plus<double>(), Times<double>(), transpose(A), B);
    BOOST_CHECK_EQUAL(C, Ones);

    C = Ones;
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(),
             Ones, Answer_9x12);
    kronecker(C, complement(Empty),  Plus<double>(), Times<double>(), transpose(A), B);
    BOOST_CHECK_EQUAL(C, answer); //Answer_9x12);

    C = Ones;
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(),
             Ones, Answer_9x12_Lower);
    kronecker(C, complement(MNotLower), Plus<double>(), Times<double>(), transpose(A), B);
    BOOST_CHECK_EQUAL(C, answer);

    C = Ones;
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(),
             Ones, Answer_9x12_NotLower);
    kronecker(C, complement(MLower), Plus<double>(), Times<double>(), transpose(A), B);
    BOOST_CHECK_EQUAL(C, answer);

    // Replace
    // Mempty vs Mfull vs Mlower

    C = Ones;
    kronecker(C, complement(Ones), Plus<double>(), Times<double>(), transpose(A), B, REPLACE);
    BOOST_CHECK_EQUAL(C, Empty);

    C = Ones;
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(),
             Ones, Answer_9x12);
    kronecker(C, complement(Empty),  Plus<double>(), Times<double>(), transpose(A), B, REPLACE);
    BOOST_CHECK_EQUAL(C, answer);

    C = Ones;
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(),
             MLower, Answer_9x12_Lower);
    kronecker(C, complement(MNotLower), Plus<double>(), Times<double>(), transpose(A), B, REPLACE);
    BOOST_CHECK_EQUAL(C, answer);

    C = Ones;
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(),
             MNotLower, Answer_9x12_NotLower);
    kronecker(C, complement(MLower), Plus<double>(), Times<double>(), transpose(A), B, REPLACE);
    BOOST_CHECK_EQUAL(C, answer);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_CompMask_Accum_ATBM_empty)
{
    Matrix<double> C(9, 12);
    Matrix<double> A(AT_sparse_3x3, 0.); // 1 0 0 / 0 2 0 / 3 0 4
    Matrix<double> B(B_sparse_3x4, 0.); // 1 1 0 0 / 0 2 2 0 / 3 0 0 3

    Matrix<double> Ones(Ones_9x12, 0.);
    Matrix<double> Empty(9, 12);
    Matrix<double> Empty3x3(3, 3);
    Matrix<double> Empty3x4(3, 4);

    Matrix<double> MLower(Lower_9x12, 0.);
    Matrix<double> MNotLower(NotLower_9x12, 0.);

    // Merge
    C = Ones;
    kronecker(C, complement(Ones), Plus<double>(), Times<double>(), transpose(A), B);
    BOOST_CHECK_EQUAL(C, Ones);

    C = Ones;
    kronecker(C, complement(MNotLower), Plus<double>(), Times<double>(), transpose(Empty3x3), B);
    BOOST_CHECK_EQUAL(C, Ones);

    C = Ones;
    kronecker(C, complement(MNotLower), Plus<double>(), Times<double>(), transpose(A), Empty3x4);
    BOOST_CHECK_EQUAL(C, Ones);

    // Replace
    C = Ones;
    kronecker(C, complement(Ones), Plus<double>(), Times<double>(), transpose(A), B, REPLACE);
    BOOST_CHECK_EQUAL(C, Empty);

    C = Ones;
    kronecker(C, complement(MNotLower), Plus<double>(), Times<double>(), transpose(Empty3x3), B, REPLACE);
    BOOST_CHECK_EQUAL(C, MLower);

    C = Ones;
    kronecker(C, complement(MNotLower), Plus<double>(), Times<double>(), transpose(A), Empty3x4, REPLACE);
    BOOST_CHECK_EQUAL(C, MLower);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_CompMask_Accum_ATBM_dense)
{
    Matrix<double> A(Ones_3x3, 0.);
    Matrix<double> B(Ones_3x4, 0.);

    Matrix<double> MLower(Lower_9x12, 0.);
    Matrix<double> MNotLower(NotLower_9x12, 0.);

    Matrix<double> Ones(Ones_9x12, 0.);
    Matrix<double> C(9, 12);
    Matrix<double> answer(9, 12);

    C = Ones;
    kronecker(C, complement(MNotLower), Plus<double>(), Times<double>(), transpose(A), B);
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(),
             Ones, MLower);
    BOOST_CHECK_EQUAL(C, answer);

    C = Ones;
    kronecker(C, complement(MNotLower), Plus<double>(), Times<double>(), transpose(A), B, REPLACE);
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(),
             MLower, MLower);
    BOOST_CHECK_EQUAL(C, answer);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_CompMask_Accum_ATB_empty_rows)
{
    Matrix<double> Ones(Ones_9x12, 0.);
    Matrix<double> A(Ar_sparse_3x3, 0.);
    Matrix<double> B(Br_sparse_3x4, 0.);
    Matrix<double> answer(Answer_rr_sparse_9x12, 0.);
    Matrix<double> C(9, 12);

    Matrix<double> MLower(Lower_9x12, 0.);
    Matrix<double> MNotLower(NotLower_9x12, 0.);

    C = Ones;
    eWiseMult(answer, NoMask(), NoAccumulate(), Times<double>(), MLower, answer);
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(), MLower, answer);
    kronecker(C, complement(MNotLower), Plus<double>(), Times<double>(), transpose(A), B, REPLACE);
    BOOST_CHECK_EQUAL(C, answer);

    C = Ones;
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(), MNotLower, answer);
    kronecker(C, complement(MNotLower), Plus<double>(), Times<double>(), transpose(A), B);
    BOOST_CHECK_EQUAL(C, answer);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_CompMask_Accum_ATB_emptyRowA_emptyColB)
{
    Matrix<double> C(9,12);
    Matrix<double> Ones(Ones_9x12, 0.);
    Matrix<double> A(Ar_sparse_3x3, 0.);
    Matrix<double> B(Bc_sparse_3x4, 0.);
    Matrix<double> answer(Answer_rc_sparse_9x12, 0.);

    Matrix<double> MLower(Lower_9x12, 0.);
    Matrix<double> MNotLower(NotLower_9x12, 0.);

    // REPLACE
    C = Ones;
    eWiseMult(answer, NoMask(), NoAccumulate(), Times<double>(), MLower, answer);
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(), MLower, answer);
    kronecker(C, complement(MNotLower), Plus<double>(), Times<double>(), transpose(A), B, REPLACE);
    BOOST_CHECK_EQUAL(C, answer);

    // Merge
    C = Ones;
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(), MNotLower, answer);
    kronecker(C, complement(MNotLower), Plus<double>(), Times<double>(), transpose(A), B);
    BOOST_CHECK_EQUAL(C, answer);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_CompMask_Accum_ATB_emptyColA_emptyRowB)
{
    Matrix<double> C(9,12);
    Matrix<double> Ones(Ones_9x12, 0.);
    Matrix<double> A(Ac_sparse_3x3, 0.);
    Matrix<double> B(Br_sparse_3x4, 0.);
    Matrix<double> answer(Answer_cr_sparse_9x12, 0.);

    Matrix<double> MLower(Lower_9x12, 0.);
    Matrix<double> MNotLower(NotLower_9x12, 0.);

    // REPLACE
    C = Ones;
    eWiseMult(answer, NoMask(), NoAccumulate(), Times<double>(), MLower, answer);
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(), MLower, answer);
    kronecker(C, complement(MNotLower), Plus<double>(), Times<double>(), transpose(A), B, REPLACE);
    BOOST_CHECK_EQUAL(C, answer);

    // Merge
    C = Ones;
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(), MNotLower, answer);
    kronecker(C, complement(MNotLower), Plus<double>(), Times<double>(), transpose(A), B);
    BOOST_CHECK_EQUAL(C, answer);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_CompMask_Accum_ATBM_emptyRowM)
{
    Matrix<double> C(9,12);
    Matrix<double> Ones(Ones_9x12, 0.);
    Matrix<double> A(AT_sparse_3x3, 0.);
    Matrix<double> B(B_sparse_3x4, 0.);
    Matrix<double> answer(Answer_sparse_9x12, 0.);

    Matrix<double> MLower(Lower_9x12, 0.);
    Matrix<double> MNotLower(NotLower_9x12, 0.);

    // Replace
    C = Ones;
    eWiseMult(answer, NoMask(), NoAccumulate(), Times<double>(), MLower, answer);
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(), MLower, answer);
    kronecker(C, complement(MNotLower), Plus<double>(), Times<double>(), transpose(A), B, REPLACE);
    BOOST_CHECK_EQUAL(C, answer);

    // Merge
    C = Ones;
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(), MNotLower, answer);
    kronecker(C, complement(MNotLower), Plus<double>(), Times<double>(), transpose(A), B);
    BOOST_CHECK_EQUAL(C, answer);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_CompMask_Accum_ATB_ABdup)
{
    Matrix<double> C(9,9);
    Matrix<double> Ones(Ones_9x9, 0.);
    Matrix<double> A(A_sparse_3x3, 0.);
    Matrix<double> answer(ATA_sparse_9x9, 0.);

    Matrix<double> MLower(Lower_9x9, 0.);
    Matrix<double> MNotLower(NotLower_9x9, 0.);

    // Replace
    C = Ones;
    eWiseMult(answer, NoMask(), NoAccumulate(), Times<double>(), MLower, answer);
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(), MLower, answer);
    kronecker(C, complement(MNotLower), Plus<double>(), Times<double>(), transpose(A), A, REPLACE);

    BOOST_CHECK_EQUAL(C, answer);

    // Merge
    C = Ones;
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(), MNotLower, answer);
    kronecker(C, complement(MNotLower), Plus<double>(), Times<double>(), transpose(A), A);
    BOOST_CHECK_EQUAL(C, answer);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_CompMask_Accum_ATB_ACdup)
{
    // Build some matrices.
    Matrix<double> mat(A_sparse_3x3, 0.);
    Matrix<double> C(3, 3);
    Matrix<double> B(1, 1);    B.setElement(0, 0, 1.0);
    Matrix<double> answer(mat);

    Matrix<double> MLower(Lower_3x3, 0.);
    Matrix<double> MNotLower(NotLower_3x3, 0.);

    // Replace
    C = mat;
    eWiseMult(answer, NoMask(), NoAccumulate(), Times<double>(), MLower, transpose(mat));
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(), mat, answer);
    kronecker(C, complement(MNotLower), Plus<double>(), Times<double>(), transpose(C), B, REPLACE);

    BOOST_CHECK_EQUAL(C, answer);

    // Merge
    C = mat;
    kronecker(C, complement(MNotLower), Plus<double>(), Times<double>(), transpose(C), B);
    BOOST_CHECK_EQUAL(C, answer);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_CompMask_Accum_ATB_BCdup)
{
    // Build some matrices.
    Matrix<double> mat(B_sparse_3x4, 0.);
    Matrix<double> C(3,4);
    Matrix<double> A(1, 1);    A.setElement(0, 0, 1.0);
    Matrix<double> answer(mat);

    Matrix<double> MLower(Lower_3x4, 0.);
    Matrix<double> MNotLower(NotLower_3x4, 0.);

    // Replace
    C = mat;
    eWiseMult(answer, NoMask(), NoAccumulate(), Times<double>(), MLower, mat);
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(), answer, answer);
    kronecker(C, complement(MNotLower), Plus<double>(), Times<double>(), transpose(A), C, REPLACE);

    BOOST_CHECK_EQUAL(C, answer);

    // Merge
    C = mat;
    eWiseMult(answer, NoMask(), NoAccumulate(), Times<double>(), MLower, mat);
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(), answer, mat);
    kronecker(C, complement(MNotLower), Plus<double>(), Times<double>(), transpose(A), C);

    BOOST_CHECK_EQUAL(C, answer);
}

//****************************************************************************
BOOST_AUTO_TEST_CASE(test_kronecker_CompMask_Accum_ATB_MCdup)
{
    // Build some matrices.
    Matrix<double> C(9,12);
    Matrix<double> Ones(Ones_9x12, 0.);
    Matrix<double> A(AT_sparse_3x3, 0.);
    Matrix<double> B(B_sparse_3x4, 0.);
    Matrix<double> answer(Answer_sparse_9x12, 0.);

    Matrix<double> MLower(Lower_9x12, 0.);
    Matrix<double> MNotLower(NotLower_9x12, 0.);

    // Replace
    C = MLower;
    eWiseMult(answer, NoMask(), NoAccumulate(), Times<double>(), MNotLower, answer);
    kronecker(C, complement(C), Plus<double>(), Times<double>(), transpose(A), B, REPLACE);

    BOOST_CHECK_EQUAL(C, answer);

    // Merge
    C = MLower;
    eWiseAdd(answer, NoMask(), NoAccumulate(), Plus<double>(), MLower, answer);
    kronecker(C, complement(C), Plus<double>(), Times<double>(), transpose(A), B);

    BOOST_CHECK_EQUAL(C, answer);
}

BOOST_AUTO_TEST_SUITE_END()


// The MIT License (MIT)
//
// Copyright (c) 2014-2016 Darrell Wright
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files( the "Software" ), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

//autolevelfilter.cpp

#include <boost/scoped_array.hpp>
#include <iostream>
#include <limits>
#include <omp.h>
#include <stdexcept>
#include <string>
#include <vector>
#include <Wt/WApplication>

#include "autolevelfilter.h"
#include "itypes.h"
#include <daw/daw_exception.h>

namespace daw { namespace imaging {

	GenericImage<rgb3> AutoLevelFilter::runfilter( GenericImage<rgb3> const & input_image ) const {

		rgb3 min_vals( std::numeric_limits<unsigned char>::max(), std::numeric_limits<unsigned char>::max(), std::numeric_limits<unsigned char>::max() );
		rgb3 max_vals( std::numeric_limits<unsigned char>::min(), std::numeric_limits<unsigned char>::min(), std::numeric_limits<unsigned char>::min() );

		for( size_t n=0; n<input_image.size( ); ++n ) {
			const rgb3 cur_val = input_image[n];
			if( cur_val.red < min_vals.red ) {
				min_vals.red = cur_val.red;
			}
			if( cur_val.green < min_vals.green ) {
				min_vals.green = cur_val.green;
			}
			if( cur_val.blue < min_vals.blue ) {
				min_vals.blue = cur_val.blue;
			}
			if( cur_val.red > max_vals.red ) {
				max_vals.red = cur_val.red;
			}
			if( cur_val.green > max_vals.green ) {
				max_vals.green = cur_val.green;
			}
			if( cur_val.blue > max_vals.blue ) {
				max_vals.blue = cur_val.blue;
			}
		}

		rgb3 range_vals( max_vals.red-min_vals.red, max_vals.green-min_vals.green, max_vals.blue-min_vals.blue );
		boost::scoped_array<float> val_mul( new float[3] );
		daw::exception::daw_throw_on_null( val_mul.get( ), "AutoLevelFilter::::runfilter could not allocate float[3] array. Null returned" );

		if( range_vals.red > 0 ) {
			val_mul[0] = 255.0f/(float)range_vals.red;
		} else {
			val_mul[0] = 0.0f;
		}
		if( range_vals.green > 0 ) {
			val_mul[1] = 255.0f/(float)range_vals.green;
		} else {
			val_mul[1] = 0.0f;
		}
		if( range_vals.blue > 0 ) {
			val_mul[2] = 255.0f/(float)range_vals.blue;
		} else {
			val_mul[2] = 0.0f;
		}

		GenericImage<rgb3> output_image( input_image.width( ), input_image.height( ) );
		if( input_image.size( ) != output_image.size( ) ) {
			static std::string const msg = "Input and Output image in autolevel filter are not the same.  This should never happen.";
			Wt::log( "error" ) << msg;
			throw std::runtime_error( msg );
		}

		//TODO: Reenable OMP #pragma omp parallel for
		for( size_t n=0; n<input_image.size( ); ++n ) {
			output_image[n] = rgb3( 0, 0, 0 );
			if( range_vals.red > 0 ) {
				output_image[n].red = (unsigned char)(((float)(input_image[n].red - min_vals.red))*val_mul[0]);
			}
			if( range_vals.green > 0 ) {
				output_image[n].green = (unsigned char)(((float)(input_image[n].green - min_vals.green))*val_mul[0]);
			}
			if( range_vals.blue > 0 ) {
				output_image[n].blue = (unsigned char)(((float)(input_image[n].blue - min_vals.blue))*val_mul[0]);
			}
		}
		return output_image;
	}

}}


/**
 * @file split_data_test.cpp
 * @author Tham Ngap Wei
 *
 * Test the SplitData method.
 *
 * mlpack is free software; you may redistribute it and/or modify it under the
 * terms of the 3-clause BSD license.  You should have received a copy of the
 * 3-clause BSD license along with mlpack.  If not, see
 * http://www.opensource.org/licenses/BSD-3-Clause for more information.
 */
#include <mlpack/core.hpp>
#include <mlpack/core/data/split_data.hpp>

#include <boost/test/unit_test.hpp>
#include "test_tools.hpp"

using namespace mlpack;
using namespace arma;
using namespace mlpack::data;

BOOST_AUTO_TEST_SUITE(SplitDataTest);

/**
 * Compare the data after train test split.  This assumes that the labels
 * correspond to each column, so that we can easily check each point against its
 * original.
 *
 * @param inputData The original data set before split.
 * @param compareData The data want to compare with the inputData;
 *   it could be train data or test data.
 * @param inputLabel The labels of each point in compareData.
 */
void CompareData(const mat& inputData,
                 const mat& compareData,
                 const Row<size_t>& inputLabel)
{
  for (size_t i = 0; i != compareData.n_cols; ++i)
  {
    const mat& lhsCol = inputData.col(inputLabel(i));
    const mat& rhsCol = compareData.col(i);
    for (size_t j = 0; j != lhsCol.n_rows; ++j)
    {
      if (std::abs(rhsCol(j)) < 1e-5)
        BOOST_REQUIRE_SMALL(lhsCol(j), 1e-5);
      else
        BOOST_REQUIRE_CLOSE(lhsCol(j), rhsCol(j), 1e-5);
    }
  }
}

void CheckMatEqual(const mat& inputData,
                   const mat& compareData)
{
  const mat& sortedInput = arma::sort(inputData, "ascend", 1);
  const mat& sortedCompare = arma::sort(compareData, "ascend", 1);
  for (size_t i = 0; i < sortedInput.n_cols; ++i)
  {
    const mat& lhsCol = sortedInput.col(i);
    const mat& rhsCol = sortedCompare.col(i);
    for (size_t j = 0; j < lhsCol.n_rows; ++j)
    {
      if (std::abs(rhsCol(j)) < 1e-5)
        BOOST_REQUIRE_SMALL(lhsCol(j), 1e-5);
      else
        BOOST_REQUIRE_CLOSE(lhsCol(j), rhsCol(j), 1e-5);
    }
  }
}

/**
 * Check that no labels have been duplicated.
 */
void CheckDuplication(const Row<size_t>& trainLabels,
                      const Row<size_t>& testLabels)
{
  // Assemble a vector that will hold the counts of each element.
  Row<size_t> counts(trainLabels.n_elem + testLabels.n_elem);
  counts.zeros();

  for (size_t i = 0; i < trainLabels.n_elem; ++i)
  {
    BOOST_REQUIRE_LT(trainLabels[i], counts.n_elem);
    counts[trainLabels[i]]++;
  }
  for (size_t i = 0; i < testLabels.n_elem; ++i)
  {
    BOOST_REQUIRE_LT(testLabels[i], counts.n_elem);
    counts[testLabels[i]]++;
  }

  // Now make sure each point has been used once.
  for (size_t i = 0; i < counts.n_elem; ++i)
    BOOST_REQUIRE_EQUAL(counts[i], 1);
}

BOOST_AUTO_TEST_CASE(SplitDataResultMat)
{
  mat input(2, 10);
  size_t count = 0; // count for putting unique sequential values
  input.imbue([&count] () { return ++count; });

  const auto value = Split(input, 0.2);
  BOOST_REQUIRE_EQUAL(std::get<0>(value).n_cols, 8); // train data
  BOOST_REQUIRE_EQUAL(std::get<1>(value).n_cols, 2); // test data

  mat concat = arma::join_rows(std::get<0>(value), std::get<1>(value));
  CheckMatEqual(input, concat);
}

BOOST_AUTO_TEST_CASE(SplitLabeledDataResultMat)
{
  mat input(2, 10);
  input.randu();

  // Set the labels to the column ID, so that CompareData can compare the data
  // after Split is called.
  const Row<size_t> labels = arma::linspace<Row<size_t>>(0, input.n_cols - 1,
      input.n_cols);

  const auto value = Split(input, labels, 0.2);
  BOOST_REQUIRE_EQUAL(std::get<0>(value).n_cols, 8);
  BOOST_REQUIRE_EQUAL(std::get<1>(value).n_cols, 2);
  BOOST_REQUIRE_EQUAL(std::get<2>(value).n_cols, 8);
  BOOST_REQUIRE_EQUAL(std::get<3>(value).n_cols, 2);

  CompareData(input, std::get<0>(value), std::get<2>(value));
  CompareData(input, std::get<1>(value), std::get<3>(value));

  // The last thing to check is that we aren't duplicating any points in the
  // train or test labels.
  CheckDuplication(std::get<2>(value), std::get<3>(value));
}

/**
 * The same test as above, but on a larger dataset.
 */
BOOST_AUTO_TEST_CASE(SplitDataLargerTest)
{
  size_t count = 0;
  mat input(10, 497);
  input.imbue([&count] () { return ++count; });

  const auto value = Split(input, 0.3);
  BOOST_REQUIRE_EQUAL(std::get<0>(value).n_cols, 497 - size_t(0.3 * 497));
  BOOST_REQUIRE_EQUAL(std::get<1>(value).n_cols, size_t(0.3 * 497));

  mat concat = arma::join_rows(std::get<0>(value), std::get<1>(value));
  CheckMatEqual(input, concat);
}

BOOST_AUTO_TEST_CASE(SplitLabeledDataLargerTest)
{
  mat input(10, 497);
  input.randu();

  // Set the labels to the column ID.
  const Row<size_t> labels = arma::linspace<Row<size_t>>(0, input.n_cols - 1,
      input.n_cols);

  const auto value = Split(input, labels, 0.3);
  BOOST_REQUIRE_EQUAL(std::get<0>(value).n_cols, 497 - size_t(0.3 * 497));
  BOOST_REQUIRE_EQUAL(std::get<1>(value).n_cols, size_t(0.3 * 497));
  BOOST_REQUIRE_EQUAL(std::get<2>(value).n_cols, 497 - size_t(0.3 * 497));
  BOOST_REQUIRE_EQUAL(std::get<3>(value).n_cols, size_t(0.3 * 497));

  CompareData(input, std::get<0>(value), std::get<2>(value));
  CompareData(input, std::get<1>(value), std::get<3>(value));

  CheckDuplication(std::get<2>(value), std::get<3>(value));
}

BOOST_AUTO_TEST_SUITE_END();


#include <Rubr.h>

#include "CSVread.h"
#include <Eigen/Dense>
#include <Eigen/Core>
#include "matplotlibcpp.h"
#include "Rubr/Gnuplot.h"

using Eigen::MatrixXd;
using Eigen::Map;
namespace plt = matplotlibcpp;

class ExampleLayer : public Rubr::Layer {
public:
	ExampleLayer() : Layer("Example") {}

	void OnUpdate() override {
		//RUBR_INFO("ExampleLayer::Update");
	}

	void OnEvent(Rubr::Event& event) override {
		RUBR_TRACE("{0}", event);
	}
};

class Stats : public Rubr::App {
public:
	Stats() {
		PushLayer(new ExampleLayer());

		std::ifstream file("C:/dev/Rubr/Stats/src/test.csv");

		if (file.is_open()) {
			//Count the number of lines
			int count = 0;
			std::string line;
			while (getline(file, line))
				count++;

			file.clear();
			file.seekg(0, std::ios::beg);

			//Create an iterator for the file
			CSVIterator loop(file);

			//Read the header
			CSVRow headerRow = *loop;
			std::vector<std::string> header;
			for (int hIdx = 0; hIdx < headerRow.size(); ++hIdx)
				header.push_back(headerRow[hIdx]);
			++loop;

			//Determine number of columns of data based on header and create matrix
			int numRows = count - 1;
			int numCols = headerRow.size();
			int rIdx = 0;

			MatrixXd data(numRows, numCols);
			std::cout << data << std::endl;

			//Read each data line and store it into the data matrix
			while (loop != CSVIterator()) {
				CSVRow row = *loop;
				auto sz = row.size();

				for (int cIdx = 0; cIdx < sz; ++cIdx) {
					data(rIdx, cIdx) = std::stod(row[cIdx], nullptr);
					RUBR_TRACE("{0}", data(rIdx, cIdx));
				}

				rIdx++;
				++loop;
			}
			std::cout << data << std::endl;
			std::cout << data.row(0) << std::endl;

			file.close();

			data.transposeInPlace();

			Rubr::Gnuplot::plot(data.col(0).data(), data.rows());
		}

	}
	~Stats() {}
};

Rubr::App* Rubr::CreateApp() {
	return new Stats();
}


/**
 * @file 	main.cpp
 * @author 	Fabian Wegscheider
 * @date 	Jul 19, 2017
 */

#include <iostream>
#include <boost/program_options.hpp>
#include <boost/timer/timer.hpp>
#include <omp.h>
#include "GraphParser.h"
#include "PrimeNumbers.h"
#include "SteinerTreeHeuristic.h"

using std::string;
using std::cout;
using std::cerr;
using std::endl;
using namespace boost;
namespace po = boost::program_options;

using weight_type = double;

using CSR_Graph = compressed_sparse_row_graph<directedS, no_property,
		property<edge_weight_t, weight_type>>;

/**
 * The main function which reads in a graph from a .gph file, considers all
 * vertices with prime indices as terminals and calculates a steiner tree
 * using an improved shortest-path-heuristic based on Dijkstra
 * @param numargs number of inputs on command line
 * @param args array of inputs on command line
 * @return whether the program operated successfully
 */
int main(int numargs, char* args[]) {

	timer::cpu_timer overall_timer;

	// default values for unspecified options
	int N_THREADS = 1;
	int N_TERMINALS = 100;

	/* parsing command line options */
	string input;
	bool print_tree_selected;

	try {
		po::options_description desc("Allowed options");
		desc.add_options()
				("help,h", "produce help message")
				("showtree,s", "print tree")
				("threads,t", po::value<int>(&N_THREADS)->default_value(1),
						"set number of threads")
				("terminals,n", po::value<int>(&N_TERMINALS)->default_value(100),
						"set number of starting terminals")
				("input-file", po::value<string>(), "input file");

		po::positional_options_description p;
		p.add("input-file", 1);
		p.add("terminals", -1);
		po::variables_map vm;
		po::store(po::command_line_parser(numargs, args).
				options(desc).positional(p).run(), vm);
		po::notify(vm);

		if (vm.count("help")) {
			cout << desc << "\n";
			exit(EXIT_SUCCESS);
		}

		if (vm.count("input-file")) {
			input = vm["input-file"].as< string >();
		} else {
			cerr << "Usage: ./ex10 <file.gph> (<number of starting terminals>) "
					<< "(other options)" << endl;
			cerr << "run with -h to see information about options" << endl;
			exit(EXIT_FAILURE);
		}

		print_tree_selected = vm.count("showtree");

	} catch (...) {
		cerr << "Usage: ./ex10 <file.gph> (<number of starting terminals>) "
				<< "(other options)" << endl;
		cerr << "run with -h to see information about options" << endl;
		exit(EXIT_FAILURE);
	}
	/* end of parsing command line options */

	/* parsing .gph file */
	int num_vertices;
	int num_edges;

	GraphParser parser(input);
	if (!parser.opened_successfully) {
		cerr << "file could not be read" << endl;
		exit(EXIT_FAILURE);
	}

	// first line is read to get number of vertices and edges
	if (!parser.read_first_line(num_vertices, num_edges)) {
		cerr << "error while reading file, not the right format" << endl;
		exit(EXIT_FAILURE);
	}

	// we use arrays for edges and weights so that we can use a boost graph
	Edge* edges = new Edge[num_edges];
	weight_type* weights = new double[num_edges];

	// rest of the file is read and parsed to a graph
	if (!parser.read_edge_data(edges, weights)) {
		cerr << "error while reading file, not the right format" << endl;
		exit(EXIT_FAILURE);
	}
	/* end of file parsing */

	/* initialization and computing */

	// here the graph is constructed using a CSR graph from boost
	const CSR_Graph g(boost::edges_are_unsorted, &edges[0], &edges[0] + num_edges,
			weights, num_vertices);

	// all primes in {2,...,num_vertices} are considered to be terminals
	vector<int> terminals = PrimeNumbers::find_primes(num_vertices);

	timer::cpu_timer algo_timer;

	const int iterations = std::min((int) terminals.size(), N_TERMINALS);

	// The tree is implicitly stored by remembering the predecessor of each
	// vertex, a -1 means that the vertex is not in the tree
	vector<int> best_tree(num_vertices);

	weight_type min_value = std::numeric_limits<weight_type>::infinity();
	int min_root = -1;

	// heuristic is called for the specified number of terminals and the best
	// solution is kept. It is done in parallel if more than one thread is chosen
	#pragma omp parallel for schedule(dynamic) num_threads(N_THREADS)
	for (int i = 0; i < iterations; ++i) {

		vector<int> tree(num_vertices, -1);
		weight_type objective_value = SteinerTreeHeuristic::compute_steiner_tree(g,
			num_vertices, terminals[i], tree, terminals);

		#pragma omp critical
		if (objective_value < min_value) {

			min_value = objective_value;
			min_root = i;
			best_tree = tree;
		}
	}

	string algo_time = algo_timer.format(3, "%w");
	/* end of computation */

	assert(SteinerTreeHeuristic::test_tree(g, num_vertices, terminals[min_root],
			best_tree, terminals));

	// output is printed, with tree if option was chosen
	cout << "TLEN: " << min_value << endl;
	if (print_tree_selected) {
		string edge_string = SteinerTreeHeuristic::print_tree(g,
				num_vertices, terminals[min_root], best_tree, terminals);
		cout << "TREE: " << edge_string << endl;
	}
	cout << "TIME: " << overall_timer.format(3, "%t") << endl;
	cout << "WALL: " << algo_time << endl;

	delete[] edges;
	delete[] weights;

	exit(EXIT_SUCCESS);
}


#pragma once

#include <Eigen/Sparse>

/**
 *
 *
 * @param ft  Sparse n x m matrix in centered zero frequency convention
 * @param nx  number of columns!
 * @param ny  number of rows!
 *
 * @return sparse matrix of size (2 ny) x (2 nx), with frequencies
 * (-nx,..,0,...nx-1) and
 * (-ny,... ,0, ...ny-1) respectively in centered zero frequency convention
 *
 * @return
 */
template <NUMERIC_T, int _OPTIONS, int _INDEX>
Eigen::Block<Eigen::SparseMatrix<NUMERIC_T, _OPTIONS, _INDEX>, Eigen::Dynamic, Eigen::Dynamic>
ftcut(const Eigen::SparseMatrix<NUMERIC_T, _OPTIONS, _INDEX> &ft, int nx, int ny)
{
  assert(ft.rows() % 2 == 0);
  assert(ft.cols() % 2 == 0);
  unsigned int nx_old = ft.rows() / 2;
  unsigned int ny_old = ft.cols() / 2;

  assert(ft.rows() >= 2 * ny);
  assert(ft.cols() >= 2 * nx);
  return ft.block(-ny + ny_old, -nx + nx_old, 2 * ny, 2 * nx);
}


/* ----------------------------------------------------------------------------
 * Copyright 2017, Massachusetts Institute of Technology,
 * Cambridge, MA 02139
 * All Rights Reserved
 * Authors: Luca Carlone, et al. (see THANKS for the full author list)
 * See LICENSE for the license information
 * -------------------------------------------------------------------------- */

/**
 * @file   testGeneralParallelPlaneRegularBasicFactor.cpp
 * @brief  test GeneralParallelPlaneRegularBasicFactor
 * @author Antoni Rosinol
 */

#include <algorithm>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <random>

#include <gtsam/base/numericalDerivative.h>
#include <boost/assign/std/vector.hpp>
#include <boost/bind.hpp>

#include <gtsam/geometry/OrientedPlane3.h>
#include <gtsam/geometry/Point3.h>
#include <gtsam/nonlinear/GaussNewtonOptimizer.h>
#include <gtsam/nonlinear/LevenbergMarquardtOptimizer.h>
#include <gtsam/slam/PriorFactor.h>
#include "factors/ParallelPlaneRegularFactor.h"
#include "factors/PointPlaneFactor.h"

#include <gflags/gflags.h>
#include <glog/logging.h>
#include <gtest/gtest.h>

using namespace std;
using namespace gtsam;

static const double tol = 1e-5;
static const double der_tol = 1e-5;

/**
  * Test that error does give the right result when it is zero.
/* ************************************************************************* */
TEST(testGeneralParallelPlaneRegularBasicFactor, ErrorIsZero) {
  /// Plane keys.
  Key plane_key_1(1);
  Key plane_key_2(2);

  /// Noise model for cosntraint between the two planes.
  noiseModel::Diagonal::shared_ptr parallel_plane_noise =
      noiseModel::Diagonal::Sigmas(Vector3(0.1, 0.1, 0.1));

  /// Measured distance between plane.
  double measured_distance_from_plane2_to_plane1 = 0.8;

  /// GeneralParallelism constraint between Plane 1 and Plane 2.
  GeneralParallelPlaneRegularBasicFactor factor(
      plane_key_1, plane_key_2, parallel_plane_noise,
      measured_distance_from_plane2_to_plane1);

  /// Planes.
  OrientedPlane3 plane_1(0.1, 0.1, 0.9, 0.9);
  OrientedPlane3 plane_2(0.1, 0.1, 0.9, 0.1);

  /// Calculate error.
  Vector error = factor.evaluateError(plane_1, plane_2);

  /// Expected error.
  Vector4 expected_error = Vector4::Constant(0.0);

  EXPECT_TRUE(gtsam::assert_equal(expected_error, error, tol));
}

/**
  * Test that error does give the right result when it is not zero.
/* ************************************************************************* */
TEST(testGeneralParallelPlaneRegularBasicFactor, ErrorOtherThanZero) {
  /// Plane keys.
  Key plane_key_1(1);
  Key plane_key_2(2);

  /// Noise model for cosntraint between the two planes.
  noiseModel::Diagonal::shared_ptr parallel_plane_noise =
      noiseModel::Diagonal::Sigmas(Vector3(0.1, 0.1, 0.1));

  /// Measured distance between plane.
  double measured_distance_from_plane2_to_plane1 = 0.7;

  /// GeneralParallelism constraint between Plane 1 and Plane 2.
  GeneralParallelPlaneRegularBasicFactor factor(
      plane_key_1, plane_key_2, parallel_plane_noise,
      measured_distance_from_plane2_to_plane1);

  /// Planes.
  OrientedPlane3 plane_1(0.3, 0.2, 1.9, 0.9);
  OrientedPlane3 plane_2(0.1, 0.1, 0.9, 0.1);

  /// Calculate error.
  Vector error = factor.evaluateError(plane_1, plane_2);

  /// Expected error.
  Vector4 expected_error;
  expected_error << 0.045362, -0.00634672, -0.00541173, 0.1;

  EXPECT_TRUE(gtsam::assert_equal(expected_error, error, tol));
}

/**
  * Test that analytical jacobians equal numerical ones.
  *
/* ************************************************************************* */
TEST(testGeneralParallelPlaneRegularFactor, Jacobians) {
  /// Plane keys.
  Key plane_key_1(1);
  Key plane_key_2(2);

  /// Noise model for cosntraint between the two planes.
  noiseModel::Diagonal::shared_ptr parallel_plane_noise =
      noiseModel::Diagonal::Sigmas(Vector3(0.1, 0.1, 0.1));

  /// GeneralParallelism constraint between Plane 1 and Plane 2.
  GeneralParallelPlaneRegularBasicFactor factor(plane_key_1, plane_key_2,
                                                parallel_plane_noise);

  /// Planes.
  OrientedPlane3 plane_1(0.3, 0.2, 1.9, 0.9);
  OrientedPlane3 plane_2(0.1, 0.1, 0.9, 0.1);

  /// Use the factor to calculate the Jacobians.
  gtsam::Matrix H1Actual, H2Actual;
  factor.evaluateError(plane_1, plane_2, H1Actual, H2Actual);

  /// Calculate numerical derivatives.
  Matrix H1Expected =
      numericalDerivative21<Vector, OrientedPlane3, OrientedPlane3>(
          boost::bind(&GeneralParallelPlaneRegularBasicFactor::evaluateError,
                      &factor, _1, _2, boost::none, boost::none),
          plane_1, plane_2, der_tol);

  Matrix H2Expected =
      numericalDerivative22<Vector, OrientedPlane3, OrientedPlane3>(
          boost::bind(&GeneralParallelPlaneRegularBasicFactor::evaluateError,
                      &factor, _1, _2, boost::none, boost::none),
          plane_1, plane_2, der_tol);

  /// Verify the Jacobians are correct.
  EXPECT_TRUE(assert_equal(H1Expected, H1Actual, tol));
  EXPECT_TRUE(assert_equal(H2Expected, H2Actual, tol));
}

/**
  * Test that optimization works.
  * Two planes constrained together, using distance + parallelism factor.
  * With one of the planes having a prior.
  *
  *                  Prior
  *                   +-+        Parallelism +
  *                   +-+        Distance
  *                    |           factor
  *                +---+---+        +-+         +-------+
  *                |Plane 1+--------+ +---------+Plane 2|
  *                +-------+        +-+         +-------+
  *
/* ************************************************************************* */
TEST(testGeneralParallelPlaneRegularBasicFactor, PlaneOptimization) {
  NonlinearFactorGraph graph;

  Key planeKey1 = 1;
  Key planeKey2 = 2;

  /// Noise prior.
  noiseModel::Diagonal::shared_ptr priorNoise =
      noiseModel::Diagonal::Sigmas(Vector3(0.1, 0.1, 0.1));

  /// Prior mean for plane 1.
  OrientedPlane3 priorMean1(0.0, 0.0, 1.0, 0.0);
  graph.emplace_shared<PriorFactor<OrientedPlane3> >(planeKey1, priorMean1,
                                                     priorNoise);

  /// Shared noise for constraints between planes.
  noiseModel::Diagonal::shared_ptr regularityNoise =
      noiseModel::Diagonal::Sigmas(Vector4(0.1, 0.1, 0.1, 0.1));

  /// Factor between planes for co-planarity.
  const double measured_distance_from_plane2_to_plane1(-2.0);
  graph.emplace_shared<GeneralParallelPlaneRegularBasicFactor>(
      planeKey1, planeKey2, regularityNoise,
      measured_distance_from_plane2_to_plane1);

  Values initial;
  initial.insert(planeKey1, OrientedPlane3(0.1, 0.2, 0.9, 0.0));
  initial.insert(planeKey2, OrientedPlane3(0.1, 0.2, 0.3, -1.0));

  GaussNewtonParams params;
  // params.setVerbosity("ERROR");
  params.setMaxIterations(20);
  params.setRelativeErrorTol(-std::numeric_limits<double>::max());
  // params.setErrorTol(-std::numeric_limits<double>::max());
  params.setAbsoluteErrorTol(-std::numeric_limits<double>::max());

  Values result = GaussNewtonOptimizer(graph, initial, params).optimize();
  // Values result = LevenbergMarquardtOptimizer(graph, initial,
  // params).optimize();

  Values expected;
  expected.insert(planeKey1, OrientedPlane3(0.0, 0.0, 1.0, 0.0));
  expected.insert(planeKey2, OrientedPlane3(0.0, 0.0, 1.0, 2.0));

  EXPECT_TRUE(gtsam::assert_equal(expected, result, tol));
}