Merge pull request #1293 from PlantsAndBuildings/hmm-cli-tests-2

Tests for bindings hmm_loglik, hmm_generate and hmm_viterbi
This commit is contained in:
Ryan Curtin
2018-03-14 05:07:56 -07:00
committed by GitHub
6 changed files with 672 additions and 3 deletions
+8 -3
View File
@@ -68,6 +68,11 @@ struct Generate
mat observations;
Row<size_t> sequence;
RequireParamValue<int>("start_state", [](int x) { return x >= 0; }, true,
"Invalid start state");
RequireParamValue<int>("length", [](int x) { return x >= 0; }, true,
"Length must be >= 0");
// Load the parameters.
const size_t startState = (size_t) CLI::GetParam<int>("start_state");
const size_t length = (size_t) CLI::GetParam<int>("length");
@@ -104,7 +109,7 @@ static void mlpackMain()
RandomSeed((size_t) time(NULL));
// Load model, and perform the generation.
HMMModel hmm;
hmm = std::move(CLI::GetParam<HMMModel>("model"));
hmm.PerformAction<Generate, void>(NULL); // No extra data required.
HMMModel* hmm;
hmm = std::move(CLI::GetParam<HMMModel*>("model"));
hmm->PerformAction<Generate, void>(NULL); // No extra data required.
}
+4
View File
@@ -149,7 +149,11 @@ add_executable(mlpack_test
main_tests/sparse_coding_test.cpp
main_tests/kmeans_test.cpp
main_tests/hoeffding_tree_test.cpp
main_tests/hmm_viterbi_test.cpp
main_tests/hmm_train_test.cpp
main_tests/hmm_loglik_test.cpp
main_tests/hmm_generate_test.cpp
main_tests/hmm_test_utils.hpp
)
# Link dependencies of test executable.
@@ -0,0 +1,249 @@
/**
* @file hmm_generate_test.cpp
* @author Daivik Nema
*
* Test mlpackMain() of hmm_generate_main.cpp
*/
#include <string>
#define BINDING_TYPE BINDING_TYPE_TEST
static const std::string testName = "HMMGenerate";
#include <mlpack/core.hpp>
#include <mlpack/core/util/mlpack_main.hpp>
#include "test_helper.hpp"
#include <mlpack/methods/hmm/hmm_model.hpp>
#include <mlpack/methods/hmm/hmm.hpp>
#include <mlpack/methods/hmm/hmm_generate_main.cpp>
#include <boost/test/unit_test.hpp>
#include "../test_tools.hpp"
#include "hmm_test_utils.hpp"
using namespace mlpack;
struct HMMGenerateTestFixture
{
public:
HMMGenerateTestFixture()
{
// Cache in the options for this program.
CLI::RestoreSettings(testName);
}
~HMMGenerateTestFixture()
{
// Clear the settings.
bindings::tests::CleanMemory();
CLI::ClearSettings();
}
};
BOOST_FIXTURE_TEST_SUITE(HMMGenerateMainTest, HMMGenerateTestFixture);
BOOST_AUTO_TEST_CASE(HMMGenerateDiscreteHMMCheckDimensionsTest)
{
// Load data to train a discrete HMM model with.
arma::mat inp;
data::Load("obs1.csv", inp);
std::vector<arma::mat> trainSeq = {inp};
// Initialize and train a discrete HMM model.
HMMModel* h = new HMMModel(DiscreteHMM);
h->PerformAction<InitHMMModel, std::vector<arma::mat>>(&trainSeq);
h->PerformAction<TrainHMMModel, std::vector<arma::mat>>(&trainSeq);
// Now that we have a trained HMM model, we can use it to generate a sequence
// of states and observations - using the hmm_generate utility.
// Load the input model to be used for inference and the length of sequence
// to be generated.
int length = 3;
SetInputParam("model", h);
SetInputParam("length", length);
// Call to hmm_generate_main.
mlpackMain();
// Get the generated observation sequence. Ensure that the generated sequence
// has the correct length (as provided in the input).
arma::mat obsSeq = CLI::GetParam<arma::mat>("output");
BOOST_REQUIRE_EQUAL(obsSeq.n_cols, (size_t)length);
BOOST_REQUIRE_EQUAL(obsSeq.n_rows, (size_t)1);
BOOST_REQUIRE_EQUAL(obsSeq.n_elem, (size_t)length);
// Get the generated state sequence. Ensure that the generated sequence
// has the correct length (as provided in the input).
arma::Mat<size_t> stateSeq = CLI::GetParam<arma::Mat<size_t>>("state");
BOOST_REQUIRE_EQUAL(stateSeq.n_cols, (size_t)length);
BOOST_REQUIRE_EQUAL(stateSeq.n_rows, (size_t)1);
BOOST_REQUIRE_EQUAL(stateSeq.n_elem, (size_t)length);
}
BOOST_AUTO_TEST_CASE(HMMGenerateGaussianHMMCheckDimensionsTest)
{
// Load data to train a gaussian HMM model with.
arma::mat inp;
data::Load("obs1.csv", inp);
std::vector<arma::mat> trainSeq = {inp};
// Initialize and train a gaussian HMM model.
HMMModel* h = new HMMModel(GaussianHMM);
h->PerformAction<InitHMMModel, std::vector<arma::mat>>(&trainSeq);
h->PerformAction<TrainHMMModel, std::vector<arma::mat>>(&trainSeq);
// Now that we have a trained HMM model, we can use it to generate a sequence
// of states and observations - using the hmm_generate utility.
// Load the input model to be used for inference and the length of sequence
// to be generated.
int length = 3;
SetInputParam("model", h);
SetInputParam("length", length);
// Call to hmm_generate_main.
mlpackMain();
// Get the generated observation sequence. Ensure that the generated sequence
// has the correct length (as provided in the input).
arma::mat obsSeq = CLI::GetParam<arma::mat>("output");
BOOST_REQUIRE_EQUAL(obsSeq.n_cols, (size_t)length);
BOOST_REQUIRE_EQUAL(obsSeq.n_rows, (size_t)1);
BOOST_REQUIRE_EQUAL(obsSeq.n_elem, (size_t)length);
// Get the generated state sequence. Ensure that the generated sequence
// has the correct length (as provided in the input).
arma::Mat<size_t> stateSeq = CLI::GetParam<arma::Mat<size_t>>("state");
BOOST_REQUIRE_EQUAL(stateSeq.n_cols, (size_t)length);
BOOST_REQUIRE_EQUAL(stateSeq.n_rows, (size_t)1);
BOOST_REQUIRE_EQUAL(stateSeq.n_elem, (size_t)length);
}
BOOST_AUTO_TEST_CASE(HMMGenerateGMMHMMCheckDimensionsTest)
{
// Load data to train a Gaussian Mixture Model HMM model with.
std::vector<GMM> gmms(2, GMM(2, 2));
gmms[0].Weights() = arma::vec("0.3 0.7");
// N([2.25 3.10], [1.00 0.20; 0.20 0.89])
gmms[0].Component(0) = GaussianDistribution("4.25 3.10",
"1.00 0.20; 0.20 0.89");
// N([4.10 1.01], [1.00 0.00; 0.00 1.01])
gmms[0].Component(1) = GaussianDistribution("7.10 5.01",
"1.00 0.00; 0.00 1.01");
gmms[1].Weights() = arma::vec("0.20 0.80");
gmms[1].Component(0) = GaussianDistribution("-3.00 -6.12",
"1.00 0.00; 0.00 1.00");
gmms[1].Component(1) = GaussianDistribution("-4.25 -2.12",
"1.50 0.60; 0.60 1.20");
// Transition matrix.
arma::mat transMat("0.40 0.60;"
"0.60 0.40");
// Make a sequence of observations.
std::vector<arma::mat> observations(5, arma::mat(2, 50));
std::vector<arma::Row<size_t> > states(5, arma::Row<size_t>(50));
for (size_t obs = 0; obs < 5; obs++)
{
states[obs][0] = 0;
observations[obs].col(0) = gmms[0].Random();
for (size_t i = 1; i < 50; i++)
{
double randValue = (double) rand() / (double) RAND_MAX;
if (randValue <= transMat(0, states[obs][i - 1]))
states[obs][i] = 0;
else
states[obs][i] = 1;
observations[obs].col(i) = gmms[states[obs][i]].Random();
}
}
// Initialize and train a GMM HMM model.
HMMModel* h = new HMMModel(GaussianMixtureModelHMM);
h->PerformAction<InitHMMModel, std::vector<arma::mat>>(&observations);
h->PerformAction<TrainHMMModel, std::vector<arma::mat>>(&observations);
// Now that we have a trained HMM model, we can use it to generate a sequence
// of states and observations - using the hmm_generate utility.
// Load the input model to be used for inference and the length of sequence
// to be generated.
int length = 3;
SetInputParam("model", h);
SetInputParam("length", length);
// Call to hmm_generate_main
mlpackMain();
// Get the generated observation sequence. Ensure that the generated sequence
// has the correct length (as provided in the input).
arma::mat obsSeq = CLI::GetParam<arma::mat>("output");
BOOST_REQUIRE_EQUAL(obsSeq.n_cols, (size_t)length);
BOOST_REQUIRE_EQUAL(obsSeq.n_rows, (size_t)2);
BOOST_REQUIRE_EQUAL(obsSeq.n_elem, (size_t)(length*2));
// Get the generated state sequence. Ensure that the generated sequence
// has the correct length (as provided in the input).
arma::Mat<size_t> stateSeq = CLI::GetParam<arma::Mat<size_t>>("state");
BOOST_REQUIRE_EQUAL(stateSeq.n_cols, (size_t)length);
BOOST_REQUIRE_EQUAL(stateSeq.n_rows, (size_t)1);
BOOST_REQUIRE_EQUAL(stateSeq.n_elem, (size_t)length);
}
BOOST_AUTO_TEST_CASE(HMMGenerateLengthPositiveTest)
{
// Load data to train a Gaussian Mixture Model HMM model with.
arma::mat inp;
data::Load("obs1.csv", inp);
std::vector<arma::mat> trainSeq = {inp};
// Initialize and train a HMM model.
HMMModel* h = new HMMModel(DiscreteHMM);
h->PerformAction<InitHMMModel, std::vector<arma::mat>>(&trainSeq);
h->PerformAction<TrainHMMModel, std::vector<arma::mat>>(&trainSeq);
// Set the params for the hmm_generate invocation
// Note that the length is negative - we expect that a runtime error will be
// raised in the call to hmm_generate_main
int length = -3; // Invalid
SetInputParam("model", h);
SetInputParam("length", length);
Log::Fatal.ignoreInput = true;
BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error);
Log::Fatal.ignoreInput = false;
}
BOOST_AUTO_TEST_CASE(HMMGenerateValidStartStateTest)
{
// Load data to train a Gaussian Mixture Model HMM model with.
arma::mat inp;
data::Load("obs1.csv", inp);
std::vector<arma::mat> trainSeq = {inp};
// Initialize and train a HMM model.
HMMModel* h = new HMMModel(DiscreteHMM);
h->PerformAction<InitHMMModel, std::vector<arma::mat>>(&trainSeq);
h->PerformAction<TrainHMMModel, std::vector<arma::mat>>(&trainSeq);
// Set the params for the hmm_generate invocation
// Note that the start state is invalid - we expect that a runtime error will
// be raised in the call to hmm_generate_main
int length = 3;
int startState = 2; // Invalid
SetInputParam("model", h);
SetInputParam("length", length);
SetInputParam("start_state", startState);
Log::Fatal.ignoreInput = true;
BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error);
Log::Fatal.ignoreInput = false;
}
BOOST_AUTO_TEST_SUITE_END();
@@ -0,0 +1,70 @@
/**
* @file hmm_loglik_test.cpp
* @author Daivik Nema
*
* Test mlpackMain() of hmm_loglik_main.cpp
*/
#include <string>
#define BINDING_TYPE BINDING_TYPE_TEST
static const std::string testName = "HMMLoglik";
#include <mlpack/core.hpp>
#include <mlpack/core/util/mlpack_main.hpp>
#include "test_helper.hpp"
#include <mlpack/methods/hmm/hmm_model.hpp>
#include <mlpack/methods/hmm/hmm.hpp>
#include <mlpack/methods/hmm/hmm_loglik_main.cpp>
#include <boost/test/unit_test.hpp>
#include "../test_tools.hpp"
#include "hmm_test_utils.hpp"
using namespace mlpack;
struct HMMLoglikTestFixture
{
public:
HMMLoglikTestFixture()
{
// Cache in the options for this program.
CLI::RestoreSettings(testName);
}
~HMMLoglikTestFixture()
{
// Clear the settings.
bindings::tests::CleanMemory();
CLI::ClearSettings();
}
};
BOOST_FIXTURE_TEST_SUITE(HMMLoglikMainTest, HMMLoglikTestFixture);
BOOST_AUTO_TEST_CASE(HMMLoglikOutputNegativeTest)
{
// Load data to train a discrete HMM model with.
arma::mat inp;
data::Load("obs1.csv", inp);
std::vector<arma::mat> trainSeq = {inp};
// Initialize and train an HMM model.
HMMModel* h = new HMMModel(DiscreteHMM);
h->PerformAction<InitHMMModel, std::vector<arma::mat>>(&trainSeq);
h->PerformAction<TrainHMMModel, std::vector<arma::mat>>(&trainSeq);
// Set the params for the hmm_loglik invocation
SetInputParam("input_model", h);
SetInputParam("input", inp);
mlpackMain();
double loglik = CLI::GetParam<double>("log_likelihood");
// Since the log of a probability <= 0 ...
BOOST_REQUIRE(loglik <= 0);
}
BOOST_AUTO_TEST_SUITE_END();
@@ -0,0 +1,163 @@
/**
* @file hmm_test_utils.hpp
* @author Daivik Nema
*
* Structs for initializing and training HMMs (either of Discrete, Gaussian or
* GMM HMMs). These structs are passed as template parameters to the
* PerformAction function of an HMMModel object. These structs have been adapted
* from the structs in mlpack/methods/hmm/hmm_train_main.cpp.
*
* 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.
*/
#ifndef MLPACK_TESTS_MAIN_TESTS_HMM_TEST_UTILS_HPP
#define MLPACK_TESTS_MAIN_TESTS_HMM_TEST_UTILS_HPP
#include <mlpack/prereqs.hpp>
#include <mlpack/methods/hmm/hmm.hpp>
struct InitHMMModel
{
template<typename HMMType>
static void Apply(HMMType& hmm, vector<mat>* trainSeq)
{
const size_t states = 2;
// Create the initialized-to-zero model.
Create(hmm, *trainSeq, states);
// Initializing the emission distribution depends on the distribution.
// Therefore we have to use the helper functions.
RandomInitialize(hmm.Emission());
}
//! Helper function to create discrete HMM.
static void Create(HMM<DiscreteDistribution>& hmm,
vector<mat>& trainSeq,
size_t states,
double tolerance = 1e-05)
{
// Maximum observation is necessary so we know how to train the discrete
// distribution.
arma::Col<size_t> maxEmissions(trainSeq[0].n_rows);
maxEmissions.zeros();
for (vector<mat>::iterator it = trainSeq.begin(); it != trainSeq.end();
++it)
{
arma::Col<size_t> maxSeqs =
arma::conv_to<arma::Col<size_t>>::from(arma::max(*it, 1)) + 1;
maxEmissions = arma::max(maxEmissions, maxSeqs);
}
hmm = HMM<DiscreteDistribution>(size_t(states),
DiscreteDistribution(maxEmissions), tolerance);
}
static void Create(HMM<GaussianDistribution>& hmm,
vector<mat>& trainSeq,
size_t states,
double tolerance = 1e-05)
{
// Find dimension of the data.
const size_t dimensionality = trainSeq[0].n_rows;
// Verify dimensionality of data.
for (size_t i = 0; i < trainSeq.size(); ++i)
{
if (trainSeq[i].n_rows != dimensionality)
{
Log::Fatal << "Observation sequence " << i << " dimensionality ("
<< trainSeq[i].n_rows << " is incorrect (should be "
<< dimensionality << ")!" << endl;
}
}
// Get the model and initialize it.
hmm = HMM<GaussianDistribution>(size_t(states),
GaussianDistribution(dimensionality), tolerance);
}
static void Create(HMM<GMM>& hmm,
vector<mat>& trainSeq,
size_t states,
double tolerance = 1e-05)
{
// Find dimension of the data.
const size_t dimensionality = trainSeq[0].n_rows;
const int gaussians = 2;
if (gaussians == 0)
{
Log::Fatal << "Number of gaussians for each GMM must be specified "
<< "when type = 'gmm'!" << endl;
}
if (gaussians < 0)
{
Log::Fatal << "Invalid number of gaussians (" << gaussians << "); must "
<< "be greater than or equal to 1." << endl;
}
// Create HMM object.
hmm = HMM<GMM>(size_t(states), GMM(size_t(gaussians), dimensionality),
tolerance);
}
//! Helper function for discrete emission distributions.
static void RandomInitialize(vector<DiscreteDistribution>& e)
{
for (size_t i = 0; i < e.size(); ++i)
{
e[i].Probabilities().randu();
e[i].Probabilities() /= arma::accu(e[i].Probabilities());
}
}
static void RandomInitialize(vector<GaussianDistribution>& e)
{
for (size_t i = 0; i < e.size(); ++i)
{
const size_t dimensionality = e[i].Mean().n_rows;
e[i].Mean().randu();
// Generate random covariance.
arma::mat r = arma::randu<arma::mat>(dimensionality, dimensionality);
e[i].Covariance(r * r.t());
}
}
static void RandomInitialize(vector<GMM>& e)
{
for (size_t i = 0; i < e.size(); ++i)
{
// Random weights.
e[i].Weights().randu();
e[i].Weights() /= arma::accu(e[i].Weights());
// Random means and covariances.
for (int g = 0; g < 2; ++g)
{
const size_t dimensionality = e[i].Component(g).Mean().n_rows;
e[i].Component(g).Mean().randu();
// Generate random covariance.
arma::mat r = arma::randu<arma::mat>(dimensionality,
dimensionality);
e[i].Component(g).Covariance(r * r.t());
}
}
}
};
struct TrainHMMModel
{
template<typename HMMType>
static void Apply(HMMType& hmm, vector<arma::mat>* trainSeq)
{
// For now, perform unsupervised (Baum-Welch) training.
hmm.Train(*trainSeq);
}
};
#endif
@@ -0,0 +1,178 @@
/**
* @file hmm_viterbi_test.cpp
* @author Daivik Nema
*
* Test mlpackMain() of hmm_viterbi_main.cpp
*/
#include <string>
#define BINDING_TYPE BINDING_TYPE_TEST
static const std::string testName = "HMMViterbi";
#include <mlpack/core.hpp>
#include <mlpack/core/util/mlpack_main.hpp>
#include "test_helper.hpp"
#include <mlpack/methods/hmm/hmm_model.hpp>
#include <mlpack/methods/hmm/hmm.hpp>
#include <mlpack/methods/hmm/hmm_viterbi_main.cpp>
#include <boost/test/unit_test.hpp>
#include "../test_tools.hpp"
#include "hmm_test_utils.hpp"
using namespace mlpack;
struct HMMViterbiTestFixture
{
public:
HMMViterbiTestFixture()
{
// Cache in the options for this program.
CLI::RestoreSettings(testName);
}
~HMMViterbiTestFixture()
{
// Clear the settings.
bindings::tests::CleanMemory();
CLI::ClearSettings();
}
};
BOOST_FIXTURE_TEST_SUITE(HMMViterbiMainTest, HMMViterbiTestFixture);
BOOST_AUTO_TEST_CASE(HMMViterbiDiscreteHMMCheckDimensionsTest)
{
// Load data to train a discrete HMM model with.
arma::mat inp;
data::Load("obs1.csv", inp);
std::vector<arma::mat> trainSeq = {inp};
// Initialize and train a discrete HMM model.
HMMModel* h = new HMMModel(DiscreteHMM);
h->PerformAction<InitHMMModel, std::vector<arma::mat>>(&trainSeq);
h->PerformAction<TrainHMMModel, std::vector<arma::mat>>(&trainSeq);
// Now that we have a trained HMM model, we can use it to predict the state
// sequence for a given observation sequence - using the Viterbi algorithm.
// Load the input model to be used for inference and the sequence over which
// inference is to be performed.
SetInputParam("input_model", h);
SetInputParam("input", inp);
// Call to hmm_viterbi_main.
mlpackMain();
// Get the output of viterbi inference.
arma::Mat<size_t> out = CLI::GetParam<arma::Mat<size_t> >("output");
// Output sequence length must be the same as input sequence length and
// there should only be one row (since states are single dimensional values).
BOOST_REQUIRE_EQUAL(out.n_rows, 1);
BOOST_REQUIRE_EQUAL(out.n_cols, inp.n_cols);
}
BOOST_AUTO_TEST_CASE(HMMViterbiGaussianHMMCheckDimensionsTest)
{
// Load data to train a gaussian HMM model with.
arma::mat inp;
data::Load("obs1.csv", inp);
std::vector<arma::mat> trainSeq = {inp};
// Initialize and train a gaussian HMM model.
HMMModel* h = new HMMModel(GaussianHMM);
h->PerformAction<InitHMMModel, std::vector<arma::mat>>(&trainSeq);
h->PerformAction<TrainHMMModel, std::vector<arma::mat>>(&trainSeq);
// Now that we have a trained HMM model, we can use it to predict the state
// sequence for a given observation sequence - using the Viterbi algorithm.
// Load the input model to be used for inference and the sequence over which
// inference is to be performed.
SetInputParam("input_model", h);
SetInputParam("input", inp);
// Call to hmm_viterbi_main.
mlpackMain();
// Get the output of viterbi inference.
arma::Mat<size_t> out = CLI::GetParam<arma::Mat<size_t> >("output");
// Output sequence length must be the same as input sequence length and
// there should only be one row (since states are single dimensional values).
BOOST_REQUIRE_EQUAL(out.n_rows, 1);
BOOST_REQUIRE_EQUAL(out.n_cols, inp.n_cols);
}
BOOST_AUTO_TEST_CASE(HMMViterbiGMMHMMCheckDimensionsTest)
{
// Load data to train a Gaussian Mixture Model HMM model with.
std::vector<GMM> gmms(2, GMM(2, 2));
gmms[0].Weights() = arma::vec("0.3 0.7");
// N([2.25 3.10], [1.00 0.20; 0.20 0.89])
gmms[0].Component(0) = GaussianDistribution("4.25 3.10",
"1.00 0.20; 0.20 0.89");
// N([4.10 1.01], [1.00 0.00; 0.00 1.01])
gmms[0].Component(1) = GaussianDistribution("7.10 5.01",
"1.00 0.00; 0.00 1.01");
gmms[1].Weights() = arma::vec("0.20 0.80");
gmms[1].Component(0) = GaussianDistribution("-3.00 -6.12",
"1.00 0.00; 0.00 1.00");
gmms[1].Component(1) = GaussianDistribution("-4.25 -2.12",
"1.50 0.60; 0.60 1.20");
// Transition matrix.
arma::mat transMat("0.40 0.60;"
"0.60 0.40");
// Make a sequence of observations.
std::vector<arma::mat> observations(5, arma::mat(2, 50));
std::vector<arma::Row<size_t> > states(5, arma::Row<size_t>(50));
for (size_t obs = 0; obs < 5; obs++)
{
states[obs][0] = 0;
observations[obs].col(0) = gmms[0].Random();
for (size_t i = 1; i < 50; i++)
{
double randValue = (double) rand() / (double) RAND_MAX;
if (randValue <= transMat(0, states[obs][i - 1]))
states[obs][i] = 0;
else
states[obs][i] = 1;
observations[obs].col(i) = gmms[states[obs][i]].Random();
}
}
// Initialize and train a GMM HMM model.
HMMModel* h = new HMMModel(GaussianMixtureModelHMM);
h->PerformAction<InitHMMModel, std::vector<arma::mat>>(&observations);
h->PerformAction<TrainHMMModel, std::vector<arma::mat>>(&observations);
// Now that we have a trained HMM model, we can use it to predict the state
// sequence for a given observation sequence - using the Viterbi algorithm.
// Load the input model to be used for inference and the sequence over which
// inference is to be performed.
SetInputParam("input_model", h);
SetInputParam("input", observations[0]);
// Call to hmm_viterbi_main.
mlpackMain();
// Get the output of viterbi inference.
arma::Mat<size_t> out = CLI::GetParam<arma::Mat<size_t> >("output");
// Output sequence length must be the same as input sequence length and
// there should only be one row (since states are single dimensional values).
BOOST_REQUIRE_EQUAL(out.n_rows, 1);
BOOST_REQUIRE_EQUAL(out.n_cols, observations[0].n_cols);
}
BOOST_AUTO_TEST_SUITE_END();