diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3bbc9ebe6dd3308caf03968ee03c17f709f916a1..7707815e5ee5d12b21a586e367d4ad39ba8db50e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -4,7 +4,7 @@ cmake_minimum_required(VERSION 3.10)
 include( ExternalProject )
 
 # set the project name
-project(sisso++ VERSION 0.1 LANGUAGES CXX)
+project(sisso++ VERSION 0.1 LANGUAGES CXX C)
 
 # Cmake modules/macros are in a subdirectory to keep this file cleaner
 set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
@@ -347,5 +347,28 @@ ExternalProject_Add_StepDependencies(external_Clp CoinUtils)
 set(COIN_CLP_LIBRARIES "${COIN_CLP_LIBRARY_DIRS}/libClp.so;${COIN_CLP_LIBRARY_DIRS}/libCoinUtils.so")
 include_directories(${COIN_CLP_INCLUDE_DIRS})
 
+find_package(GTest)
+if(NOT GTEST_FOUND)
+    set(GTEST_BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/external/gtest/build/")
+    set(GTEST_INSTALL_DIR "${CMAKE_CURRENT_BINARY_DIR}/external/gtest/bin/")
+    set(GTEST_INCLUDE_DIRS "${GTEST_INSTALL_DIR}/include")
+    set(GTEST_LIBRARY_DIRS "${GTEST_INSTALL_DIR}/lib")
+
+    ExternalProject_Add(
+        external_gtest
+        PREFIX "external/gtest"
+        GIT_REPOSITORY "https://github.com/google/googletest.git"
+        GIT_TAG "v1.10.x"
+        CMAKE_ARGS "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER};-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER};-DCMAKE_INSTALL_PREFIX=${GTEST_INSTALL_DIR};-DCMAKE_INSTALL_LIBDIR=${GTEST_LIBRARY_DIRS};-DBUILD_SHARED_LIBS=ON;"
+        BINARY_DIR "${GTEST_BUILD_DIR}"
+        INSTALL_DIR "${GTEST_INSTALL_DIR}"
+    )
+    set(GTEST_BOTH_LIBRARIES "${GTEST_LIBRARY_DIRS}/libgtest.so;${GTEST_LIBRARY_DIRS}/libgtest_main.so;${GTEST_LIBRARY_DIRS}/libgmock.so;${GTEST_LIBRARY_DIRS}/libgmock_main.so")
+endif()
+
+include_directories(${GTEST_INCLUDE_DIRS})
+
 include_directories(${CMAKE_CURRENT_LIST_DIR}/src)
+
 add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/src)
+add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/tests/googletest/)
diff --git a/src/descriptor_identifier/Model/Model.hpp b/src/descriptor_identifier/Model/Model.hpp
index 6dfca9d39141b725770a8f1aa708dc17e9cc6150..b9fcf0f935fc5345de93e5ab92788a83d715fe7a 100644
--- a/src/descriptor_identifier/Model/Model.hpp
+++ b/src/descriptor_identifier/Model/Model.hpp
@@ -150,13 +150,19 @@ public:
      */
     inline bool fix_intercept(){return _fix_intercept;}
 
+    /**
+     * @brief Accessor to the coefficients for the model
+     * @return The coefficients for the model for each task
+     */
+    inline std::vector<std::vector<double>> coefs(){return _coefs;}
+
     #ifdef PY_BINDINGS
         // DocString: model_coefs
         /**
          * @brief The coefficient array for the model
          * @return The coefficients as a python list
          */
-        inline py::list coefs()
+        inline py::list coefs_py()
         {
             py::list coef_lst;
             for(auto& task_coefs : _coefs)
diff --git a/src/descriptor_identifier/Model/ModelClassifier.cpp b/src/descriptor_identifier/Model/ModelClassifier.cpp
index 3f41f0e247a748fd63fbea408854b872b65430a2..3784e5033b229c1f491c853b9a0091b9a295d649 100644
--- a/src/descriptor_identifier/Model/ModelClassifier.cpp
+++ b/src/descriptor_identifier/Model/ModelClassifier.cpp
@@ -444,7 +444,9 @@ std::ostream& operator<< (std::ostream& outStream, const ModelClassifier& model)
 void ModelClassifier::to_file(std::string filename, bool train, std::vector<int> test_inds)
 {
     boost::filesystem::path p(filename.c_str());
-    boost::filesystem::create_directories(p.remove_filename());
+    boost::filesystem::path parent = p.remove_filename();
+    if(parent.string().size() > 0)
+        boost::filesystem::create_directories(parent);
 
     std::ofstream out_file_stream = std::ofstream();
     out_file_stream.open(filename);
diff --git a/src/descriptor_identifier/Model/ModelLogRegressor.cpp b/src/descriptor_identifier/Model/ModelLogRegressor.cpp
index 7a86f6f260d1cb964d65270077017dbee2aaf8c2..76064e7faff8dc3a5843fc70139f99469aedf1a3 100644
--- a/src/descriptor_identifier/Model/ModelLogRegressor.cpp
+++ b/src/descriptor_identifier/Model/ModelLogRegressor.cpp
@@ -1,18 +1,20 @@
 #include <descriptor_identifier/Model/ModelLogRegressor.hpp>
 
+ModelLogRegressor::ModelLogRegressor()
+{}
+
 ModelLogRegressor::ModelLogRegressor(std::string prop_label, Unit prop_unit, std::vector<double> prop_train, std::vector<double> prop_test, std::vector<model_node_ptr> feats, std::vector<int> task_sizes_train, std::vector<int> task_sizes_test, bool fix_intercept) :
-    ModelRegressor(prop_label, prop_unit, prop_train, prop_test, feats, task_sizes_train, task_sizes_test, fix_intercept),
+    ModelRegressor(prop_label, prop_unit, prop_train, prop_test, feats, task_sizes_train, task_sizes_test, fix_intercept, false),
     _log_prop_train(_n_samp_train, 0.0),
     _log_prop_test(_n_samp_test, 0.0),
     _log_prop_train_est(_n_samp_train, 0.0),
     _log_prop_test_est(_n_samp_test, 0.0),
-    _log_train_error(_n_samp_train),
-    _log_test_error(_n_samp_test)
+    _log_train_error(_n_samp_train, 0.0),
+    _log_test_error(_n_samp_test, 0.0)
 {
     std::transform(_prop_train.begin(), _prop_train.end(), _log_prop_train.begin(),[](double p){return std::log(p);});
     std::transform(_prop_test.begin(), _prop_test.end(), _log_prop_test.begin(),[](double p){return std::log(p);});
 
-
     _log_prop_train.reserve(_n_samp_train);
     _log_prop_test.reserve(_n_samp_test);
 
@@ -38,6 +40,7 @@ ModelLogRegressor::ModelLogRegressor(std::string prop_label, Unit prop_unit, std
 
         dgels_('N', sz, _n_dim + 1 - _fix_intercept, 1, a.data(), sz, _log_prop_train.data() + start, sz, work.data(), work.size(), &info);
 
+        _coefs.push_back(std::vector<double>(_n_dim + (!fix_intercept), 0.0));
         std::copy_n(_log_prop_train.begin() + start, _n_dim + 1 - _fix_intercept, _coefs[tt].data());
         std::transform(_prop_train.begin() + start, _prop_train.begin() + start + sz, _log_prop_train.begin() + start, [](double p){return std::log(p);});
 
@@ -53,10 +56,10 @@ ModelLogRegressor::ModelLogRegressor(std::string prop_label, Unit prop_unit, std
     {
         if(sz > 0)
         {
+            std::fill_n(_D_test.data(), _D_test.size(), 1.0);
             for(int ff = 0; ff < feats.size(); ++ff)
                 std::transform(feats[ff]->test_value_ptr() + start, feats[ff]->test_value_ptr() + start + sz, _D_test.data() + ff * sz, [](double fv){return std::log(fv);});
 
-            std::fill_n(_D_test.data() + feats.size() * sz, sz, 1.0);
             dgemv_('N', sz, _n_dim + 1 - _fix_intercept, 1.0, _D_test.data(), sz, _coefs[ii].data(), 1, 0.0, _log_prop_test_est.data() + start, 1);
             std::transform(_log_prop_test_est.begin() + start, _log_prop_test_est.begin() + start + sz, _log_prop_test.data() + start, _log_test_error.data() + start, std::minus<double>());
         }
@@ -72,7 +75,13 @@ ModelLogRegressor::ModelLogRegressor(std::string prop_label, Unit prop_unit, std
 }
 
 ModelLogRegressor::ModelLogRegressor(std::string train_file) :
-    ModelRegressor(train_file)
+    ModelRegressor(train_file),
+    _log_prop_train(_n_samp_train, 0.0),
+    _log_prop_test(_n_samp_test, 0.0),
+    _log_prop_train_est(_n_samp_train, 0.0),
+    _log_prop_test_est(_n_samp_test, 0.0),
+    _log_train_error(_n_samp_train),
+    _log_test_error(_n_samp_test)
 {
     std::transform(_D_train.begin(), _D_train.begin() + _n_samp_train, _D_train.begin(), [](double dt){return std::log(dt);});
     std::transform(_prop_train.begin(), _prop_train.end(), _log_prop_train.begin(), [](double pt){return std::log(pt);});
@@ -82,7 +91,13 @@ ModelLogRegressor::ModelLogRegressor(std::string train_file) :
 }
 
 ModelLogRegressor::ModelLogRegressor(std::string train_file, std::string test_file) :
-    ModelRegressor(train_file, test_file)
+    ModelRegressor(train_file, test_file),
+    _log_prop_train(_n_samp_train, 0.0),
+    _log_prop_test(_n_samp_test, 0.0),
+    _log_prop_train_est(_n_samp_train, 0.0),
+    _log_prop_test_est(_n_samp_test, 0.0),
+    _log_train_error(_n_samp_train),
+    _log_test_error(_n_samp_test)
 {
     std::transform(_D_train.begin(), _D_train.begin() + _n_samp_train, _D_train.begin(), [](double dt){return std::log(dt);});
     std::transform(_D_test.begin(), _D_test.begin() + _n_samp_test, _D_test.begin(), [](double dt){return std::log(dt);});
@@ -103,19 +118,38 @@ ModelLogRegressor::ModelLogRegressor(std::string train_file, std::string test_fi
 std::string ModelLogRegressor::toString() const
 {
     std::stringstream unit_rep;
-    unit_rep << "c0";
-    for(int ff = 0; ff < _feats.size(); ++ff)
-        unit_rep << " * (" << _feats[ff]->expr() << ")^a" << ff;
+    if(_fix_intercept)
+    {
+        unit_rep << "(" << _feats[0]->expr() << ")^a0";
+        for(int ff = 1; ff < _feats.size(); ++ff)
+            unit_rep << " * (" << _feats[ff]->expr() << ")^a" << ff;
+    }
+    else
+    {
+        unit_rep << "exp(c0)";
+        for(int ff = 0; ff < _feats.size(); ++ff)
+            unit_rep << " * (" << _feats[ff]->expr() << ")^a" << ff;
+    }
     return unit_rep.str();
 }
 
 std::string ModelLogRegressor::toLatexString() const
 {
     std::stringstream unit_rep;
-    unit_rep << "$c_0";
-    for(int ff = 0; ff < _feats.size(); ++ff)
-        unit_rep << "\\left(" << _feats[ff]->get_latex_expr("") << "\\right)^{a_" << ff << "}";
-    unit_rep << "$";
+    if(_fix_intercept)
+    {
+        unit_rep << "$\\left(" << _feats[0]->get_latex_expr("") << "\\right)^{a_0}" << std::endl;
+        for(int ff = 1; ff < _feats.size(); ++ff)
+            unit_rep << "\\left(" << _feats[ff]->get_latex_expr("") << "\\right)^{a_" << ff << "}";
+        unit_rep << "$";
+    }
+    else
+    {
+        unit_rep << "$c_0";
+        for(int ff = 0; ff < _feats.size(); ++ff)
+            unit_rep << "\\left(" << _feats[ff]->get_latex_expr("") << "\\right)^{a_" << ff << "}";
+        unit_rep << "$";
+    }
     return unit_rep.str();
 }
 
diff --git a/src/descriptor_identifier/Model/ModelLogRegressor.hpp b/src/descriptor_identifier/Model/ModelLogRegressor.hpp
index 73be5d6b372148f4158b70e18802e83e1a2feb1c..62255b0a23edd25c7a35dc3fadd93426d5edbf01 100644
--- a/src/descriptor_identifier/Model/ModelLogRegressor.hpp
+++ b/src/descriptor_identifier/Model/ModelLogRegressor.hpp
@@ -26,6 +26,7 @@ typedef std::shared_ptr<ModelNode> model_node_ptr;
  */
 class ModelLogRegressor : public ModelRegressor
 {
+protected:
     std::vector<double> _log_prop_train; //!< The log of the real Property
     std::vector<double> _log_prop_test; //!< The log of the real Property
 
@@ -36,6 +37,11 @@ class ModelLogRegressor : public ModelRegressor
     std::vector<double> _log_test_error; //!< The error of the model (testing)
 
 public:
+    /**
+     * @brief Default constructor]
+     */
+    ModelLogRegressor();
+
     /**
      * @brief Constructor for the model
      *
diff --git a/src/descriptor_identifier/Model/ModelRegressor.cpp b/src/descriptor_identifier/Model/ModelRegressor.cpp
index 8926c5cd96715387558077ecf565d81fea4a3e27..ca2b33095adfe5752c81674896ee10b5d369a438 100644
--- a/src/descriptor_identifier/Model/ModelRegressor.cpp
+++ b/src/descriptor_identifier/Model/ModelRegressor.cpp
@@ -1,6 +1,9 @@
 #include <descriptor_identifier/Model/ModelRegressor.hpp>
 
-ModelRegressor::ModelRegressor(std::string prop_label, Unit prop_unit, std::vector<double> prop_train, std::vector<double> prop_test, std::vector<model_node_ptr> feats, std::vector<int> task_sizes_train, std::vector<int> task_sizes_test, bool fix_intercept) :
+ModelRegressor::ModelRegressor()
+{}
+
+ModelRegressor::ModelRegressor(std::string prop_label, Unit prop_unit, std::vector<double> prop_train, std::vector<double> prop_test, std::vector<model_node_ptr> feats, std::vector<int> task_sizes_train, std::vector<int> task_sizes_test, bool fix_intercept, bool fill_vecs) :
     Model(prop_label, prop_unit, prop_train, prop_test, feats, task_sizes_train, task_sizes_test, fix_intercept),
     _prop_train_est(_n_samp_train, 0.0),
     _prop_test_est(_n_samp_test, 0.0),
@@ -9,51 +12,53 @@ ModelRegressor::ModelRegressor(std::string prop_label, Unit prop_unit, std::vect
 {
     _prop_train_est.reserve(_n_samp_train);
     _prop_test_est.reserve(_n_samp_test);
-
-    std::vector<double> a(_D_train.size(), 1.0);
-    std::vector<double> work(_D_train.size(), 0.0);
-
-    int info;
-    int start = 0;
-
-    std::copy_n(_D_train.begin(), _D_train.size(), a.begin());
-    for(auto& sz : _task_sizes_train)
+    if(fill_vecs)
     {
-        std::fill_n(a.data(), a.size(), 1.0);
-        std::fill_n(_D_train.data(), _D_train.size(), 1.0);
+        std::vector<double> a(_D_train.size(), 1.0);
+        std::vector<double> work(_D_train.size(), 0.0);
 
-        for(int ff = 0; ff < feats.size(); ++ff)
+        int info;
+        int start = 0;
+
+        std::copy_n(_D_train.begin(), _D_train.size(), a.begin());
+        for(auto& sz : _task_sizes_train)
         {
-            std::copy_n(feats[ff]->value_ptr() + start, sz, _D_train.data() + ff * sz);
-            std::copy_n(feats[ff]->value_ptr() + start, sz, a.data() + ff * sz);
-        }
+            std::fill_n(a.data(), a.size(), 1.0);
+            std::fill_n(_D_train.data(), _D_train.size(), 1.0);
 
-        dgels_('N', sz, _n_dim + 1 - _fix_intercept, 1, a.data(), sz, prop_train.data() + start, sz, work.data(), work.size(), &info);
+            for(int ff = 0; ff < feats.size(); ++ff)
+            {
+                std::copy_n(feats[ff]->value_ptr() + start, sz, _D_train.data() + ff * sz);
+                std::copy_n(feats[ff]->value_ptr() + start, sz, a.data() + ff * sz);
+            }
 
-        _coefs.push_back(std::vector<double>(_n_dim + (!fix_intercept), 0.0));
+            dgels_('N', sz, _n_dim + 1 - _fix_intercept, 1, a.data(), sz, prop_train.data() + start, sz, work.data(), work.size(), &info);
 
-        std::copy_n(prop_train.begin() + start, _n_dim + 1 - _fix_intercept, _coefs.back().data());
-        dgemv_('N', sz, _n_dim + 1 - _fix_intercept, 1.0, _D_train.data(), sz, _coefs.back().data(), 1, 0.0, _prop_train_est.data() + start, 1);
-        std::transform(_prop_train_est.begin() + start, _prop_train_est.begin() + start + sz, _prop_train.data() + start, _train_error.data() + start, std::minus<double>());
+            _coefs.push_back(std::vector<double>(_n_dim + (!fix_intercept), 0.0));
 
-        start += sz;
-    }
+            std::copy_n(prop_train.begin() + start, _n_dim + 1 - _fix_intercept, _coefs.back().data());
+            dgemv_('N', sz, _n_dim + 1 - _fix_intercept, 1.0, _D_train.data(), sz, _coefs.back().data(), 1, 0.0, _prop_train_est.data() + start, 1);
+            std::transform(_prop_train_est.begin() + start, _prop_train_est.begin() + start + sz, _prop_train.data() + start, _train_error.data() + start, std::minus<double>());
 
-    start = 0;
-    int ii = 0;
-    for(auto& sz : _task_sizes_test)
-    {
-        if(sz > 0)
-        {
-            for(int ff = 0; ff < feats.size(); ++ff)
-                std::copy_n(feats[ff]->test_value().data() + start, sz, _D_test.data() + ff * sz);
+            start += sz;
+        }
 
-            std::fill_n(_D_test.data() + feats.size() * sz, sz, 1.0);
-            dgemv_('N', sz, _n_dim + 1 - _fix_intercept, 1.0, _D_test.data(), sz, _coefs[ii].data(), 1, 0.0, _prop_test_est.data() + start, 1);
-            std::transform(_prop_test_est.begin() + start, _prop_test_est.begin() + start + sz, _prop_test.data() + start, _test_error.data() + start, std::minus<double>());
+        start = 0;
+        int ii = 0;
+        for(auto& sz : _task_sizes_test)
+        {
+            if(sz > 0)
+            {
+                std::fill_n(_D_test.data(), _D_test.size(), 1.0);
+                for(int ff = 0; ff < feats.size(); ++ff)
+                    std::copy_n(feats[ff]->test_value().data() + start, sz, _D_test.data() + ff * sz);
+
+                dgemv_('N', sz, _n_dim + 1 - _fix_intercept, 1.0, _D_test.data(), sz, _coefs[ii].data(), 1, 0.0, _prop_test_est.data() + start, 1);
+                std::transform(_prop_test_est.begin() + start, _prop_test_est.begin() + start + sz, _prop_test.data() + start, _test_error.data() + start, std::minus<double>());
+            }
+            ++ii;
+            start += sz;
         }
-        ++ii;
-        start += sz;
     }
 }
 
@@ -125,6 +130,8 @@ std::vector<std::string> ModelRegressor::populate_model(std::string filename, bo
     std::string model_line;
     std::getline(file_stream, model_line);
 
+    _fix_intercept = (model_line.substr(0, 4).compare("# c0") != 0) && (model_line.substr(0, 9).compare("# exp(c0)") != 0);
+
     // Get the property unit and error
     std::string unit_line;
     std::string error_line;
@@ -173,20 +180,14 @@ std::vector<std::string> ModelRegressor::populate_model(std::string filename, bo
         if(train)
         {
             _coefs.push_back(std::vector<double>(n_dim + 1, 0.0));
-            std::transform(split_line.begin() + 1, split_line.end()-1, _coefs.back().data(), [](std::string s){return  std::stod(s);});
+            std::transform(split_line.begin() + 1, split_line.end()-1, _coefs.back().data(), [](std::string s){return std::stod(s);});
         }
         std::getline(file_stream, line);
     } while(line.substr(0, 14).compare("# Feature Rung") != 0);
 
-    if(_coefs.back().back() == 0.0)
-        _fix_intercept = true;
-    else
-        _fix_intercept = false;
-
-    _n_dim = n_dim;
-
+    _n_dim = n_dim + _fix_intercept;
     std::getline(file_stream, line);
-    for(int ff = 0; ff < n_dim; ++ff)
+    for(int ff = 0; ff < _n_dim; ++ff)
     {
         feature_expr.push_back(line.substr(6));
         std::getline(file_stream, line);
@@ -270,19 +271,38 @@ std::vector<std::string> ModelRegressor::populate_model(std::string filename, bo
 std::string ModelRegressor::toString() const
 {
     std::stringstream unit_rep;
-    unit_rep << "c0";
-    for(int ff = 0; ff < _feats.size(); ++ff)
-        unit_rep << " + a" << std::to_string(ff) << " * " << _feats[ff]->expr();
+    if(!_fix_intercept)
+    {
+        unit_rep << "c0";
+        for(int ff = 0; ff < _feats.size(); ++ff)
+            unit_rep << " + a" << std::to_string(ff) << " * " << _feats[ff]->expr();
+    }
+    else
+    {
+        unit_rep << "a0 * " << _feats[0]->expr();
+        for(int ff = 1; ff < _feats.size(); ++ff)
+            unit_rep << " + a" << std::to_string(ff) << " * " << _feats[ff]->expr();
+    }
     return unit_rep.str();
 }
 
 std::string ModelRegressor::toLatexString() const
 {
     std::stringstream unit_rep;
-    unit_rep << "$c_0";
-    for(int ff = 0; ff < _feats.size(); ++ff)
-        unit_rep << " + a_" << std::to_string(ff) << _feats[ff]->get_latex_expr("");
-    unit_rep << "$";
+    if(_fix_intercept)
+    {
+        unit_rep << "$a_0" << _feats[0]->get_latex_expr("");
+        for(int ff = 1; ff < _feats.size(); ++ff)
+            unit_rep << " + a_" << std::to_string(ff) << _feats[ff]->get_latex_expr("");
+        unit_rep << "$";
+    }
+    else
+    {
+        unit_rep << "$c_0";
+        for(int ff = 0; ff < _feats.size(); ++ff)
+            unit_rep << " + a_" << std::to_string(ff) << _feats[ff]->get_latex_expr("");
+        unit_rep << "$";
+    }
     return unit_rep.str();
 }
 
@@ -295,7 +315,9 @@ std::ostream& operator<< (std::ostream& outStream, const ModelRegressor& model)
 void ModelRegressor::to_file(std::string filename, bool train, std::vector<int> test_inds)
 {
     boost::filesystem::path p(filename.c_str());
-    boost::filesystem::create_directories(p.remove_filename());
+    boost::filesystem::path parent = p.remove_filename();
+    if(parent.string().size() > 0)
+        boost::filesystem::create_directories(parent);
 
     std::ofstream out_file_stream = std::ofstream();
     out_file_stream.open(filename);
@@ -310,7 +332,7 @@ void ModelRegressor::to_file(std::string filename, bool train, std::vector<int>
     out_file_stream << "# Coefficients" << std::endl;
     out_file_stream << std::setw(10) << std::left << "# Task";
 
-    for(int cc = 0; cc < _coefs[0].size() - (1 - _fix_intercept); ++cc)
+    for(int cc = 0; cc < _coefs[0].size() - (!_fix_intercept); ++cc)
         out_file_stream << std::setw(24) << " a" + std::to_string(cc);
 
     if(!_fix_intercept)
diff --git a/src/descriptor_identifier/Model/ModelRegressor.hpp b/src/descriptor_identifier/Model/ModelRegressor.hpp
index a12973fa59f44df977e475dd3fd85b3a15a6cd74..9f21ca7826db5809d1d7fbfc4d70c30a9ac3ff28 100644
--- a/src/descriptor_identifier/Model/ModelRegressor.hpp
+++ b/src/descriptor_identifier/Model/ModelRegressor.hpp
@@ -44,6 +44,11 @@ protected:
     std::vector<double> _test_error; //!< The error of the model (testing)
 
 public:
+    /**
+     * @brief Default Constructor
+     */
+    ModelRegressor();
+
     /**
      * @brief Constructor for the model
      *
@@ -54,7 +59,7 @@ public:
      * @param task_sizes_train Number of samples per task in the training data
      * @param task_sizes_test Number of samples per task in the test data
      */
-    ModelRegressor(std::string prop_label, Unit prop_unit, std::vector<double> prop_train, std::vector<double> prop_test, std::vector<model_node_ptr> feats, std::vector<int> task_sizes_train, std::vector<int> task_sizes_test, bool fix_intercept);
+    ModelRegressor(std::string prop_label, Unit prop_unit, std::vector<double> prop_train, std::vector<double> prop_test, std::vector<model_node_ptr> feats, std::vector<int> task_sizes_train, std::vector<int> task_sizes_test, bool fix_intercept, bool fill_vecs=true);
 
     // DocString: model_reg_init_str
     /**
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/abs/absolute_value.cpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/abs/absolute_value.cpp
index 58522a9ea4f5c15bde03c3fac6f796566c8f45c7..1147614ef5427df3b6cc63b9afdf9c9caf2eb40f 100644
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/abs/absolute_value.cpp
+++ b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/abs/absolute_value.cpp
@@ -6,8 +6,12 @@ void generateAbsNode(std::vector<node_ptr>& feat_list, node_ptr feat, int& feat_
     if((feat->type() == NODE_TYPE::ABS) || (feat->type() == NODE_TYPE::ABS_DIFF))
         return;
 
+    double* val_ptr = feat->value_ptr(feat->rung() + 1);
+    if(*std::min_element(val_ptr, val_ptr + feat->n_samp()) > 0.0)
+        return;
+
     int offset = feat->rung() + 1;
-    double* val_ptr = node_value_arrs::get_value_ptr(feat_ind, feat_ind, offset);
+    val_ptr = node_value_arrs::get_value_ptr(feat_ind, feat_ind, offset);
     allowed_op_funcs::abs(feat->n_samp(), feat->value_ptr(offset + 2), val_ptr);
 
     if((util_funcs::stand_dev(val_ptr, feat->n_samp()) < 1.0e-13) || std::any_of(val_ptr, val_ptr + feat->n_samp(), [&u_bound](double d){return !std::isfinite(d) || (std::abs(d) > u_bound);}) || (util_funcs::max_abs_val<double>(val_ptr, feat->n_samp()) < l_bound))
@@ -28,6 +32,11 @@ AbsNode::AbsNode(node_ptr feat, int feat_ind, double l_bound, double u_bound):
 {
     if((feat->type() == NODE_TYPE::ABS) || (feat->type() == NODE_TYPE::ABS_DIFF))
         throw InvalidFeatureException();
+
+    double* val_ptr = feat->value_ptr(rung() + 2);
+    if(*std::min_element(val_ptr, val_ptr + _n_samp) > 0.0)
+        throw InvalidFeatureException();
+
     set_value();
     if(is_nan() || is_const() || (util_funcs::max_abs_val<double>(value_ptr(), _n_samp) > u_bound) || (util_funcs::max_abs_val<double>(value_ptr(), _n_samp) < l_bound))
         throw InvalidFeatureException();
diff --git a/src/feature_creation/node/value_storage/nodes_value_containers.cpp b/src/feature_creation/node/value_storage/nodes_value_containers.cpp
index e0dc8bb47786f37f9915433f7e170c867a417122..cd6d214f2047f64bfde758a58b008f1229adc0ee 100644
--- a/src/feature_creation/node/value_storage/nodes_value_containers.cpp
+++ b/src/feature_creation/node/value_storage/nodes_value_containers.cpp
@@ -64,6 +64,8 @@ void node_value_arrs::resize_values_arr(int n_dims, int n_feat, bool use_temp)
     {
         TEMP_STORAGE_ARR = {};
         TEMP_STORAGE_REG = {};
+        TEMP_STORAGE_TEST_ARR = {};
+        TEMP_STORAGE_TEST_REG = {};
     }
 }
 
diff --git a/src/feature_creation/units/Unit.cpp b/src/feature_creation/units/Unit.cpp
index f96e5bd78feff445dd5f91768317075b2640e0e3..15a0726698719443d1d23f9eccedc9c55d6fb116 100644
--- a/src/feature_creation/units/Unit.cpp
+++ b/src/feature_creation/units/Unit.cpp
@@ -11,6 +11,7 @@ Unit::Unit(std::string unit_str) :
     _dct()
 {
     boost::ireplace_all(unit_str, " ", "");
+    boost::ireplace_all(unit_str, "Unitless", "");
 
     std::vector<std::string> split_unit;
     boost::algorithm::split(split_unit, unit_str, boost::algorithm::is_any_of("*/"));
@@ -42,12 +43,13 @@ Unit::Unit(std::string unit_str) :
 
     for(int oo = 0; oo < ops.size(); ++oo)
     {
-        std::vector<std::string> get_power;
-        boost::algorithm::split(get_power, split_unit[oo], boost::algorithm::is_any_of("^"));
+        std::vector<std::string> get_power = str_utils::split_string_trim(split_unit[oo], "^");
+        if(get_power[0][0] == '1')
+            continue;
         if(get_power.size() > 1)
-            _dct[get_power[0]] = ops[oo] * std::stod(get_power[1]);
+            _dct[get_power[0]] += ops[oo] * std::stod(get_power[1]);
         else
-            _dct[get_power[0]] = ops[oo];
+            _dct[get_power[0]] += ops[oo];
     }
 }
 
@@ -180,14 +182,12 @@ Unit Unit::inverse()
     return Unit(to_out);
 }
 
-bool Unit::equal(Unit unit_2)
+bool Unit::equal(Unit unit_2) const
 {
     for(auto& el : unit_2.dct())
     {
         if((_dct.count(el.first) == 0) && (el.second != 0))
             return false;
-        else if(_dct[el.first] != el.second)
-            return false;
     }
 
     for(auto& el : _dct)
diff --git a/src/feature_creation/units/Unit.hpp b/src/feature_creation/units/Unit.hpp
index 9dcf29d34b950a33193b2c89807d8152fbca146e..1414adc83d93fd6e7e01ebf14f079d5e43d4b17e 100644
--- a/src/feature_creation/units/Unit.hpp
+++ b/src/feature_creation/units/Unit.hpp
@@ -22,6 +22,8 @@
 #include <boost/serialization/map.hpp>
 #include <boost/serialization/string.hpp>
 
+#include <utils/string_utils.hpp>
+
 using StringRange = boost::iterator_range<std::string::const_iterator>;
 
 // DocString: cls_unit
@@ -146,7 +148,7 @@ public:
      * @param unit_2 The unit to compare against
      * @return True if unit_2 equals this unit
      */
-    bool equal(Unit unit_2);
+    bool equal(Unit unit_2) const;
 
     // DocString: unit_eq
     /**
@@ -155,7 +157,7 @@ public:
      * @param unit_2 The unit to compare against
      * @return True if unit_2 equals this unit
      */
-    inline bool operator== (Unit unit_2){return equal(unit_2);}
+    inline bool operator== (Unit unit_2) const {return equal(unit_2);}
 
     // DocString: unit_neq
     /**
@@ -164,7 +166,7 @@ public:
      * @param unit_2 The unit to compare against
      * @return False if unit_2 equals this unit
      */
-    inline bool operator!= (Unit unit_2){return !equal(unit_2);}
+    inline bool operator!= (Unit unit_2) const {return !equal(unit_2);}
 
     /**
      * @brief The dictionary
@@ -181,4 +183,8 @@ public:
  */
 std::ostream& operator<< (std::ostream& outStream, const Unit& unit);
 
+// inline bool operator==(const Unit& u1, const Unit& u2)
+// {
+//     return u1.equal(u2);
+// }
 #endif
diff --git a/src/python/bindings_docstring_keyed.cpp b/src/python/bindings_docstring_keyed.cpp
index bbbc86c3c83370c0e9af6f763e35c2a1befe8728..10e6f5960888de14a782b216093f4c09c85af286 100644
--- a/src/python/bindings_docstring_keyed.cpp
+++ b/src/python/bindings_docstring_keyed.cpp
@@ -357,7 +357,7 @@ void sisso::descriptor_identifier::registerModel()
         .add_property("prop_train", &Model::prop_train, "@DocString_model_prop_train@")
         .add_property("prop_test", &Model::prop_test, "@DocString_model_prop_test@")
         .add_property("feats", &Model::feats, "@DocString_model_feats@")
-        .add_property("coefs", &Model::coefs, "@DocString_model_coefs@")
+        .add_property("coefs", &Model::coefs_py, "@DocString_model_coefs@")
         .add_property("prop_unit", &Model::prop_unit, "@DocString_model_prop_unit@")
         .add_property("prop_label", &Model::prop_label, "@DocString_model_prop_label@")
         .add_property("task_size_train", &Model::task_sizes_train, "@DocString_model_task_sizes_train")
diff --git a/src/python/postprocess/utils.py b/src/python/postprocess/utils.py
index c08b7e431bdfd9460a56ced5f1e54f29f3a23667..f0e8876da55bd592a70b3a2536fc44e7c5407c48 100644
--- a/src/python/postprocess/utils.py
+++ b/src/python/postprocess/utils.py
@@ -36,7 +36,7 @@ def load_model(train_file, test_file=None):
             return ModelRegressor(train_file, test_file)
         else:
             return ModelRegressor(train_file)
-    elif model_line[:4] == "c0 *":
+    elif model_line[:9] == "exp(c0) *":
         if test_file:
             return ModelLogRegressor(train_file, test_file)
         else:
diff --git a/tests/googletest/CMakeLists.txt b/tests/googletest/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..a1675e225c43a6c63b76ba6e93154379cbe169c7
--- /dev/null
+++ b/tests/googletest/CMakeLists.txt
@@ -0,0 +1,16 @@
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated-declarations")
+set(CMAKE_INSTALL_RPATH ${Boost_LIBRARY_DIRS};${LAPACK_DIR};${MPI_DIR};${COIN_CLP_LIBRARY_DIRS};${COIN_UTILS_LIBRARY_DIRS};${GTEST_LIBRARY_DIRS};${NLOPT_LIBRARY_DIRS};${FMT_LIBRARY_DIRS};${CMAKE_CURRENT_LIST_DIR}/../../lib/)
+
+file(GLOB_RECURSE SISSO_TEST_SOURCES *.cc)
+add_executable(sisso_test  ${SISSO_TEST_SOURCES})
+
+set_target_properties(sisso_test
+    PROPERTIES
+    ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
+    LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
+    RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
+)
+
+target_link_libraries(sisso_test libsisso ${LAPACK_LIBRARIES} ${MPI_LIBRARIES} -Wl,--rpath=${GTEST_LIBRARY_DIRS} -Wl,--rpath=${Boost_LIB_DIR} -Wl,--rpath=${LAPACK_DIR} ${Boost_LIBRARIES} ${COIN_CLP_LIBRARIES} ${OPENMP_LIBRARIES} ${GTEST_BOTH_LIBRARIES} ${FMT_LIBRARIES} ${NLOPT_LIBRARIES})
+message(STATUS "${LAPACK_LIBRARIES} ${MPI_LIBRARIES} -Wl,--rpath=${GTEST_LIBRARY_DIRS} -Wl,--rpath=${Boost_LIB_DIR} -Wl,--rpath=${LAPACK_DIR} ${Boost_LIBRARIES} ${COIN_CLP_LIBRARIES} ${OPENMP_LIBRARIES} ${GTEST_BOTH_LIBRARIES} ${FMT_LIBRARIES}")
+install(TARGETS sisso_test DESTINATION ${CMAKE_CURRENT_LIST_DIR})
diff --git a/tests/googletest/descriptor_identification/model/test_model_classifier.cc b/tests/googletest/descriptor_identification/model/test_model_classifier.cc
new file mode 100644
index 0000000000000000000000000000000000000000..927d60db068bc90021e2dc6a229a3b2ccd73e5a3
--- /dev/null
+++ b/tests/googletest/descriptor_identification/model/test_model_classifier.cc
@@ -0,0 +1,82 @@
+#include <descriptor_identifier/Model/ModelClassifier.hpp>
+#include <boost/filesystem.hpp>
+#include "gtest/gtest.h"
+
+namespace
+{
+    class ModelClassifierTests : public ::testing::Test
+    {
+    protected:
+        void SetUp() override
+        {
+            _task_sizes_train = {12};
+            _task_sizes_test = {2};
+
+            std::vector<double> value_1 = {1.0, -2.0, 3.0, -4.0, 5.0, -6.0, 7.0, -8.0, 9.0, -10.0, 0.1, -0.1};
+            std::vector<double> test_value_1 = {10.0, -7.0};
+
+            model_node_ptr feat_1 = std::make_shared<ModelNode>(0, 0, "A", "$A$", "0", value_1, test_value_1, Unit("m"));
+
+            _features = {feat_1};
+
+            _prop = {0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0};
+            _prop_test = {0.0, 1.0};
+        }
+        std::vector<int> _task_sizes_train;
+        std::vector<int> _task_sizes_test;
+
+        std::vector<double> _prop;
+        std::vector<double> _prop_test;
+
+        std::vector<model_node_ptr> _features;
+    };
+
+    TEST_F(ModelClassifierTests, NodesTest)
+    {
+        ModelClassifier model(
+            "Property",
+            Unit("m"),
+            _prop,
+            _prop_test,
+            _features,
+            _task_sizes_train,
+            _task_sizes_test,
+            false
+        );
+        EXPECT_STREQ(model.toString().c_str(), "[A]");
+        EXPECT_EQ(model.n_convex_overlap_train(), 0);
+        EXPECT_EQ(model.n_convex_overlap_test(), 0);
+        EXPECT_EQ(model.n_svm_misclassified_train(), 0);
+        EXPECT_EQ(model.n_svm_misclassified_test(), 0);
+        EXPECT_LT(model.percent_train_error(), 1.0e-10);
+        EXPECT_LT(model.percent_test_error(), 1.0e-10);
+
+        EXPECT_EQ(model.n_samp_train(), 12);
+        EXPECT_EQ(model.n_samp_test(), 2);
+        EXPECT_EQ(model.n_dim(), 1);
+        EXPECT_EQ(model.prop_unit(), Unit("m"));
+
+        model.to_file("train_class_mods.dat", true, {5, 11});
+        model.to_file("test_class_mods.dat", false, {5, 11});
+    }
+
+    TEST_F(ModelClassifierTests, FileTest)
+    {
+        ModelClassifier model("train_class_mods.dat", "test_class_mods.dat");
+        EXPECT_STREQ(model.toString().c_str(), "[A]");
+        EXPECT_EQ(model.n_convex_overlap_train(), 0);
+        EXPECT_EQ(model.n_convex_overlap_test(), 0);
+        EXPECT_EQ(model.n_svm_misclassified_train(), 0);
+        EXPECT_EQ(model.n_svm_misclassified_test(), 0);
+        EXPECT_LT(model.percent_train_error(), 1.0e-10);
+        EXPECT_LT(model.percent_test_error(), 1.0e-10);
+
+        EXPECT_EQ(model.n_samp_train(), 12);
+        EXPECT_EQ(model.n_samp_test(), 2);
+        EXPECT_EQ(model.n_dim(), 1);
+        EXPECT_EQ(model.prop_unit(), Unit("m"));
+
+        boost::filesystem::remove("train_class_mods.dat");
+        boost::filesystem::remove("test_class_mods.dat");
+    }
+}
diff --git a/tests/googletest/descriptor_identification/model/test_model_log_regressor.cc b/tests/googletest/descriptor_identification/model/test_model_log_regressor.cc
new file mode 100644
index 0000000000000000000000000000000000000000..b4ab2054a88729d20584a9f2cc4a92cf7d3242f7
--- /dev/null
+++ b/tests/googletest/descriptor_identification/model/test_model_log_regressor.cc
@@ -0,0 +1,196 @@
+#include <descriptor_identifier/Model/ModelLogRegressor.hpp>
+#include <boost/filesystem.hpp>
+#include "gtest/gtest.h"
+
+namespace
+{
+    class ModelLogRegssorTests : public ::testing::Test
+    {
+    protected:
+        void SetUp() override
+        {
+            _task_sizes_train = {10};
+            _task_sizes_test = {2};
+
+            std::vector<double> value_1 = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0};
+            std::vector<double> test_value_1 = {1.0, 7.0};
+
+            std::vector<double> value_2 = {1.10, 2.20, 3.10, 4.20, 5.10, 6.20, 7.10, 8.20, 9.10, 10.20};
+            std::vector<double> test_value_2 = {2.10, 4.80};
+
+            model_node_ptr feat_1 = std::make_shared<ModelNode>(0, 0, "A", "$A$", "0", value_1, test_value_1, Unit("m"));
+            model_node_ptr feat_2 = std::make_shared<ModelNode>(1, 0, "B", "$B$", "1", value_2, test_value_2, Unit("m"));
+
+            _features = {feat_1, feat_2};
+
+            _prop = std::vector<double>(10, 0.0);
+            _prop_test = std::vector<double>(2, 0.0);
+
+            std::transform(value_1.begin(), value_1.end(), value_2.begin(), _prop.begin(), [](double v1, double v2){return 0.001 * std::pow(v1, 0.1) * std::pow(v2, -2.1);});
+            std::transform(test_value_1.begin(), test_value_1.end(), test_value_2.begin(), _prop_test.begin(), [](double v1, double v2){return 0.001 * std::pow(v1, 0.1) * std::pow(v2, -2.1);});
+        }
+        std::vector<int> _task_sizes_train;
+        std::vector<int> _task_sizes_test;
+
+        std::vector<double> _prop;
+        std::vector<double> _prop_test;
+
+        std::vector<model_node_ptr> _features;
+    };
+
+    TEST_F(ModelLogRegssorTests, FixInterceptFalseTest)
+    {
+        ModelLogRegressor model(
+            "Property",
+            Unit("m"),
+            _prop,
+            _prop_test,
+            _features,
+            _task_sizes_train,
+            _task_sizes_test,
+            false
+        );
+        EXPECT_STREQ(model.toString().c_str(), "exp(c0) * (A)^a0 * (B)^a1");
+        EXPECT_LT(model.rmse(), 1e-10);
+        EXPECT_LT(model.test_rmse(), 1e-10);
+        EXPECT_LT(model.max_ae(), 1e-10);
+        EXPECT_LT(model.test_max_ae(), 1e-10);
+        EXPECT_LT(model.mae(), 1e-10);
+        EXPECT_LT(model.test_mae(), 1e-10);
+        EXPECT_LT(model.mape(), 1e-10);
+        EXPECT_LT(model.test_mape(), 1e-10);
+        EXPECT_LT(model.percentile_25_ae(), 1e-10);
+        EXPECT_LT(model.percentile_25_test_ae(), 1e-10);
+        EXPECT_LT(model.percentile_50_ae(), 1e-10);
+        EXPECT_LT(model.percentile_50_test_ae(), 1e-10);
+        EXPECT_LT(model.percentile_75_ae(), 1e-10);
+        EXPECT_LT(model.percentile_75_test_ae(), 1e-10);
+        EXPECT_LT(model.percentile_95_ae(), 1e-10);
+        EXPECT_LT(model.percentile_95_test_ae(), 1e-10);
+
+        EXPECT_EQ(model.n_samp_train(), 10);
+        EXPECT_EQ(model.n_samp_test(), 2);
+        EXPECT_EQ(model.n_dim(), 2);
+        EXPECT_EQ(model.prop_unit(), Unit("m"));
+
+        EXPECT_LT(std::abs(model.coefs()[0][0] - 0.1), 1e-10);
+        EXPECT_LT(std::abs(model.coefs()[0][1] + 2.1), 1e-10);
+        EXPECT_LT(std::abs(model.coefs()[0][2] - std::log(0.001)), 1e-10);
+
+        model.to_file("train_false.dat", true, {5, 11});
+        model.to_file("test_false.dat", false, {5, 11});
+    }
+
+    TEST_F(ModelLogRegssorTests, FixInterceptFalseFileTest)
+    {
+        ModelLogRegressor model("train_false.dat", "test_false.dat");
+        EXPECT_STREQ(model.toString().c_str(), "exp(c0) * (A)^a0 * (B)^a1");
+        EXPECT_LT(model.rmse(), 1e-10);
+        EXPECT_LT(model.test_rmse(), 1e-10);
+        EXPECT_LT(model.max_ae(), 1e-10);
+        EXPECT_LT(model.test_max_ae(), 1e-10);
+        EXPECT_LT(model.mae(), 1e-10);
+        EXPECT_LT(model.test_mae(), 1e-10);
+        EXPECT_LT(model.mape(), 1e-10);
+        EXPECT_LT(model.test_mape(), 1e-10);
+        EXPECT_LT(model.percentile_25_ae(), 1e-10);
+        EXPECT_LT(model.percentile_25_test_ae(), 1e-10);
+        EXPECT_LT(model.percentile_50_ae(), 1e-10);
+        EXPECT_LT(model.percentile_50_test_ae(), 1e-10);
+        EXPECT_LT(model.percentile_75_ae(), 1e-10);
+        EXPECT_LT(model.percentile_75_test_ae(), 1e-10);
+        EXPECT_LT(model.percentile_95_ae(), 1e-10);
+        EXPECT_LT(model.percentile_95_test_ae(), 1e-10);
+
+        EXPECT_EQ(model.n_samp_train(), 10);
+        EXPECT_EQ(model.n_samp_test(), 2);
+        EXPECT_EQ(model.n_dim(), 2);
+        EXPECT_EQ(model.prop_unit(), Unit("m"));
+
+        EXPECT_LT(std::abs(model.coefs()[0][0] - 0.1), 1e-10);
+        EXPECT_LT(std::abs(model.coefs()[0][1] + 2.1), 1e-10);
+        EXPECT_LT(std::abs(model.coefs()[0][2] - std::log(0.001)), 1e-10);
+
+        boost::filesystem::remove("train_false.dat");
+        boost::filesystem::remove("test_false.dat");
+    }
+
+    TEST_F(ModelLogRegssorTests, FixInterceptTrueTest)
+    {
+        std::transform(_features[0]->value_ptr(), _features[0]->value_ptr() + 10, _features[1]->value_ptr(), _prop.begin(), [](double v1, double v2){return std::pow(v1, 0.1) * std::pow(v2, -2.1);});
+        std::transform(_features[0]->test_value_ptr(), _features[0]->test_value_ptr() + 2, _features[1]->test_value_ptr(), _prop_test.begin(), [](double v1, double v2){return std::pow(v1, 0.1) * std::pow(v2, -2.1);});
+
+        ModelLogRegressor model(
+            "Property",
+            Unit("m"),
+            _prop,
+            _prop_test,
+            _features,
+            _task_sizes_train,
+            _task_sizes_test,
+            true
+        );
+        EXPECT_STREQ(model.toString().c_str(), "(A)^a0 * (B)^a1");
+
+        EXPECT_LT(model.rmse(), 1e-10);
+        EXPECT_LT(model.test_rmse(), 1e-10);
+        EXPECT_LT(model.max_ae(), 1e-10);
+        EXPECT_LT(model.test_max_ae(), 1e-10);
+        EXPECT_LT(model.mae(), 1e-10);
+        EXPECT_LT(model.test_mae(), 1e-10);
+        EXPECT_LT(model.mape(), 1e-10);
+        EXPECT_LT(model.test_mape(), 1e-10);
+        EXPECT_LT(model.percentile_25_ae(), 1e-10);
+        EXPECT_LT(model.percentile_25_test_ae(), 1e-10);
+        EXPECT_LT(model.percentile_50_ae(), 1e-10);
+        EXPECT_LT(model.percentile_50_test_ae(), 1e-10);
+        EXPECT_LT(model.percentile_75_ae(), 1e-10);
+        EXPECT_LT(model.percentile_75_test_ae(), 1e-10);
+        EXPECT_LT(model.percentile_95_ae(), 1e-10);
+        EXPECT_LT(model.percentile_95_test_ae(), 1e-10);
+
+        EXPECT_EQ(model.n_samp_train(), 10);
+        EXPECT_EQ(model.n_samp_test(), 2);
+        EXPECT_EQ(model.n_dim(), 2);
+        EXPECT_EQ(model.prop_unit(), Unit("m"));
+
+        EXPECT_LT(std::abs(model.coefs()[0][0] - 0.1), 1e-10);
+        EXPECT_LT(std::abs(model.coefs()[0][1] + 2.1), 1e-10);
+
+        model.to_file("train_true.dat", true, {5, 11});
+        model.to_file("test_true.dat", false, {5, 11});
+    }
+
+    TEST_F(ModelLogRegssorTests, FixInterceptTrueFileTest)
+    {
+        ModelLogRegressor model("train_true.dat", "test_true.dat");
+        EXPECT_STREQ(model.toString().c_str(), "(A)^a0 * (B)^a1");
+        EXPECT_LT(model.rmse(), 1e-10);
+        EXPECT_LT(model.test_rmse(), 1e-10);
+        EXPECT_LT(model.max_ae(), 1e-10);
+        EXPECT_LT(model.test_max_ae(), 1e-10);
+        EXPECT_LT(model.mae(), 1e-10);
+        EXPECT_LT(model.test_mae(), 1e-10);
+        EXPECT_LT(model.mape(), 1e-10);
+        EXPECT_LT(model.test_mape(), 1e-10);
+        EXPECT_LT(model.percentile_25_ae(), 1e-10);
+        EXPECT_LT(model.percentile_25_test_ae(), 1e-10);
+        EXPECT_LT(model.percentile_50_ae(), 1e-10);
+        EXPECT_LT(model.percentile_50_test_ae(), 1e-10);
+        EXPECT_LT(model.percentile_75_ae(), 1e-10);
+        EXPECT_LT(model.percentile_75_test_ae(), 1e-10);
+        EXPECT_LT(model.percentile_95_ae(), 1e-10);
+        EXPECT_LT(model.percentile_95_test_ae(), 1e-10);
+
+        EXPECT_EQ(model.n_samp_train(), 10);
+        EXPECT_EQ(model.n_samp_test(), 2);
+        EXPECT_EQ(model.n_dim(), 2);
+        EXPECT_EQ(model.prop_unit(), Unit("m"));
+
+        EXPECT_LT(std::abs(model.coefs()[0][0] - 0.1), 1e-10);
+        EXPECT_LT(std::abs(model.coefs()[0][1] + 2.1), 1e-10);
+
+        // boost::filesystem::remove("train_true.dat");
+        // boost::filesystem::remove("test_true.dat");
+    }
+}
diff --git a/tests/googletest/descriptor_identification/model/test_model_regressor.cc b/tests/googletest/descriptor_identification/model/test_model_regressor.cc
new file mode 100644
index 0000000000000000000000000000000000000000..f76fc5bafe7106f85c095ef1ba17c72f77cab60e
--- /dev/null
+++ b/tests/googletest/descriptor_identification/model/test_model_regressor.cc
@@ -0,0 +1,211 @@
+#include <descriptor_identifier/Model/ModelRegressor.hpp>
+#include <boost/filesystem.hpp>
+#include "gtest/gtest.h"
+
+namespace
+{
+    class ModelRegssorTests : public ::testing::Test
+    {
+    protected:
+        void SetUp() override
+        {
+            _task_sizes_train = {5, 5};
+            _task_sizes_test = {1, 1};
+
+            std::vector<double> value_1 = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0};
+            std::vector<double> test_value_1 = {1.0, 7.0};
+
+            std::vector<double> value_2 = {1.10, 2.20, 3.10, 4.20, 5.10, 6.20, 7.10, 8.20, 9.10, 10.20};
+            std::vector<double> test_value_2 = {2.10, 4.80};
+
+            model_node_ptr feat_1 = std::make_shared<ModelNode>(0, 0, "A", "$A$", "0", value_1, test_value_1, Unit("m"));
+            model_node_ptr feat_2 = std::make_shared<ModelNode>(1, 0, "B", "$B$", "1", value_2, test_value_2, Unit("m"));
+
+            _features = {feat_1, feat_2};
+
+            _prop = std::vector<double>(10, 0.0);
+            _prop_test = std::vector<double>(2, 0.0);
+
+            std::transform(value_1.begin(), value_1.begin() + 5, value_2.begin(), _prop.begin(), [](double v1, double v2){return 0.001 + v1 + v2;});
+            std::transform(value_1.begin() + 5, value_1.end(), value_2.begin() + 5, _prop.begin() + 5, [](double v1, double v2){return -6.5 + 1.25 * v1 - 0.4 * v2;});
+
+            std::transform(test_value_1.begin(), test_value_1.begin() + 1, test_value_2.begin(), _prop_test.begin(), [](double v1, double v2){return 0.001 + v1 + v2;});
+            std::transform(test_value_1.begin() + 1, test_value_1.end(), test_value_2.begin() + 1, _prop_test.begin() + 1, [](double v1, double v2){return -6.5 + 1.25 * v1 - 0.4 * v2;});
+        }
+        std::vector<int> _task_sizes_train;
+        std::vector<int> _task_sizes_test;
+
+        std::vector<double> _prop;
+        std::vector<double> _prop_test;
+
+        std::vector<model_node_ptr> _features;
+    };
+
+    TEST_F(ModelRegssorTests, FixInterceptFalseTest)
+    {
+        ModelRegressor model(
+            "Property",
+            Unit("m"),
+            _prop,
+            _prop_test,
+            _features,
+            _task_sizes_train,
+            _task_sizes_test,
+            false
+        );
+        EXPECT_STREQ(model.toString().c_str(), "c0 + a0 * A + a1 * B");
+        EXPECT_LT(model.rmse(), 1e-10);
+        EXPECT_LT(model.test_rmse(), 1e-10);
+        EXPECT_LT(model.max_ae(), 1e-10);
+        EXPECT_LT(model.test_max_ae(), 1e-10);
+        EXPECT_LT(model.mae(), 1e-10);
+        EXPECT_LT(model.test_mae(), 1e-10);
+        EXPECT_LT(model.mape(), 1e-10);
+        EXPECT_LT(model.test_mape(), 1e-10);
+        EXPECT_LT(model.percentile_25_ae(), 1e-10);
+        EXPECT_LT(model.percentile_25_test_ae(), 1e-10);
+        EXPECT_LT(model.percentile_50_ae(), 1e-10);
+        EXPECT_LT(model.percentile_50_test_ae(), 1e-10);
+        EXPECT_LT(model.percentile_75_ae(), 1e-10);
+        EXPECT_LT(model.percentile_75_test_ae(), 1e-10);
+        EXPECT_LT(model.percentile_95_ae(), 1e-10);
+        EXPECT_LT(model.percentile_95_test_ae(), 1e-10);
+
+        EXPECT_EQ(model.n_samp_train(), 10);
+        EXPECT_EQ(model.n_samp_test(), 2);
+        EXPECT_EQ(model.n_dim(), 2);
+        EXPECT_EQ(model.prop_unit(), Unit("m"));
+
+        EXPECT_LT(std::abs(model.coefs()[0][0] - 1.0), 1e-10);
+        EXPECT_LT(std::abs(model.coefs()[0][1] - 1.0), 1e-10);
+        EXPECT_LT(std::abs(model.coefs()[0][2] - 0.001), 1e-10);
+        EXPECT_LT(std::abs(model.coefs()[1][0] - 1.25), 1e-10);
+        EXPECT_LT(std::abs(model.coefs()[1][1] + 0.4), 1e-10);
+        EXPECT_LT(std::abs(model.coefs()[1][2] + 6.5), 1e-10);
+
+        model.to_file("train_false.dat", true, {5, 11});
+        model.to_file("test_false.dat", false, {5, 11});
+    }
+
+    TEST_F(ModelRegssorTests, FixInterceptFalseFileTest)
+    {
+        ModelRegressor model("train_false.dat", "test_false.dat");
+        EXPECT_STREQ(model.toString().c_str(), "c0 + a0 * A + a1 * B");
+        EXPECT_LT(model.rmse(), 1e-10);
+        EXPECT_LT(model.test_rmse(), 1e-10);
+        EXPECT_LT(model.max_ae(), 1e-10);
+        EXPECT_LT(model.test_max_ae(), 1e-10);
+        EXPECT_LT(model.mae(), 1e-10);
+        EXPECT_LT(model.test_mae(), 1e-10);
+        EXPECT_LT(model.mape(), 1e-10);
+        EXPECT_LT(model.test_mape(), 1e-10);
+        EXPECT_LT(model.percentile_25_ae(), 1e-10);
+        EXPECT_LT(model.percentile_25_test_ae(), 1e-10);
+        EXPECT_LT(model.percentile_50_ae(), 1e-10);
+        EXPECT_LT(model.percentile_50_test_ae(), 1e-10);
+        EXPECT_LT(model.percentile_75_ae(), 1e-10);
+        EXPECT_LT(model.percentile_75_test_ae(), 1e-10);
+        EXPECT_LT(model.percentile_95_ae(), 1e-10);
+        EXPECT_LT(model.percentile_95_test_ae(), 1e-10);
+
+        EXPECT_EQ(model.n_samp_train(), 10);
+        EXPECT_EQ(model.n_samp_test(), 2);
+        EXPECT_EQ(model.n_dim(), 2);
+        EXPECT_EQ(model.prop_unit(), Unit("m"));
+
+        EXPECT_LT(std::abs(model.coefs()[0][0] - 1.0), 1e-10);
+        EXPECT_LT(std::abs(model.coefs()[0][1] - 1.0), 1e-10);
+        EXPECT_LT(std::abs(model.coefs()[0][2] - 0.001), 1e-10);
+        EXPECT_LT(std::abs(model.coefs()[1][0] - 1.25), 1e-10);
+        EXPECT_LT(std::abs(model.coefs()[1][1] + 0.4), 1e-10);
+        EXPECT_LT(std::abs(model.coefs()[1][2] + 6.5), 1e-10);
+
+        boost::filesystem::remove("train_false.dat");
+        boost::filesystem::remove("test_false.dat");
+    }
+
+    TEST_F(ModelRegssorTests, FixInterceptTrueTest)
+    {
+        std::transform(_features[0]->value_ptr(), _features[0]->value_ptr() + 5, _features[1]->value_ptr(), _prop.begin(), [](double v1, double v2){return v1 + v2;});
+        std::transform(_features[0]->value_ptr() + 5, _features[0]->value_ptr() + 10, _features[1]->value_ptr() + 5, _prop.begin() + 5, [](double v1, double v2){return 1.25 * v1 - 0.4 * v2;});
+
+        std::transform(_features[0]->test_value_ptr(), _features[0]->test_value_ptr() + 1, _features[1]->test_value_ptr(), _prop_test.begin(), [](double v1, double v2){return v1 + v2;});
+        std::transform(_features[0]->test_value_ptr() + 1, _features[0]->test_value_ptr() + 2, _features[1]->test_value_ptr() + 1, _prop_test.begin() + 1, [](double v1, double v2){return 1.25 * v1 - 0.4 * v2;});
+
+        ModelRegressor model(
+            "Property",
+            Unit("m"),
+            _prop,
+            _prop_test,
+            _features,
+            _task_sizes_train,
+            _task_sizes_test,
+            true
+        );
+        EXPECT_STREQ(model.toString().c_str(), "a0 * A + a1 * B");
+        EXPECT_LT(model.rmse(), 1e-10);
+        EXPECT_LT(model.test_rmse(), 1e-10);
+        EXPECT_LT(model.max_ae(), 1e-10);
+        EXPECT_LT(model.test_max_ae(), 1e-10);
+        EXPECT_LT(model.mae(), 1e-10);
+        EXPECT_LT(model.test_mae(), 1e-10);
+        EXPECT_LT(model.mape(), 1e-10);
+        EXPECT_LT(model.test_mape(), 1e-10);
+        EXPECT_LT(model.percentile_25_ae(), 1e-10);
+        EXPECT_LT(model.percentile_25_test_ae(), 1e-10);
+        EXPECT_LT(model.percentile_50_ae(), 1e-10);
+        EXPECT_LT(model.percentile_50_test_ae(), 1e-10);
+        EXPECT_LT(model.percentile_75_ae(), 1e-10);
+        EXPECT_LT(model.percentile_75_test_ae(), 1e-10);
+        EXPECT_LT(model.percentile_95_ae(), 1e-10);
+        EXPECT_LT(model.percentile_95_test_ae(), 1e-10);
+
+        EXPECT_EQ(model.n_samp_train(), 10);
+        EXPECT_EQ(model.n_samp_test(), 2);
+        EXPECT_EQ(model.n_dim(), 2);
+        EXPECT_EQ(model.prop_unit(), Unit("m"));
+
+        EXPECT_LT(std::abs(model.coefs()[0][0] - 1.0), 1e-10);
+        EXPECT_LT(std::abs(model.coefs()[0][1] - 1.0), 1e-10);
+        EXPECT_LT(std::abs(model.coefs()[1][0] - 1.25), 1e-10);
+        EXPECT_LT(std::abs(model.coefs()[1][1] + 0.4), 1e-10);
+
+        model.to_file("train_true.dat", true, {5, 11});
+        model.to_file("test_true.dat", false, {5, 11});
+    }
+
+    TEST_F(ModelRegssorTests, FixInterceptTrueFileTest)
+    {
+        ModelRegressor model("train_true.dat", "test_true.dat");
+        EXPECT_STREQ(model.toString().c_str(), "a0 * A + a1 * B");
+        EXPECT_LT(model.rmse(), 1e-10);
+        EXPECT_LT(model.test_rmse(), 1e-10);
+        EXPECT_LT(model.max_ae(), 1e-10);
+        EXPECT_LT(model.test_max_ae(), 1e-10);
+        EXPECT_LT(model.mae(), 1e-10);
+        EXPECT_LT(model.test_mae(), 1e-10);
+        EXPECT_LT(model.mape(), 1e-10);
+        EXPECT_LT(model.test_mape(), 1e-10);
+        EXPECT_LT(model.percentile_25_ae(), 1e-10);
+        EXPECT_LT(model.percentile_25_test_ae(), 1e-10);
+        EXPECT_LT(model.percentile_50_ae(), 1e-10);
+        EXPECT_LT(model.percentile_50_test_ae(), 1e-10);
+        EXPECT_LT(model.percentile_75_ae(), 1e-10);
+        EXPECT_LT(model.percentile_75_test_ae(), 1e-10);
+        EXPECT_LT(model.percentile_95_ae(), 1e-10);
+        EXPECT_LT(model.percentile_95_test_ae(), 1e-10);
+
+        EXPECT_EQ(model.n_samp_train(), 10);
+        EXPECT_EQ(model.n_samp_test(), 2);
+        EXPECT_EQ(model.n_dim(), 2);
+        EXPECT_EQ(model.prop_unit(), Unit("m"));
+
+        EXPECT_LT(std::abs(model.coefs()[0][0] - 1.0), 1e-10);
+        EXPECT_LT(std::abs(model.coefs()[0][1] - 1.0), 1e-10);
+        EXPECT_LT(std::abs(model.coefs()[1][0] - 1.25), 1e-10);
+        EXPECT_LT(std::abs(model.coefs()[1][1] + 0.4), 1e-10);
+
+        boost::filesystem::remove("train_true.dat");
+        boost::filesystem::remove("test_true.dat");
+    }
+}
diff --git a/tests/googletest/descriptor_identification/sisso_regressor/test_sisso_classifier.cc b/tests/googletest/descriptor_identification/sisso_regressor/test_sisso_classifier.cc
new file mode 100644
index 0000000000000000000000000000000000000000..62a777ce45cd2321cf27db81fb72ff7e52277044
--- /dev/null
+++ b/tests/googletest/descriptor_identification/sisso_regressor/test_sisso_classifier.cc
@@ -0,0 +1,189 @@
+#include <descriptor_identifier/SISSO_DI/SISSOClassifier.hpp>
+#include <boost/filesystem.hpp>
+#include "gtest/gtest.h"
+#include <random>
+
+namespace
+{
+    class SISSOClassifierTests : public ::testing::Test
+    {
+    protected:
+        void SetUp() override
+        {
+            allowed_op_maps::set_node_maps();
+            node_value_arrs::initialize_d_matrix_arr();
+            mpi_setup::init_mpi_env();
+
+            node_value_arrs::initialize_values_arr(80, 20, 3);
+
+            _task_sizes_train = {80};
+            _task_sizes_test = {20};
+            _leave_out_inds = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+
+            std::vector<double> value_1(80, 0.0);
+            std::vector<double> value_2(80, 0.0);
+            std::vector<double> value_3(80, 0.0);
+
+            std::vector<double> test_value_1(20, 0.0);
+            std::vector<double> test_value_2(20, 0.0);
+            std::vector<double> test_value_3(20, 0.0);
+
+            std::default_random_engine generator;
+            std::uniform_real_distribution<double> distribution_3(-1.0, 1.0);
+            std::uniform_real_distribution<double> distribution_12_pos(0.1, 1.0);
+            std::uniform_real_distribution<double> distribution_12_neg(-1.0, -0.1);
+
+
+            for(int ii = 0; ii < 20; ++ii)
+            {
+                value_1[ii] = distribution_12_neg(generator);
+                value_2[ii] = distribution_12_neg(generator);
+                value_3[ii] = distribution_3(generator);
+            }
+            for(int ii = 20; ii < 40; ++ii)
+            {
+                value_1[ii] = distribution_12_pos(generator);
+                value_2[ii] = distribution_12_pos(generator);
+                value_3[ii] = distribution_3(generator);
+            }
+            for(int ii = 40; ii < 60; ++ii)
+            {
+                value_1[ii] = distribution_12_neg(generator);
+                value_2[ii] = distribution_12_pos(generator);
+                value_3[ii] = distribution_3(generator);
+            }
+            for(int ii = 60; ii < 80; ++ii)
+            {
+                value_1[ii] = distribution_12_pos(generator);
+                value_2[ii] = distribution_12_neg(generator);
+                value_3[ii] = distribution_3(generator);
+            }
+
+            value_1[0] = 0.1;
+            value_1[20] = -0.1;
+            value_1[40] = 0.1;
+            value_1[60] = -0.1;
+
+            value_2[0] = -0.1;
+            value_2[20] = 0.1;
+            value_2[40] = 0.1;
+            value_2[60] = -0.1;
+
+            for(int ii = 0; ii < 5; ++ii)
+            {
+                test_value_1[ii] = distribution_12_neg(generator);
+                test_value_2[ii] = distribution_12_neg(generator);
+                test_value_3[ii] = distribution_3(generator);
+            }
+            for(int ii = 5; ii < 10; ++ii)
+            {
+                test_value_1[ii] = distribution_12_pos(generator);
+                test_value_2[ii] = distribution_12_pos(generator);
+                test_value_3[ii] = distribution_3(generator);
+            }
+            for(int ii = 10; ii < 15; ++ii)
+            {
+                test_value_1[ii] = distribution_12_neg(generator);
+                test_value_2[ii] = distribution_12_pos(generator);
+                test_value_3[ii] = distribution_3(generator);
+            }
+            for(int ii = 15; ii < 20; ++ii)
+            {
+                test_value_1[ii] = distribution_12_pos(generator);
+                test_value_2[ii] = distribution_12_neg(generator);
+                test_value_3[ii] = distribution_3(generator);
+            }
+
+
+            test_value_1[0] = 0.1;
+            test_value_1[5] = -0.1;
+            test_value_1[10] = 0.1;
+            test_value_1[15] = -0.1;
+
+            test_value_2[0] = -0.1;
+            test_value_2[5] = 0.1;
+            test_value_2[10] = 0.1;
+            test_value_2[15] = -0.1;
+
+            node_ptr feat_1 = std::make_shared<FeatureNode>(0, "A", value_1, test_value_1, Unit("m"));
+            node_ptr feat_2 = std::make_shared<FeatureNode>(1, "B", value_2, test_value_2, Unit("m"));
+            node_ptr feat_3 = std::make_shared<FeatureNode>(2, "C", value_3, test_value_3, Unit("s"));
+
+            _prop = std::vector<double>(80, 0.0);
+            _prop_test = std::vector<double>(20, 0.0);
+            std::fill_n(_prop.begin() + 40, 40, 1.0);
+            std::fill_n(_prop_test.begin() + 10, 10, 1.0);
+
+            _phi_0 ={feat_1, feat_2, feat_3};
+
+            _allowed_ops = {"add", "sub", "mult", "sq", "cb", "sqrt", "cbrt"};
+
+        }
+        std::vector<std::string> _allowed_ops;
+        std::vector<node_ptr> _phi_0;
+
+        std::vector<double> _prop;
+        std::vector<double> _prop_test;
+
+        std::vector<int> _task_sizes_train;
+        std::vector<int> _task_sizes_test;
+        std::vector<int> _leave_out_inds;
+    };
+
+    TEST_F(SISSOClassifierTests, FixInterceptFalseTest)
+    {
+        std::shared_ptr<FeatureSpace> feat_space = std::make_shared<FeatureSpace>(
+            mpi_setup::comm,
+            _phi_0,
+            _allowed_ops,
+            _prop,
+            _task_sizes_train,
+            "classification",
+            2,
+            10,
+            1,
+            0,
+            1.0,
+            1e-50,
+            1e50
+        );
+        SISSOClassifier sisso(
+            feat_space,
+            "Property",
+            Unit("m"),
+            _prop,
+            _prop_test,
+            _task_sizes_train,
+            _task_sizes_test,
+            _leave_out_inds,
+            2,
+            2,
+            3
+        );
+        std::vector<double> prop_comp(80, 0.0);
+        std::transform(_prop.begin(), _prop.end(), sisso.prop().begin(), prop_comp.begin(), [](double p1, double p2){return std::abs(p1 - p2);});
+        EXPECT_FALSE(std::any_of(prop_comp.begin(), prop_comp.end(), [](double p){return p > 1e-10;}));
+
+        std::transform(_prop_test.begin(), _prop_test.begin() + 10, sisso.prop_test().begin(), prop_comp.begin(), [](double p1, double p2){return std::abs(p1 - p2);});
+        EXPECT_FALSE(std::any_of(prop_comp.begin(), prop_comp.begin() + 10, [](double p){return p > 1e-10;}));
+
+        EXPECT_EQ(sisso.n_samp(), 80);
+        EXPECT_EQ(sisso.n_dim(), 2);
+        EXPECT_EQ(sisso.n_residual(), 2);
+        EXPECT_EQ(sisso.n_models_store(), 3);
+
+        sisso.fit();
+
+        EXPECT_EQ(sisso.models().size(), 2);
+        EXPECT_EQ(sisso.models()[0].size(), 3);
+
+        EXPECT_EQ(sisso.models()[0][0].n_convex_overlap_train(), 4);
+        EXPECT_EQ(sisso.models().back()[0].n_convex_overlap_train(), 0);
+
+        EXPECT_EQ(sisso.models()[0][0].n_convex_overlap_test(), 4);
+        EXPECT_EQ(sisso.models().back()[0].n_convex_overlap_test(), 0);
+
+        boost::filesystem::remove_all("feature_space/");
+        boost::filesystem::remove_all("models/");
+    }
+}
diff --git a/tests/googletest/descriptor_identification/sisso_regressor/test_sisso_log_regressor.cc b/tests/googletest/descriptor_identification/sisso_regressor/test_sisso_log_regressor.cc
new file mode 100644
index 0000000000000000000000000000000000000000..efa7555519298c092489ea83587f68b7d417e66e
--- /dev/null
+++ b/tests/googletest/descriptor_identification/sisso_regressor/test_sisso_log_regressor.cc
@@ -0,0 +1,199 @@
+#include <descriptor_identifier/SISSO_DI/SISSOLogRegressor.hpp>
+#include <boost/filesystem.hpp>
+#include "gtest/gtest.h"
+#include <random>
+
+namespace
+{
+    class SISSOLogRegressorTests : public ::testing::Test
+    {
+    protected:
+        void SetUp() override
+        {
+            allowed_op_maps::set_node_maps();
+            node_value_arrs::initialize_d_matrix_arr();
+            mpi_setup::init_mpi_env();
+
+            node_value_arrs::initialize_values_arr(90, 10, 3);
+
+            _task_sizes_train = {90};
+            _task_sizes_test = {10};
+            _leave_out_inds = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+
+            std::vector<double> value_1(90, 0.0);
+            std::vector<double> value_2(90, 0.0);
+            std::vector<double> value_3(90, 0.0);
+
+            std::vector<double> test_value_1(10, 0.0);
+            std::vector<double> test_value_2(10, 0.0);
+            std::vector<double> test_value_3(10, 0.0);
+
+            std::default_random_engine generator;
+            std::uniform_real_distribution<double> distribution_feats(0.01, 100.0);
+            std::uniform_real_distribution<double> distribution_params(0.9, 1.1);
+
+            for(int ii = 0; ii < 90; ++ii)
+            {
+                value_1[ii] = distribution_feats(generator);
+                value_2[ii] = distribution_feats(generator);
+                value_3[ii] = distribution_feats(generator);
+            }
+
+            for(int ii = 0; ii < 10; ++ii)
+            {
+                test_value_1[ii] = distribution_feats(generator);
+                test_value_2[ii] = distribution_feats(generator);
+                test_value_3[ii] = distribution_feats(generator);
+            }
+
+            node_ptr feat_1 = std::make_shared<FeatureNode>(0, "A", value_1, test_value_1, Unit("m"));
+            node_ptr feat_2 = std::make_shared<FeatureNode>(1, "B", value_2, test_value_2, Unit("m"));
+            node_ptr feat_3 = std::make_shared<FeatureNode>(2, "C", value_3, test_value_3, Unit("s"));
+
+            _phi_0 ={feat_1, feat_2, feat_3};
+
+            double a00 = distribution_params(generator);
+            double a10 = distribution_params(generator);
+            double c00 = distribution_feats(generator);
+
+            _prop = std::vector<double>(90, 0.0);
+            std::transform(value_1.begin(), value_1.end(), value_2.begin(), _prop.begin(), [&c00, &a00, &a10](double v1, double v2){return c00 * std::pow(v1 * v1, a00) * std::pow(v2, a10);});
+
+            _prop_test = std::vector<double>(10, 0.0);
+            std::transform(test_value_1.begin(), test_value_1.end(), test_value_2.begin(), _prop_test.begin(), [&c00, &a00, &a10](double v1, double v2){return c00 * std::pow(v1 * v1, a00) * std::pow(v2, a10);});
+
+            _prop_zero_int = std::vector<double>(90, 0.0);
+            std::transform(value_1.begin(), value_1.end(), value_2.begin(), _prop_zero_int.begin(), [&a00, &a10](double v1, double v2){return std::pow(v1 * v1, a00) * std::pow(v2, a10);});
+
+            _prop_test_zero_int = std::vector<double>(10, 0.0);
+            std::transform(test_value_1.begin(), test_value_1.end(), test_value_2.begin(), _prop_test_zero_int.begin(), [&a00, &a10](double v1, double v2){return std::pow(v1 * v1, a00) * std::pow(v2, a10);});
+
+            _allowed_ops = {"div", "add", "mult", "sub"};
+
+        }
+        std::vector<std::string> _allowed_ops;
+        std::vector<node_ptr> _phi_0;
+
+        std::vector<double> _prop;
+        std::vector<double> _prop_test;
+
+        std::vector<double> _prop_zero_int;
+        std::vector<double> _prop_test_zero_int;
+
+        std::vector<int> _task_sizes_train;
+        std::vector<int> _task_sizes_test;
+        std::vector<int> _leave_out_inds;
+    };
+
+    TEST_F(SISSOLogRegressorTests, FixInterceptFalseTest)
+    {
+        std::shared_ptr<FeatureSpace> feat_space = std::make_shared<FeatureSpace>(
+            mpi_setup::comm,
+            _phi_0,
+            _allowed_ops,
+            _prop,
+            _task_sizes_train,
+            "log_regression",
+            2,
+            10,
+            1,
+            0,
+            1.0,
+            1e-50,
+            1e50
+        );
+        SISSOLogRegressor sisso(
+            feat_space,
+            "Property",
+            Unit("m"),
+            _prop,
+            _prop_test,
+            _task_sizes_train,
+            _task_sizes_test,
+            _leave_out_inds,
+            2,
+            2,
+            3,
+            false
+        );
+        std::vector<double> prop_comp(90, 0.0);
+        std::transform(_prop.begin(), _prop.end(), sisso.prop().begin(), prop_comp.begin(), [](double p1, double p2){return std::abs(std::log(p1) - p2);});
+        EXPECT_FALSE(std::any_of(prop_comp.begin(), prop_comp.end(), [](double p){return p > 1e-10;}));
+
+        std::transform(_prop_test.begin(), _prop_test.begin() + 10, sisso.prop_test().begin(), prop_comp.begin(), [](double p1, double p2){return std::abs(p1 - p2);});
+        EXPECT_FALSE(std::any_of(prop_comp.begin(), prop_comp.begin() + 10, [](double p){return p > 1e-10;}));
+
+        EXPECT_EQ(sisso.n_samp(), 90);
+        EXPECT_EQ(sisso.n_dim(), 2);
+        EXPECT_EQ(sisso.n_residual(), 2);
+        EXPECT_EQ(sisso.n_models_store(), 3);
+
+        sisso.fit();
+
+        EXPECT_EQ(sisso.models().size(), 2);
+        EXPECT_EQ(sisso.models()[0].size(), 3);
+
+        EXPECT_LT(sisso.models().back()[0].rmse(), 1e-7);
+        EXPECT_LT(sisso.models().back()[0].test_rmse(), 1e-7);
+
+        boost::filesystem::remove_all("feature_space/");
+        boost::filesystem::remove_all("models/");
+    }
+
+    TEST_F(SISSOLogRegressorTests, FixInterceptTrueTest)
+    {
+        std::shared_ptr<FeatureSpace> feat_space = std::make_shared<FeatureSpace>(
+            mpi_setup::comm,
+            _phi_0,
+            _allowed_ops,
+            _prop_zero_int,
+            _task_sizes_train,
+            "log_regression",
+            2,
+            10,
+            1,
+            0,
+            1.0,
+            1e-50,
+            1e50
+        );
+
+        SISSOLogRegressor sisso(
+            feat_space,
+            "Property",
+            Unit("m"),
+            _prop_zero_int,
+            _prop_test_zero_int,
+            _task_sizes_train,
+            _task_sizes_test,
+            _leave_out_inds,
+            2,
+            2,
+            3,
+            true
+        );
+
+        std::vector<double> prop_comp(90, 0.0);
+        std::transform(_prop_zero_int.begin(), _prop_zero_int.end(), sisso.prop().begin(), prop_comp.begin(), [](double p1, double p2){return std::abs(std::log(p1) - p2);});
+        EXPECT_FALSE(std::any_of(prop_comp.begin(), prop_comp.end(), [](double p){return p > 1e-10;}));
+
+        std::transform(_prop_test_zero_int.begin(), _prop_test_zero_int.begin() + 10, sisso.prop_test().begin(), prop_comp.begin(), [](double p1, double p2){return std::abs(p1 - p2);});
+        EXPECT_FALSE(std::any_of(prop_comp.begin(), prop_comp.begin() + 10, [](double p){return p > 1e-10;}));
+
+        EXPECT_EQ(sisso.n_samp(), 90);
+        EXPECT_EQ(sisso.n_dim(), 2);
+        EXPECT_EQ(sisso.n_residual(), 2);
+        EXPECT_EQ(sisso.n_models_store(), 3);
+
+        sisso.fit();
+
+        EXPECT_EQ(sisso.models().size(), 2);
+        EXPECT_EQ(sisso.models()[0].size(), 3);
+
+        EXPECT_LT(sisso.models().back()[0].rmse(), 1e-7);
+        EXPECT_LT(sisso.models().back()[0].test_rmse(), 1e-7);
+
+        boost::filesystem::remove_all("feature_space/");
+        boost::filesystem::remove_all("models/");
+    }
+}
diff --git a/tests/googletest/descriptor_identification/sisso_regressor/test_sisso_regressor.cc b/tests/googletest/descriptor_identification/sisso_regressor/test_sisso_regressor.cc
new file mode 100644
index 0000000000000000000000000000000000000000..54c006789cc10d2844a7529d7ff57a6c83d3a8af
--- /dev/null
+++ b/tests/googletest/descriptor_identification/sisso_regressor/test_sisso_regressor.cc
@@ -0,0 +1,220 @@
+#include <descriptor_identifier/SISSO_DI/SISSORegressor.hpp>
+#include <boost/filesystem.hpp>
+#include "gtest/gtest.h"
+#include <random>
+
+namespace
+{
+    class SISSORegressorTests : public ::testing::Test
+    {
+    protected:
+        void SetUp() override
+        {
+            allowed_op_maps::set_node_maps();
+            node_value_arrs::initialize_d_matrix_arr();
+            mpi_setup::init_mpi_env();
+
+            node_value_arrs::initialize_values_arr(90, 10, 3);
+
+            _task_sizes_train = {36, 54};
+            _task_sizes_test = {4, 6};
+            _leave_out_inds = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+
+            std::vector<double> value_1(90, 0.0);
+            std::vector<double> value_2(90, 0.0);
+            std::vector<double> value_3(90, 0.0);
+
+            std::vector<double> test_value_1(10, 0.0);
+            std::vector<double> test_value_2(10, 0.0);
+            std::vector<double> test_value_3(10, 0.0);
+
+            std::default_random_engine generator;
+            std::uniform_real_distribution<double> distribution_feats(-50.0, 50.0);
+            std::uniform_real_distribution<double> distribution_params(-2.50, 2.50);
+
+            for(int ii = 0; ii < 90; ++ii)
+            {
+                value_1[ii] = distribution_feats(generator);
+                value_2[ii] = distribution_feats(generator);
+                value_3[ii] = distribution_feats(generator);
+            }
+
+            for(int ii = 0; ii < 10; ++ii)
+            {
+                test_value_1[ii] = distribution_feats(generator);
+                test_value_2[ii] = distribution_feats(generator);
+                test_value_3[ii] = distribution_feats(generator);
+            }
+
+            node_ptr feat_1 = std::make_shared<FeatureNode>(0, "A", value_1, test_value_1, Unit("m"));
+            node_ptr feat_2 = std::make_shared<FeatureNode>(1, "B", value_2, test_value_2, Unit("m"));
+            node_ptr feat_3 = std::make_shared<FeatureNode>(2, "C", value_3, test_value_3, Unit("s"));
+
+            _phi_0 ={feat_1, feat_2, feat_3};
+
+            double a00 = distribution_params(generator);
+            double a01 = distribution_params(generator);
+
+            double a10 = distribution_params(generator);
+            double a11 = distribution_params(generator);
+
+            double c00 = distribution_params(generator);
+            double c01 = distribution_params(generator);
+
+            _prop = std::vector<double>(90, 0.0);
+            std::transform(value_1.begin(), value_1.begin() + _task_sizes_train[0], value_2.begin(), _prop.begin(), [&c00, &a00](double v1, double v2){return c00 + a00 * (v1 - v2) * (v1 - v2);});
+            std::transform(value_1.begin() + _task_sizes_train[0], value_1.end(), value_2.begin() + _task_sizes_train[0], _prop.begin() + _task_sizes_train[0], [&c01, &a01](double v1, double v2){return c01 + a01 * (v1 - v2) * (v1 - v2);});
+
+            std::transform(value_3.begin(), value_3.begin() + _task_sizes_train[0], _prop.begin(), _prop.begin(), [&a10](double v3, double p){return p + a10 * v3;});
+            std::transform(value_3.begin() + _task_sizes_train[0], value_3.end(), _prop.begin() + _task_sizes_train[0], _prop.begin() + _task_sizes_train[0], [&a11](double v3, double p){return p + a11 * v3;});
+
+            _prop_test = std::vector<double>(10, 0.0);
+            std::transform(test_value_1.begin(), test_value_1.begin() + _task_sizes_test[0], test_value_2.begin(), _prop_test.begin(), [&c00, &a00](double v1, double v2){return c00 + a00 * (v1 - v2) * (v1 - v2);});
+            std::transform(test_value_1.begin() + _task_sizes_test[0], test_value_1.end(), test_value_2.begin() + _task_sizes_test[0], _prop_test.begin() + _task_sizes_test[0], [&c01, &a01](double v1, double v2){return c01 + a01 * (v1 - v2) * (v1 - v2);});
+
+            std::transform(test_value_3.begin(), test_value_3.begin() + _task_sizes_test[0], _prop_test.begin(), _prop_test.begin(), [&a10](double v3, double p){return p + a10 * v3;});
+            std::transform(test_value_3.begin() + _task_sizes_test[0], test_value_3.end(), _prop_test.begin() + _task_sizes_test[0], _prop_test.begin() + _task_sizes_test[0], [&a11](double v3, double p){return p + a11 * v3;});
+
+            _prop_zero_int = std::vector<double>(90, 0.0);
+            std::transform(value_1.begin(), value_1.begin() + _task_sizes_train[0], value_2.begin(), _prop_zero_int.begin(), [&a00](double v1, double v2){return a00 * (v1 - v2) * (v1 - v2);});
+            std::transform(value_1.begin() + _task_sizes_train[0], value_1.end(), value_2.begin() + _task_sizes_train[0], _prop_zero_int.begin() + _task_sizes_train[0], [&a01](double v1, double v2){return a01 * (v1 - v2) * (v1 - v2);});
+
+            std::transform(value_3.begin(), value_3.begin() + _task_sizes_train[0], _prop_zero_int.begin(), _prop_zero_int.begin(), [&a10](double v3, double p){return p + a10 * v3;});
+            std::transform(value_3.begin() + _task_sizes_train[0], value_3.end(), _prop_zero_int.begin() + _task_sizes_train[0], _prop_zero_int.begin() + _task_sizes_train[0], [&a11](double v3, double p){return p + a11 * v3;});
+
+            _prop_test_zero_int = std::vector<double>(10, 0.0);
+            std::transform(test_value_1.begin(), test_value_1.begin() + _task_sizes_test[0], test_value_2.begin(), _prop_test_zero_int.begin(), [&a00](double v1, double v2){return  a00 * (v1 - v2) * (v1 - v2);});
+            std::transform(test_value_1.begin() + _task_sizes_test[0], test_value_1.end(), test_value_2.begin() + _task_sizes_test[0], _prop_test_zero_int.begin() + _task_sizes_test[0], [&a01](double v1, double v2){return a01 * (v1 - v2) * (v1 - v2);});
+
+            std::transform(test_value_3.begin(), test_value_3.begin() + _task_sizes_test[0], _prop_test_zero_int.begin(), _prop_test_zero_int.begin(), [&a10](double v3, double p){return p + a10 * v3;});
+            std::transform(test_value_3.begin() + _task_sizes_test[0], test_value_3.end(), _prop_test_zero_int.begin() + _task_sizes_test[0], _prop_test_zero_int.begin() + _task_sizes_test[0], [&a11](double v3, double p){return p + a11 * v3;});
+
+
+            _allowed_ops = {"div", "sq", "cb", "sub"};
+        }
+        std::vector<std::string> _allowed_ops;
+        std::vector<node_ptr> _phi_0;
+
+        std::vector<double> _prop;
+        std::vector<double> _prop_test;
+
+        std::vector<double> _prop_zero_int;
+        std::vector<double> _prop_test_zero_int;
+
+        std::vector<int> _task_sizes_train;
+        std::vector<int> _task_sizes_test;
+        std::vector<int> _leave_out_inds;
+    };
+
+    TEST_F(SISSORegressorTests, FixInterceptFalseTest)
+    {
+        std::shared_ptr<FeatureSpace> feat_space = std::make_shared<FeatureSpace>(
+            mpi_setup::comm,
+            _phi_0,
+            _allowed_ops,
+            _prop,
+            _task_sizes_train,
+            "regression",
+            2,
+            10,
+            1,
+            0,
+            1.0,
+            1e-50,
+            1e50
+        );
+        SISSORegressor sisso(
+            feat_space,
+            "Property",
+            Unit("m"),
+            _prop,
+            _prop_test,
+            _task_sizes_train,
+            _task_sizes_test,
+            _leave_out_inds,
+            2,
+            2,
+            3,
+            false
+        );
+        std::vector<double> prop_comp(90, 0.0);
+        std::transform(_prop.begin(), _prop.end(), sisso.prop().begin(), prop_comp.begin(), [](double p1, double p2){return std::abs(p1 - p2);});
+        EXPECT_FALSE(std::any_of(prop_comp.begin(), prop_comp.end(), [](double p){return p > 1e-10;}));
+
+        std::transform(_prop_test.begin(), _prop_test.begin() + 2, sisso.prop_test().begin(), prop_comp.begin(), [](double p1, double p2){return std::abs(p1 - p2);});
+        EXPECT_FALSE(std::any_of(prop_comp.begin(), prop_comp.begin() + 2, [](double p){return p > 1e-10;}));
+
+        EXPECT_EQ(sisso.n_samp(), 90);
+        EXPECT_EQ(sisso.n_dim(), 2);
+        EXPECT_EQ(sisso.n_residual(), 2);
+        EXPECT_EQ(sisso.n_models_store(), 3);
+
+        sisso.fit();
+
+        EXPECT_EQ(sisso.models().size(), 2);
+        EXPECT_EQ(sisso.models()[0].size(), 3);
+
+        EXPECT_LT(sisso.models().back()[0].rmse(), 1e-10);
+        EXPECT_LT(sisso.models().back()[0].test_rmse(), 1e-10);
+
+        boost::filesystem::remove_all("feature_space/");
+        boost::filesystem::remove_all("models/");
+    }
+
+    TEST_F(SISSORegressorTests, FixInterceptTrueTest)
+    {
+        std::shared_ptr<FeatureSpace> feat_space = std::make_shared<FeatureSpace>(
+            mpi_setup::comm,
+            _phi_0,
+            _allowed_ops,
+            _prop_zero_int,
+            _task_sizes_train,
+            "regression",
+            2,
+            10,
+            1,
+            0,
+            1.0,
+            1e-50,
+            1e50
+        );
+
+        SISSORegressor sisso(
+            feat_space,
+            "Property",
+            Unit("m"),
+            _prop_zero_int,
+            _prop_test_zero_int,
+            _task_sizes_train,
+            _task_sizes_test,
+            _leave_out_inds,
+            2,
+            2,
+            3,
+            true
+        );
+
+        std::vector<double> prop_comp(90, 0.0);
+        std::transform(_prop_zero_int.begin(), _prop_zero_int.end(), sisso.prop().begin(), prop_comp.begin(), [](double p1, double p2){return std::abs(p1 - p2);});
+        EXPECT_FALSE(std::any_of(prop_comp.begin(), prop_comp.end(), [](double p){return p > 1e-10;}));
+
+        std::transform(_prop_test_zero_int.begin(), _prop_test_zero_int.begin() + 2, sisso.prop_test().begin(), prop_comp.begin(), [](double p1, double p2){return std::abs(p1 - p2);});
+        EXPECT_FALSE(std::any_of(prop_comp.begin(), prop_comp.begin() + 2, [](double p){return p > 1e-10;}));
+
+        EXPECT_EQ(sisso.n_samp(), 90);
+        EXPECT_EQ(sisso.n_dim(), 2);
+        EXPECT_EQ(sisso.n_residual(), 2);
+        EXPECT_EQ(sisso.n_models_store(), 3);
+
+        sisso.fit();
+
+        EXPECT_EQ(sisso.models().size(), 2);
+        EXPECT_EQ(sisso.models()[0].size(), 3);
+
+        EXPECT_LT(sisso.models().back()[0].rmse(), 1e-10);
+        EXPECT_LT(sisso.models().back()[0].test_rmse(), 1e-10);
+
+        boost::filesystem::remove_all("feature_space/");
+        boost::filesystem::remove_all("models/");
+    }
+}
diff --git a/tests/googletest/feature_creation/feature_generation/test_abs_diff_node.cc b/tests/googletest/feature_creation/feature_generation/test_abs_diff_node.cc
new file mode 100644
index 0000000000000000000000000000000000000000..95339a08109f196c0824f02ef5426d9327fb708b
--- /dev/null
+++ b/tests/googletest/feature_creation/feature_generation/test_abs_diff_node.cc
@@ -0,0 +1,161 @@
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/abs_diff/absolute_difference.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/add/add.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/sub/subtract.hpp>
+#include <feature_creation/node/value_storage/nodes_value_containers.hpp>
+#include <feature_creation/node/FeatureNode.hpp>
+#include "gtest/gtest.h"
+
+namespace
+{
+    class AbsDiffNodeTest : public ::testing::Test
+    {
+    protected:
+        void SetUp() override
+        {
+            node_value_arrs::initialize_values_arr(4, 1, 4);
+
+            std::vector<double> value_1 = {1.0, 2.0, 3.0, 4.0};
+            std::vector<double> test_value_1 =  {5.0};
+
+            std::vector<double> value_2 = {10.0, 20.0, 30.0, 40.0};
+            std::vector<double> test_value_2 =  {50.0};
+
+            std::vector<double> value_3 = {1.0, 2.0, 3.0, 4.0};
+            std::vector<double> test_value_3 =  {5.0};
+
+            _feat_1 = std::make_shared<FeatureNode>(0, "A", value_1, test_value_1, Unit("m"));
+            _feat_2 = std::make_shared<FeatureNode>(1, "B", value_2, test_value_2, Unit("m"));
+            _feat_3 = std::make_shared<FeatureNode>(2, "C", value_2, test_value_2, Unit("s"));
+            _feat_4 = std::make_shared<FeatureNode>(3, "C", value_3, test_value_3, Unit("s"));
+
+            _phi = {_feat_1, _feat_2, _feat_3, _feat_4};
+            _phi.push_back(std::make_shared<AddNode>(_feat_1, _feat_2, 4, -1e-50, 1e50));
+            _phi.push_back(std::make_shared<SubNode>(_feat_1, _feat_2, 4, -1e-50, 1e50));
+        }
+
+        node_ptr _feat_1;
+        node_ptr _feat_2;
+        node_ptr _feat_3;
+        node_ptr _feat_4;
+        node_ptr _abs_diff_test;
+
+        std::vector<node_ptr> _phi;
+    };
+
+    TEST_F(AbsDiffNodeTest, GeneratorTest)
+    {
+        int feat_ind = _phi.size();
+
+        generateAbsDiffNode(_phi, _phi[0], _phi[2], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 6) << " (AbsDiffNode created when units do not match.)";
+
+        generateAbsDiffNode(_phi, _phi[0], _phi[3], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 6) << " (Creation of AbsDiffNode led to a feature with a constant value)";
+
+        generateAbsDiffNode(_phi, _phi[0], _phi[1], feat_ind, 1e-50, 1.0);
+        EXPECT_EQ(_phi.size(), 6) << " (AbsDiffNode created with an absolute value above the upper bound)";
+
+        generateAbsDiffNode(_phi, _phi[0], _phi[1], feat_ind, 1e3, 1e50);
+        EXPECT_EQ(_phi.size(), 6) << " (AbsDiffNode created with an absolute value below the lower bound)";
+
+        generateAbsDiffNode(_phi, _phi[0], _phi[0], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 6) << " (AbsDiffNode created with only one primary feature present)";
+
+        generateAbsDiffNode(_phi, _phi[4], _phi[4], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 6) << " (AbsDiffNode created when some terms cancel out)";
+
+        generateAbsDiffNode(_phi, _phi[5], _phi[4], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 6) << " (AbsDiffNode created that is a duplicate of a second feature)";
+
+        generateAbsDiffNode(_phi, _phi[0], _phi[1], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 7) << " (Failure to create a valid feature)";
+    }
+
+    TEST_F(AbsDiffNodeTest, ConstructorTest)
+    {
+        int feat_ind = _phi.size();
+
+        try
+        {
+            _abs_diff_test = std::make_shared<AbsDiffNode>(_phi[0], _phi[2], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (AbsDiffNode created when units do not match.)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _abs_diff_test = std::make_shared<AbsDiffNode>(_phi[0], _phi[3], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (Creation of AbsDiffNode led to a feature with a constant value)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _abs_diff_test = std::make_shared<AbsDiffNode>(_phi[0], _phi[1], feat_ind, 1e-50, 1.0);
+            EXPECT_TRUE(false) << " (AbsDiffNode created with an absolute value above the upper bound)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _abs_diff_test = std::make_shared<AbsDiffNode>(_phi[0], _phi[1], feat_ind, 1e3, 1e50);
+            EXPECT_TRUE(false) << " (AbsDiffNode created with an absolute value below the lower bound)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _abs_diff_test = std::make_shared<AbsDiffNode>(_phi[0], _phi[0], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (AbsDiffNode created with only one primary feature present)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+        try
+        {
+            _abs_diff_test = std::make_shared<AbsDiffNode>(_phi[1], _phi[4], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (AbsDiffNode created when some terms cancel out)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _abs_diff_test = std::make_shared<AbsDiffNode>(_phi[0], _phi[1], feat_ind, 1e-50, 1e50);
+            ++feat_ind;
+        }
+        catch(const InvalidFeatureException& e)
+        {
+            ASSERT_TRUE(false) << " (Failure to create a valid feature)";
+        }
+        try
+        {
+            _abs_diff_test = std::make_shared<AbsDiffNode>(_phi[5], _phi[4], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (AbsDiffNode created that is a duplicate of a second feature)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+    }
+
+    TEST_F(AbsDiffNodeTest, AttributesTest)
+    {
+        _abs_diff_test = std::make_shared<AbsDiffNode>(_phi[0], _phi[1], 5, 1e-50, 1e50);
+        _abs_diff_test = std::make_shared<AbsDiffNode>(_abs_diff_test, _phi[1], 6, 1e-50, 1e50);
+
+        EXPECT_EQ(_abs_diff_test->rung(), 2);
+
+        EXPECT_EQ(_abs_diff_test->value_ptr()[0], 1.0);
+        EXPECT_EQ(_abs_diff_test->test_value_ptr()[0], 5.0);
+
+        EXPECT_EQ(_abs_diff_test->value()[0], 1.0);
+        EXPECT_EQ(_abs_diff_test->test_value()[0], 5.0);
+
+        EXPECT_STREQ(_abs_diff_test->unit().toString().c_str(), "m");
+
+        EXPECT_STREQ(_abs_diff_test->expr().c_str(), "||A - (B)| - (B)|");
+        EXPECT_STREQ(_abs_diff_test->postfix_expr().c_str(), "0|1|abd|1|abd");
+    }
+}
diff --git a/tests/googletest/feature_creation/feature_generation/test_abs_node.cc b/tests/googletest/feature_creation/feature_generation/test_abs_node.cc
new file mode 100644
index 0000000000000000000000000000000000000000..97910a20fdc3cdf177c57df3dbe2a0e590cdb8fc
--- /dev/null
+++ b/tests/googletest/feature_creation/feature_generation/test_abs_node.cc
@@ -0,0 +1,149 @@
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/abs_diff/absolute_difference.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/abs/absolute_value.hpp>
+#include <feature_creation/node/value_storage/nodes_value_containers.hpp>
+#include <feature_creation/node/FeatureNode.hpp>
+#include "gtest/gtest.h"
+
+namespace
+{
+    class AbsNodeTest : public ::testing::Test
+    {
+    protected:
+        void SetUp() override
+        {
+            node_value_arrs::initialize_values_arr(4, 1, 4);
+
+            std::vector<double> value_1 = {-1.0, -2.0, -3.0, -4.0};
+            std::vector<double> test_value_1 =  {50.0};
+
+            std::vector<double> value_2 = {1.0, 2.0, 3.0, 4.0};
+            std::vector<double> test_value_2 =  {5.0};
+
+            std::vector<double> value_3 = {1.0, -1.0, 1.0, -1.0};
+            std::vector<double> test_value_3 =  {1.0};
+
+            _feat_1 = std::make_shared<FeatureNode>(0, "A", value_1, test_value_1, Unit("m"));
+            _feat_2 = std::make_shared<FeatureNode>(1, "B", value_2, test_value_2, Unit("m"));
+            _feat_3 = std::make_shared<FeatureNode>(2, "C", value_3, test_value_3, Unit("m"));
+
+            _phi = {_feat_1, _feat_2, _feat_3};
+            _phi.push_back(std::make_shared<AbsDiffNode>(_feat_1, _feat_3, 3, 1e-50, 1e50));
+        }
+
+        node_ptr _feat_1;
+        node_ptr _feat_2;
+        node_ptr _feat_3;
+
+        node_ptr _abs_test;
+
+        std::vector<node_ptr> _phi;
+    };
+
+    TEST_F(AbsNodeTest, GeneratorTest)
+    {
+        int feat_ind = _phi.size();
+
+        generateAbsNode(_phi, _phi[2], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 4) << " (Creation of AbsNode led to a feature with a constant value)";
+
+        generateAbsNode(_phi, _phi[1], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 4) << "(AbsNode created with a feature that only has positive values)";
+
+        generateAbsNode(_phi, _phi[0], feat_ind, 1e-50, 1e-40);
+        EXPECT_EQ(_phi.size(), 4) << " (AbsNode created with an absolute value above the upper bound)";
+
+        generateAbsNode(_phi, _phi[0], feat_ind, 1e3, 1e50);
+        EXPECT_EQ(_phi.size(), 4) << " (AbsNode created with an absolute value below the lower bound)";
+
+        generateAbsNode(_phi, _phi[3], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 4) << " (AbsfNode created from another an AbsDiffNode)";
+
+        generateAbsNode(_phi, _phi[0], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 5) << " (Failure to create a valid feature)";
+
+        generateAbsNode(_phi, _phi[4], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 5) << " (AbsNode created from another AbsNode)";
+
+    }
+
+    TEST_F(AbsNodeTest, ConstructorTest)
+    {
+        int feat_ind = _phi.size();
+
+        try
+        {
+            _abs_test = std::make_shared<AbsNode>(_phi[2], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (Creation of AbsNode led to a feature with a constant value)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _abs_test = std::make_shared<AbsNode>(_phi[1], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (AbsNode created with a feature that only has positive values)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _abs_test = std::make_shared<AbsNode>(_phi[0], feat_ind, 1e-50, 1e-40);
+            EXPECT_TRUE(false) << " (AbsNode created with an absolute value above the upper bound)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _abs_test = std::make_shared<AbsNode>(_phi[0], feat_ind, 1e3, 1e50);
+            EXPECT_TRUE(false) << " (AbsNode created with an absolute value below the lower bound)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _abs_test = std::make_shared<AbsNode>(_phi[3], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (AbsfNode created from another an AbsDiffNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _abs_test = std::make_shared<AbsNode>(_phi[0], feat_ind, 1e-50, 1e50);
+            ++feat_ind;
+        }
+        catch(const InvalidFeatureException& e)
+        {
+            ASSERT_TRUE(false) << " (Failure to create a valid feature)";
+        }
+
+        try
+        {
+            _abs_test = std::make_shared<AbsNode>(_abs_test, feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (AbsNode created from another AbsNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+    }
+
+    TEST_F(AbsNodeTest, AttributesTest)
+    {
+        _abs_test = std::make_shared<AbsNode>(_phi[0], 5, 1e-50, 1e50);
+
+        EXPECT_EQ(_abs_test->rung(), 1);
+
+        EXPECT_EQ(_abs_test->value_ptr()[0], 1.0);
+        EXPECT_EQ(_abs_test->test_value_ptr()[0], 50.0);
+
+        EXPECT_EQ(_abs_test->value()[0], 1.0);
+        EXPECT_EQ(_abs_test->test_value()[0], 50.0);
+
+        EXPECT_STREQ(_abs_test->unit().toString().c_str(), "m");
+
+        EXPECT_STREQ(_abs_test->expr().c_str(), "|A|");
+        EXPECT_STREQ(_abs_test->postfix_expr().c_str(), "0|abs");
+    }
+}
diff --git a/tests/googletest/feature_creation/feature_generation/test_add_node.cc b/tests/googletest/feature_creation/feature_generation/test_add_node.cc
new file mode 100644
index 0000000000000000000000000000000000000000..b93800539d0fd067f710bec38e8f317f5fa570f7
--- /dev/null
+++ b/tests/googletest/feature_creation/feature_generation/test_add_node.cc
@@ -0,0 +1,159 @@
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/add/add.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/sub/subtract.hpp>
+#include <feature_creation/node/value_storage/nodes_value_containers.hpp>
+#include <feature_creation/node/FeatureNode.hpp>
+#include "gtest/gtest.h"
+
+namespace
+{
+    class AddNodeTest : public ::testing::Test
+    {
+    protected:
+        void SetUp() override
+        {
+            node_value_arrs::initialize_values_arr(4, 1, 4);
+
+            std::vector<double> value_1 = {1.0, 2.0, 3.0, 4.0};
+            std::vector<double> test_value_1 =  {5.0};
+
+            std::vector<double> value_2 = {10.0, 20.0, 30.0, 40.0};
+            std::vector<double> test_value_2 =  {50.0};
+
+            std::vector<double> value_3 = {-1.0, -2.0, -3.0, -4.0};
+            std::vector<double> test_value_3 =  {-5.0};
+
+            _feat_1 = std::make_shared<FeatureNode>(0, "A", value_1, test_value_1, Unit("m"));
+            _feat_2 = std::make_shared<FeatureNode>(1, "B", value_2, test_value_2, Unit("m"));
+            _feat_3 = std::make_shared<FeatureNode>(2, "C", value_2, test_value_2, Unit("s"));
+            _feat_4 = std::make_shared<FeatureNode>(3, "C", value_3, test_value_3, Unit("s"));
+
+            _phi = {_feat_1, _feat_2, _feat_3, _feat_4};
+            _phi.push_back(std::make_shared<SubNode>(_feat_1, _feat_2, 4, -1e-50, 1e50));
+        }
+
+        node_ptr _feat_1;
+        node_ptr _feat_2;
+        node_ptr _feat_3;
+        node_ptr _feat_4;
+        node_ptr _add_test;
+
+        std::vector<node_ptr> _phi;
+    };
+
+    TEST_F(AddNodeTest, GeneratorTest)
+    {
+        int feat_ind = _phi.size();
+
+        generateAddNode(_phi, _phi[0], _phi[2], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 5) << " (AddNode created when units do not match.)";
+
+        generateAddNode(_phi, _phi[0], _phi[3], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 5) << " (Creation of AddNode led to a feature with a constant value)";
+
+        generateAddNode(_phi, _phi[0], _phi[1], feat_ind, 1e-50, 1.0);
+        EXPECT_EQ(_phi.size(), 5) << " (AddNode created with an absolute value above the upper bound)";
+
+        generateAddNode(_phi, _phi[0], _phi[1], feat_ind, 1e3, 1e50);
+        EXPECT_EQ(_phi.size(), 5) << " (AddNode created with an absolute value below the lower bound)";
+
+        generateAddNode(_phi, _phi[0], _phi[0], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 5) << " (AddNode created with only one primary feature present)";
+
+        generateAddNode(_phi, _phi[4], _phi[4], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 5) << " (AddNode created when some terms cancel out)";
+
+        generateAddNode(_phi, _phi[0], _phi[1], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 6) << " (Failure to create a valid feature)";
+
+        generateAddNode(_phi, _phi[5], _phi[4], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 6) << " (AddNode created that is a duplicate of a second feature)";
+    }
+
+    TEST_F(AddNodeTest, ConstructorTest)
+    {
+        int feat_ind = _phi.size();
+
+        try
+        {
+            _add_test = std::make_shared<AddNode>(_phi[0], _phi[2], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (AddNode created when units do not match.)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _add_test = std::make_shared<AddNode>(_phi[0], _phi[3], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (Creation of AddNode led to a feature with a constant value)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _add_test = std::make_shared<AddNode>(_phi[0], _phi[1], feat_ind, 1e-50, 1.0);
+            EXPECT_TRUE(false) << " (AddNode created with an absolute value above the upper bound)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _add_test = std::make_shared<AddNode>(_phi[0], _phi[1], feat_ind, 1e3, 1e50);
+            EXPECT_TRUE(false) << " (AddNode created with an absolute value below the lower bound)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _add_test = std::make_shared<AddNode>(_phi[0], _phi[0], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (AddNode created with only one primary feature present)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+        try
+        {
+            _add_test = std::make_shared<AddNode>(_phi[1], _phi[4], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (AddNode created when some terms cancel out)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _add_test = std::make_shared<AddNode>(_phi[0], _phi[1], feat_ind, 1e-50, 1e50);
+            ++feat_ind;
+        }
+        catch(const InvalidFeatureException& e)
+        {
+            ASSERT_TRUE(false) << " (Failure to create a valid feature)";
+        }
+        try
+        {
+            _add_test = std::make_shared<AddNode>(_add_test, _add_test, feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (AddNode created that is a duplicate of a second feature)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+    }
+
+    TEST_F(AddNodeTest, AttributesTest)
+    {
+        _add_test = std::make_shared<AddNode>(_phi[0], _phi[1], 5, 1e-50, 1e50);
+        _add_test = std::make_shared<AddNode>(_add_test, _phi[1], 6, 1e-50, 1e50);
+
+        EXPECT_EQ(_add_test->rung(), 2);
+
+        EXPECT_EQ(_add_test->value_ptr()[0], 21.0);
+        EXPECT_EQ(_add_test->test_value_ptr()[0], 105.0);
+
+        EXPECT_EQ(_add_test->value()[0], 21.0);
+        EXPECT_EQ(_add_test->test_value()[0], 105.0);
+
+        EXPECT_STREQ(_add_test->unit().toString().c_str(), "m");
+
+        EXPECT_STREQ(_add_test->expr().c_str(), "((A + B) + B)");
+        EXPECT_STREQ(_add_test->postfix_expr().c_str(), "0|1|add|1|add");
+    }
+}
diff --git a/tests/googletest/feature_creation/feature_generation/test_cb_node.cc b/tests/googletest/feature_creation/feature_generation/test_cb_node.cc
new file mode 100644
index 0000000000000000000000000000000000000000..6099b8e5e2dd795e09335f97248a8923ed74baa2
--- /dev/null
+++ b/tests/googletest/feature_creation/feature_generation/test_cb_node.cc
@@ -0,0 +1,146 @@
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/cb/cube.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/cbrt/cube_root.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/inv/inverse.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/sq/square.hpp>
+
+#include <feature_creation/node/value_storage/nodes_value_containers.hpp>
+#include <feature_creation/node/FeatureNode.hpp>
+#include "gtest/gtest.h"
+#include <math.h>
+
+namespace
+{
+    class CbNodeTest : public ::testing::Test
+    {
+    protected:
+        void SetUp() override
+        {
+            node_value_arrs::initialize_values_arr(4, 1, 4);
+
+            std::vector<double> value_1 = {1.0, 2.0, 3.0, 8.0};
+            std::vector<double> test_value_1 =  {2.0};
+
+            std::vector<double> value_2 = {1.0, 2.0 * M_PI, 4.0 * M_PI, 6.0 * M_PI};
+            std::vector<double> test_value_2 =  {5.0};
+
+            std::vector<double> value_3 = {-1.0, 2.0, 3.0, 4.0};
+            std::vector<double> test_value_3 =  {0.0};
+
+            _feat_1 = std::make_shared<FeatureNode>(0, "A", value_1, test_value_1, Unit("m"));
+            _feat_2 = std::make_shared<FeatureNode>(1, "B", value_1, test_value_1, Unit("m"));
+            _feat_3 = std::make_shared<FeatureNode>(2, "C", value_2, test_value_2, Unit("m"));
+            _feat_4 = std::make_shared<FeatureNode>(3, "D", value_3, test_value_3, Unit("m"));
+
+            _phi = {_feat_1, _feat_2, _feat_3, _feat_4};
+            _phi.push_back(std::make_shared<InvNode>(_feat_1, 6, 1e-50, 1e50));
+            _phi.push_back(std::make_shared<SqNode>(_feat_1, 11, 1e-50, 1e50));
+            _phi.push_back(std::make_shared<CbrtNode>(_feat_1, 11, 1e-50, 1e50));
+        }
+
+        node_ptr _feat_1;
+        node_ptr _feat_2;
+        node_ptr _feat_3;
+        node_ptr _feat_4;
+
+        node_ptr _cb_test;
+
+        std::vector<node_ptr> _phi;
+    };
+
+    TEST_F(CbNodeTest, GeneratorTest)
+    {
+        int feat_ind = _phi.size();
+        int phi_sz = _phi.size();
+
+        generateCbNode(_phi, _phi[0], feat_ind, 1e-50, 1e-40);
+        EXPECT_EQ(_phi.size(), phi_sz) << " (CbNode created with an absolute value above the upper bound)";
+
+        generateCbNode(_phi, _phi[0], feat_ind, 1e10, 1e50);
+        EXPECT_EQ(_phi.size(), phi_sz) << " (CbNode created with an absolute value below the lower bound)";
+
+        generateCbNode(_phi, _phi[4], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), phi_sz) << " (CbNode created from a InvNode)";
+
+        generateCbNode(_phi, _phi[5], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), phi_sz) << " (CbNode created from a SqNode)";
+
+        generateCbNode(_phi, _phi[6], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), phi_sz) << " (CbNode created from a CbrtNode)";
+
+        generateCbNode(_phi, _phi[0], feat_ind, 1e-50, 1e50);
+        ++phi_sz;
+        EXPECT_EQ(_phi.size(), phi_sz) << " (Failure to create a valid feature)";
+    }
+
+    TEST_F(CbNodeTest, ConstructorTest)
+    {
+        int feat_ind = _phi.size();
+        try
+        {
+            _cb_test = std::make_shared<CbNode>(_phi[0], feat_ind, 1e-50, 1e-40);
+            EXPECT_TRUE(false) << " (CbNode created with an absolute value above the upper bound)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _cb_test = std::make_shared<CbNode>(_phi[0], feat_ind, 1e10, 1e50);
+            EXPECT_TRUE(false) << " (CbNode created with an absolute value below the lower bound)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _cb_test = std::make_shared<CbNode>(_phi[4], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (CbNode created from a InvNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _cb_test = std::make_shared<CbNode>(_phi[5], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (CbNode created from a SqNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _cb_test = std::make_shared<CbNode>(_phi[6], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (CbNode created from a CbrtNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _cb_test = std::make_shared<CbNode>(_phi[0], feat_ind, 1e-50, 1e50);
+            ++feat_ind;
+        }
+        catch(const InvalidFeatureException& e)
+        {
+            ASSERT_TRUE(false) << " (Failure to create a valid feature)";
+        }
+    }
+
+    TEST_F(CbNodeTest, AttributesTest)
+    {
+        _cb_test = std::make_shared<CbNode>(_phi[0], 5, 1e-50, 1e50);
+
+        EXPECT_EQ(_cb_test->rung(), 1);
+
+        EXPECT_EQ(_cb_test->value_ptr()[1], 8.0);
+        EXPECT_EQ(_cb_test->test_value_ptr()[0], 8.0);
+
+        EXPECT_EQ(_cb_test->value()[1], 8.0);
+        EXPECT_EQ(_cb_test->test_value()[0], 8.0);
+
+        EXPECT_STREQ(_cb_test->unit().toString().c_str(), "m^3");
+
+        EXPECT_STREQ(_cb_test->expr().c_str(), "(A)^3");
+        EXPECT_STREQ(_cb_test->postfix_expr().c_str(), "0|cb");
+    }
+}
diff --git a/tests/googletest/feature_creation/feature_generation/test_cbrt_node.cc b/tests/googletest/feature_creation/feature_generation/test_cbrt_node.cc
new file mode 100644
index 0000000000000000000000000000000000000000..5760cb80981973efba51448fa005ca2a5afc0e34
--- /dev/null
+++ b/tests/googletest/feature_creation/feature_generation/test_cbrt_node.cc
@@ -0,0 +1,171 @@
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/cb/cube.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/cbrt/cube_root.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/inv/inverse.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/sp/sixth_power.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/sq/square.hpp>
+
+#include <feature_creation/node/value_storage/nodes_value_containers.hpp>
+#include <feature_creation/node/FeatureNode.hpp>
+#include "gtest/gtest.h"
+#include <math.h>
+
+namespace
+{
+    class CbrtNodeTest : public ::testing::Test
+    {
+    protected:
+        void SetUp() override
+        {
+            node_value_arrs::initialize_values_arr(4, 1, 4);
+
+            std::vector<double> value_1 = {1.0, 2.0, 3.0, 8.0};
+            std::vector<double> test_value_1 =  {8.0};
+
+            std::vector<double> value_2 = {1.0, 2.0 * M_PI, 4.0 * M_PI, 6.0 * M_PI};
+            std::vector<double> test_value_2 =  {5.0};
+
+            std::vector<double> value_3 = {-1.0, 2.0, 3.0, 4.0};
+            std::vector<double> test_value_3 =  {0.0};
+
+            _feat_1 = std::make_shared<FeatureNode>(0, "A", value_1, test_value_1, Unit("m"));
+            _feat_2 = std::make_shared<FeatureNode>(1, "B", value_1, test_value_1, Unit("m"));
+            _feat_3 = std::make_shared<FeatureNode>(2, "C", value_2, test_value_2, Unit("m"));
+            _feat_4 = std::make_shared<FeatureNode>(3, "D", value_3, test_value_3, Unit("m"));
+
+            _phi = {_feat_1, _feat_2, _feat_3, _feat_4};
+            _phi.push_back(std::make_shared<InvNode>(_feat_1, 6, 1e-50, 1e50));
+            _phi.push_back(std::make_shared<SqNode>(_feat_1, 11, 1e-50, 1e50));
+            _phi.push_back(std::make_shared<CbNode>(_feat_1, 12, 1e-50, 1e50));
+            _phi.push_back(std::make_shared<SixPowNode>(_feat_1, 12, 1e-50, 1e50));
+        }
+
+        node_ptr _feat_1;
+        node_ptr _feat_2;
+        node_ptr _feat_3;
+        node_ptr _feat_4;
+
+        node_ptr _cbrt_test;
+
+        std::vector<node_ptr> _phi;
+    };
+
+    TEST_F(CbrtNodeTest, GeneratorTest)
+    {
+        int feat_ind = _phi.size();
+        int phi_sz = _phi.size();
+
+        generateCbrtNode(_phi, _phi[3], feat_ind, 1e-50, 1e-40);
+        EXPECT_EQ(_phi.size(), phi_sz) << " (CbrtNode created with a feature that has a value < 0.0)";
+
+        generateCbrtNode(_phi, _phi[0], feat_ind, 1e-50, 1e-40);
+        EXPECT_EQ(_phi.size(), phi_sz) << " (CbrtNode created with an absolute value above the upper bound)";
+
+        generateCbrtNode(_phi, _phi[0], feat_ind, 1e10, 1e50);
+        EXPECT_EQ(_phi.size(), phi_sz) << " (CbrtNode created with an absolute value below the lower bound)";
+
+        generateCbrtNode(_phi, _phi[4], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), phi_sz) << " (CbrtNode created from a InvNode)";
+
+        generateCbrtNode(_phi, _phi[5], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), phi_sz) << " (CbrtNode created from a SqNode)";
+
+        generateCbrtNode(_phi, _phi[6], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), phi_sz) << " (CbrtNode created from a CbNode)";
+
+        generateCbrtNode(_phi, _phi[7], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), phi_sz) << " (CbrtNode created from a SixPowNode)";
+
+        generateCbrtNode(_phi, _phi[0], feat_ind, 1e-50, 1e50);
+        ++phi_sz;
+        EXPECT_EQ(_phi.size(), phi_sz) << " (Failure to create a valid feature)";
+    }
+
+    TEST_F(CbrtNodeTest, ConstructorTest)
+    {
+        int feat_ind = _phi.size();
+
+        try
+        {
+            _cbrt_test = std::make_shared<CbrtNode>(_phi[3], feat_ind, 1e-50, 1e-40);
+            EXPECT_TRUE(false) << " (CbrtNode created with a feature that has a value < 0.0)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _cbrt_test = std::make_shared<CbrtNode>(_phi[0], feat_ind, 1e-50, 1e-40);
+            EXPECT_TRUE(false) << " (CbrtNode created with an absolute value above the upper bound)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _cbrt_test = std::make_shared<CbrtNode>(_phi[0], feat_ind, 1e10, 1e50);
+            EXPECT_TRUE(false) << " (CbrtNode created with an absolute value below the lower bound)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _cbrt_test = std::make_shared<CbrtNode>(_phi[4], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (CbrtNode created from a InvNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _cbrt_test = std::make_shared<CbrtNode>(_phi[5], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (CbrtNode created from a SqNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _cbrt_test = std::make_shared<CbrtNode>(_phi[6], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (CbrtNode created from a CbNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _cbrt_test = std::make_shared<CbrtNode>(_phi[7], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (CbrtNode created from a SixPowNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _cbrt_test = std::make_shared<CbrtNode>(_phi[0], feat_ind, 1e-50, 1e50);
+            ++feat_ind;
+        }
+        catch(const InvalidFeatureException& e)
+        {
+            ASSERT_TRUE(false) << " (Failure to create a valid feature)";
+        }
+    }
+
+    TEST_F(CbrtNodeTest, AttributesTest)
+    {
+        _cbrt_test = std::make_shared<CbrtNode>(_phi[0], 5, 1e-50, 1e50);
+
+        EXPECT_EQ(_cbrt_test->rung(), 1);
+
+        EXPECT_EQ(_cbrt_test->value_ptr()[3], 2.0);
+        EXPECT_EQ(_cbrt_test->test_value_ptr()[0], 2.0);
+
+        EXPECT_EQ(_cbrt_test->value()[3], 2.0);
+        EXPECT_EQ(_cbrt_test->test_value()[0], 2.0);
+
+        EXPECT_STREQ(_cbrt_test->unit().toString().c_str(), "m^0.333333");
+
+        EXPECT_STREQ(_cbrt_test->expr().c_str(), "cbrt(A)");
+        EXPECT_STREQ(_cbrt_test->postfix_expr().c_str(), "0|cbrt");
+    }
+}
diff --git a/tests/googletest/feature_creation/feature_generation/test_cos_node.cc b/tests/googletest/feature_creation/feature_generation/test_cos_node.cc
new file mode 100644
index 0000000000000000000000000000000000000000..9b4f270a78e6e80738c63fb95b71f1a3cb8110cd
--- /dev/null
+++ b/tests/googletest/feature_creation/feature_generation/test_cos_node.cc
@@ -0,0 +1,147 @@
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/cos/cos.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/sin/sin.hpp>
+#include <feature_creation/node/value_storage/nodes_value_containers.hpp>
+#include <feature_creation/node/FeatureNode.hpp>
+#include "gtest/gtest.h"
+#include <math.h>
+
+namespace
+{
+    class CosNodeTest : public ::testing::Test
+    {
+    protected:
+        void SetUp() override
+        {
+            node_value_arrs::initialize_values_arr(4, 1, 4);
+
+            std::vector<double> value_1 = {0.0, 2.0, 3.0, 4.0};
+            std::vector<double> test_value_1 =  {0.0};
+
+            std::vector<double> value_2 = {0.0, 2.0 * M_PI, 4.0 * M_PI, 6.0 * M_PI};
+            std::vector<double> test_value_2 =  {5.0};
+
+            _feat_1 = std::make_shared<FeatureNode>(0, "A", value_1, test_value_1, Unit());
+            _feat_2 = std::make_shared<FeatureNode>(1, "B", value_1, test_value_1, Unit("m"));
+            _feat_3 = std::make_shared<FeatureNode>(2, "C", value_2, test_value_2, Unit());
+
+            _phi = {_feat_1, _feat_2, _feat_3};
+            _phi.push_back(std::make_shared<SinNode>(_feat_1, 3, 1e-50, 1e50));
+        }
+
+        node_ptr _feat_1;
+        node_ptr _feat_2;
+        node_ptr _feat_3;
+
+        node_ptr _cos_test;
+
+        std::vector<node_ptr> _phi;
+    };
+
+    TEST_F(CosNodeTest, GeneratorTest)
+    {
+        int feat_ind = _phi.size();
+
+        generateCosNode(_phi, _phi[2], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 4) << " (Creation of CosNode led to a feature with a constant value)";
+
+        generateCosNode(_phi, _phi[1], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 4) << "(CosNode created with a feature that is not unitless)";
+
+        generateCosNode(_phi, _phi[0], feat_ind, 1e-50, 1e-40);
+        EXPECT_EQ(_phi.size(), 4) << " (CosNode created with an absolute value above the upper bound)";
+
+        generateCosNode(_phi, _phi[0], feat_ind, 1e3, 1e50);
+        EXPECT_EQ(_phi.size(), 4) << " (CosNode created with an absolute value below the lower bound)";
+
+        generateCosNode(_phi, _phi[3], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 4) << " (CosNode created from another a SinNode)";
+
+        generateCosNode(_phi, _phi[0], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 5) << " (Failure to create a valid feature)";
+
+        generateCosNode(_phi, _phi[4], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 5) << " (CosNode created from another CosNode)";
+
+    }
+
+    TEST_F(CosNodeTest, ConstructorTest)
+    {
+        int feat_ind = _phi.size();
+
+        try
+        {
+            _cos_test = std::make_shared<CosNode>(_phi[2], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (Creation of CosNode led to a feature with a constant value)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _cos_test = std::make_shared<CosNode>(_phi[1], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << "(CosNode created with a feature that is not unitless)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _cos_test = std::make_shared<CosNode>(_phi[0], feat_ind, 1e-50, 1e-40);
+            EXPECT_TRUE(false) << " (CosNode created with an absolute value above the upper bound)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _cos_test = std::make_shared<CosNode>(_phi[0], feat_ind, 1e3, 1e50);
+            EXPECT_TRUE(false) << " (CosNode created with an absolute value below the lower bound)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _cos_test = std::make_shared<CosNode>(_phi[3], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (CosNode created from another a SinNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _cos_test = std::make_shared<CosNode>(_phi[0], feat_ind, 1e-50, 1e50);
+            ++feat_ind;
+        }
+        catch(const InvalidFeatureException& e)
+        {
+            ASSERT_TRUE(false) << " (Failure to create a valid feature)";
+        }
+
+        try
+        {
+            _cos_test = std::make_shared<CosNode>(_cos_test, feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (CosNode created from another CosNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+    }
+
+    TEST_F(CosNodeTest, AttributesTest)
+    {
+        _cos_test = std::make_shared<CosNode>(_phi[0], 5, 1e-50, 1e50);
+
+        EXPECT_EQ(_cos_test->rung(), 1);
+
+        EXPECT_EQ(_cos_test->value_ptr()[0], 1.0);
+        EXPECT_EQ(_cos_test->test_value_ptr()[0], 1.0);
+
+        EXPECT_EQ(_cos_test->value()[0], 1.0);
+        EXPECT_EQ(_cos_test->test_value()[0], 1.0);
+
+        EXPECT_STREQ(_cos_test->unit().toString().c_str(), "Unitless");
+
+        EXPECT_STREQ(_cos_test->expr().c_str(), "cos(A)");
+        EXPECT_STREQ(_cos_test->postfix_expr().c_str(), "0|cos");
+    }
+}
diff --git a/tests/googletest/feature_creation/feature_generation/test_div_node.cc b/tests/googletest/feature_creation/feature_generation/test_div_node.cc
new file mode 100644
index 0000000000000000000000000000000000000000..42cd9c6d8d596d8d55cdf9f5b3e69d4524bdc12a
--- /dev/null
+++ b/tests/googletest/feature_creation/feature_generation/test_div_node.cc
@@ -0,0 +1,209 @@
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/div/divide.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/inv/inverse.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/mult/multiply.hpp>
+#include <feature_creation/node/value_storage/nodes_value_containers.hpp>
+#include <feature_creation/node/FeatureNode.hpp>
+#include "gtest/gtest.h"
+
+namespace
+{
+    class DivNodeTest : public ::testing::Test
+    {
+    protected:
+        void SetUp() override
+        {
+            node_value_arrs::initialize_values_arr(4, 1, 4);
+
+            std::vector<double> value_1 = {1.0, 2.0, 3.0, 4.0};
+            std::vector<double> test_value_1 =  {5.0};
+
+            std::vector<double> value_2 = {10.0, 25.0, 30.0, 40.0};
+            std::vector<double> test_value_2 =  {50.0};
+
+            std::vector<double> value_3 = {0.5, 1.0, 1.5, 2.0};
+            std::vector<double> test_value_3 =  {-5.0};
+
+            _feat_1 = std::make_shared<FeatureNode>(0, "A", value_1, test_value_1, Unit("m"));
+            _feat_2 = std::make_shared<FeatureNode>(1, "B", value_2, test_value_2, Unit("m"));
+            _feat_3 = std::make_shared<FeatureNode>(2, "C", value_2, test_value_2, Unit("s"));
+            _feat_4 = std::make_shared<FeatureNode>(3, "D", value_3, test_value_3, Unit("s"));
+
+            _phi = {_feat_1, _feat_2, _feat_3, _feat_4};
+            _phi.push_back(std::make_shared<MultNode>(_feat_1, _feat_2, 4, -1e-50, 1e50));
+            _phi.push_back(std::make_shared<InvNode>(_feat_2, 5, -1e-50, 1e50));
+        }
+
+        node_ptr _feat_1;
+        node_ptr _feat_2;
+        node_ptr _feat_3;
+        node_ptr _feat_4;
+        node_ptr _div_test;
+
+        std::vector<node_ptr> _phi;
+    };
+
+    TEST_F(DivNodeTest, GeneratorTest)
+    {
+        int feat_ind = _phi.size();
+
+        generateDivNode(_phi, _phi[0], _phi[3], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 6) << " (Creation of DivNode led to a feature with a constant value)";
+
+        generateDivNode(_phi, _phi[0], _phi[1], feat_ind, 1e-50, 1e-40);
+        EXPECT_EQ(_phi.size(), 6) << " (DivNode created with an absolute value above the upper bound)";
+
+        generateDivNode(_phi, _phi[0], _phi[1], feat_ind, 1e3, 1e50);
+        EXPECT_EQ(_phi.size(), 6) << " (DivNode created with an absolute value below the lower bound)";
+
+        generateDivNode(_phi, _phi[0], _phi[0], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 6) << " (DivNode created with only one primary feature present)";
+
+        generateDivNode(_phi, _phi[4], _phi[4], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 6) << " (DivNode created when some terms cancel out)";
+
+        generateDivNode(_phi, _phi[0], _phi[5], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 6) << " (DivNode created when one of the input features is an InvNode)";
+
+        generateDivNode(_phi, _phi[5], _phi[0], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 6) << " (DivNode created when one of the input features is an InvNode)";
+
+        generateDivNode(_phi, _phi[0], _phi[1], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 7) << " (Failure to create a valid feature)";
+
+        generateDivNode(_phi, _phi[6], _phi[4], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 7) << " (DivNode created that is a duplicate of a second feature)";
+
+        generateDivNode(_phi, _phi[2], _phi[6], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 7) << " (DivNode created when the second input feature is a DivvNode)";
+
+        generateDivNode(_phi, _phi[0], _phi[2], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 8) << " (Failure to create valid feature.)";
+
+        generateDivNode(_phi, _phi[6], _phi[2], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 9) << " (Failure to create valid feature.)";
+    }
+
+    TEST_F(DivNodeTest, ConstructorTest)
+    {
+        int feat_ind = _phi.size();
+
+        try
+        {
+            _div_test = std::make_shared<DivNode>(_phi[0], _phi[3], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (Creation of DivNode led to a feature with a constant value)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _div_test = std::make_shared<DivNode>(_phi[0], _phi[1], feat_ind, 1e-50, 1e-40);
+            EXPECT_TRUE(false) << " (DivNode created with an absolute value above the upper bound)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _div_test = std::make_shared<DivNode>(_phi[0], _phi[1], feat_ind, 1e3, 1e50);
+            EXPECT_TRUE(false) << " (DivNode created with an absolute value below the lower bound)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _div_test = std::make_shared<DivNode>(_phi[0], _phi[0], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (DivNode created with only one primary feature present)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _div_test = std::make_shared<DivNode>(_phi[4], _phi[4], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (DivNode created when some terms cancel out)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _div_test = std::make_shared<DivNode>(_phi[0], _phi[5], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (DivNode created when one of the input features is an InvNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _div_test = std::make_shared<DivNode>(_phi[5], _phi[0], feat_ind, 1e-50, 1e50);
+            ++feat_ind;
+            EXPECT_TRUE(false) << " (DivNode created when one of the input features is an InvNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _div_test = std::make_shared<DivNode>(_phi[0], _phi[1], feat_ind, 1e-50, 1e50);
+        }
+        catch(const InvalidFeatureException& e)
+        {
+            EXPECT_TRUE(false) << " (Failure to create a valid feature)";
+        }
+
+        try
+        {
+            _div_test = std::make_shared<DivNode>(_div_test, _phi[4], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (DivNode created that is a duplicate of a second feature)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _div_test = std::make_shared<DivNode>(_phi[2], _div_test, feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (DivNode created when the second input feature is a DivNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _div_test = std::make_shared<DivNode>(_phi[0], _phi[2], feat_ind, 1e-50, 1e50);
+        }
+        catch(const InvalidFeatureException& e)
+        {
+            EXPECT_TRUE(false) << " (Failure to create valid feature.)";
+        }
+
+        try
+        {
+            _div_test = std::make_shared<DivNode>(_div_test, _phi[2], feat_ind, 1e-50, 1e50);
+        }
+        catch(const InvalidFeatureException& e)
+        {
+            EXPECT_TRUE(false) << " (Failure to create valid feature.)";
+        }
+    }
+
+    TEST_F(DivNodeTest, AttributesTest)
+    {
+        _div_test = std::make_shared<DivNode>(_phi[0], _phi[1], 5, 1e-50, 1e50);
+        _div_test = std::make_shared<DivNode>(_div_test, _phi[1], 6, 1e-50, 1e50);
+
+        EXPECT_EQ(_div_test->rung(), 2);
+
+        EXPECT_EQ(_div_test->value_ptr()[0], 0.01);
+        EXPECT_EQ(_div_test->test_value_ptr()[0], 0.002);
+
+        EXPECT_EQ(_div_test->value()[0], 0.01);
+        EXPECT_EQ(_div_test->test_value()[0], 0.002);
+
+        EXPECT_STREQ(_div_test->unit().toString().c_str(), "m^-1");
+
+        EXPECT_STREQ(_div_test->expr().c_str(), "[([(A) / (B)]) / (B)]");
+        EXPECT_STREQ(_div_test->postfix_expr().c_str(), "0|1|div|1|div");
+    }
+}
diff --git a/tests/googletest/feature_creation/feature_generation/test_exp_node.cc b/tests/googletest/feature_creation/feature_generation/test_exp_node.cc
new file mode 100644
index 0000000000000000000000000000000000000000..3d15c29a367ea5e8e9d0f0c5f01a4a1133edce83
--- /dev/null
+++ b/tests/googletest/feature_creation/feature_generation/test_exp_node.cc
@@ -0,0 +1,176 @@
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/add/add.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/exp/exponential.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/log/log.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/neg_exp/negative_exponential.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/sub/subtract.hpp>
+
+#include <feature_creation/node/value_storage/nodes_value_containers.hpp>
+#include <feature_creation/node/FeatureNode.hpp>
+#include "gtest/gtest.h"
+#include <math.h>
+
+namespace
+{
+    class ExpNodeTest : public ::testing::Test
+    {
+    protected:
+        void SetUp() override
+        {
+            node_value_arrs::initialize_values_arr(4, 1, 3);
+
+            std::vector<double> value_1 = {0.0, 2.0, 3.0, 4.0};
+            std::vector<double> test_value_1 =  {0.0};
+
+            std::vector<double> value_2 = {1.0, 2.0 * M_PI, 4.0 * M_PI, 6.0 * M_PI};
+            std::vector<double> test_value_2 =  {5.0};
+
+            _feat_1 = std::make_shared<FeatureNode>(0, "A", value_1, test_value_1, Unit());
+            _feat_2 = std::make_shared<FeatureNode>(1, "B", value_1, test_value_1, Unit("m"));
+            _feat_3 = std::make_shared<FeatureNode>(2, "C", value_2, test_value_2, Unit());
+
+            _phi = {_feat_1, _feat_2, _feat_3};
+            _phi.push_back(std::make_shared<LogNode>(_feat_3, 3, 1e-50, 1e50));
+            _phi.push_back(std::make_shared<NegExpNode>(_feat_1, 3, 1e-50, 1e50));
+            _phi.push_back(std::make_shared<AddNode>(_feat_1, _feat_3, 3, 1e-50, 1e50));
+            _phi.push_back(std::make_shared<SubNode>(_feat_1, _feat_3, 3, 1e-50, 1e50));
+        }
+
+        node_ptr _feat_1;
+        node_ptr _feat_2;
+        node_ptr _feat_3;
+
+        node_ptr _exp_test;
+
+        std::vector<node_ptr> _phi;
+    };
+
+    TEST_F(ExpNodeTest, GeneratorTest)
+    {
+        int feat_ind = _phi.size();
+
+        generateExpNode(_phi, _phi[1], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 7) << "(ExpNode created with a feature that is not unitless)";
+
+        generateExpNode(_phi, _phi[0], feat_ind, 1e-50, 1e-40);
+        EXPECT_EQ(_phi.size(), 7) << " (ExpNode created with an absolute value above the upper bound)";
+
+        generateExpNode(_phi, _phi[0], feat_ind, 1e3, 1e50);
+        EXPECT_EQ(_phi.size(), 7) << " (ExpNode created with an absolute value below the lower bound)";
+
+        generateExpNode(_phi, _phi[3], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 7) << " (ExpNode created from a LogNode)";
+
+        generateExpNode(_phi, _phi[4], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 7) << " (ExpNode created from a NegExpNode)";
+
+        generateExpNode(_phi, _phi[5], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 7) << " (ExpNode created from a AddNode)";
+
+        generateExpNode(_phi, _phi[6], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 7) << " (ExpNode created from a SubNode)";
+
+        generateExpNode(_phi, _phi[0], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 8) << " (Failure to create a valid feature)";
+
+        generateExpNode(_phi, _phi[4], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 8) << " (ExpNode created from another ExpNode)";
+
+    }
+
+    TEST_F(ExpNodeTest, ConstructorTest)
+    {
+        int feat_ind = _phi.size();
+
+        try
+        {
+            _exp_test = std::make_shared<ExpNode>(_phi[1], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << "(ExpNode created with a feature that is not unitless)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _exp_test = std::make_shared<ExpNode>(_phi[0], feat_ind, 1e-50, 1e-40);
+            EXPECT_TRUE(false) << " (ExpNode created with an absolute value above the upper bound)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _exp_test = std::make_shared<ExpNode>(_phi[0], feat_ind, 1e3, 1e50);
+            EXPECT_TRUE(false) << " (ExpNode created with an absolute value below the lower bound)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _exp_test = std::make_shared<ExpNode>(_phi[3], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (ExpNode created from a LogNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _exp_test = std::make_shared<ExpNode>(_phi[4], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (ExpNode created from a NegExpNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _exp_test = std::make_shared<ExpNode>(_phi[5], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (ExpNode created from a AddNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _exp_test = std::make_shared<ExpNode>(_phi[6], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (ExpNode created from a SubNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _exp_test = std::make_shared<ExpNode>(_phi[0], feat_ind, 1e-50, 1e50);
+            ++feat_ind;
+        }
+        catch(const InvalidFeatureException& e)
+        {
+            ASSERT_TRUE(false) << " (Failure to create a valid feature)";
+        }
+
+        try
+        {
+            _exp_test = std::make_shared<ExpNode>(_exp_test, feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (ExpNode created from another ExpNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+    }
+
+    TEST_F(ExpNodeTest, AttributesTest)
+    {
+        _exp_test = std::make_shared<ExpNode>(_phi[0], 5, 1e-50, 1e50);
+
+        EXPECT_EQ(_exp_test->rung(), 1);
+
+        EXPECT_EQ(_exp_test->value_ptr()[0], 1.0);
+        EXPECT_EQ(_exp_test->test_value_ptr()[0], 1.0);
+
+        EXPECT_EQ(_exp_test->value()[0], 1.0);
+        EXPECT_EQ(_exp_test->test_value()[0], 1.0);
+
+        EXPECT_STREQ(_exp_test->unit().toString().c_str(), "Unitless");
+
+        EXPECT_STREQ(_exp_test->expr().c_str(), "exp(A)");
+        EXPECT_STREQ(_exp_test->postfix_expr().c_str(), "0|exp");
+    }
+}
diff --git a/tests/googletest/feature_creation/feature_generation/test_feat_node.cc b/tests/googletest/feature_creation/feature_generation/test_feat_node.cc
new file mode 100644
index 0000000000000000000000000000000000000000..75da02f179e6555784993d00eb5bbafb95f5d291
--- /dev/null
+++ b/tests/googletest/feature_creation/feature_generation/test_feat_node.cc
@@ -0,0 +1,74 @@
+#include <feature_creation/node/FeatureNode.hpp>
+#include <feature_creation/node/value_storage/nodes_value_containers.hpp>
+#include <feature_creation/node/FeatureNode.hpp>
+#include "gtest/gtest.h"
+
+namespace
+{
+    class FeatNodeTest : public ::testing::Test
+    {
+    protected:
+        void SetUp() override
+        {
+            node_value_arrs::initialize_values_arr(4, 1, 3);
+
+            _value_1 = {1.0, 2.0, 3.0, 4.0};
+            _test_value_1 =  {5.0};
+
+            _value_2 = {10.0, 10.0, 10.0, 10.0};
+            _test_value_2 =  {10.0};
+
+            _value_3 = {1.0, 2.0, 3.0, 1.0/0.0};
+            _test_value_3 =  {5.0};
+        }
+
+        std::vector<double> _value_1;
+        std::vector<double> _test_value_1;
+
+        std::vector<double> _value_2;
+        std::vector<double> _test_value_2;
+
+        std::vector<double> _value_3;
+        std::vector<double> _test_value_3;
+    };
+
+    TEST_F(FeatNodeTest, ConstructorTest)
+    {
+        node_ptr feat_1 = std::make_shared<FeatureNode>(0, "A", _value_1, _test_value_1, Unit("m"));
+        node_ptr feat_2 = std::make_shared<FeatureNode>(1, "B", _value_2, _test_value_2, Unit());
+        node_ptr feat_3 = std::make_shared<FeatureNode>(2, "C", _value_3, _test_value_3, Unit("m"));
+
+        EXPECT_FALSE(feat_1->is_const());
+        EXPECT_FALSE(feat_1->is_nan());
+        EXPECT_STREQ(feat_1->unit().toString().c_str(), "m");
+        EXPECT_STREQ(feat_1->expr().c_str(), "A");
+        EXPECT_STREQ(feat_1->postfix_expr().c_str(), "0");
+        EXPECT_EQ(feat_1->value()[0], _value_1[0]);
+        EXPECT_EQ(feat_1->test_value()[0], _test_value_1[0]);
+        EXPECT_EQ(feat_1->value_ptr()[0], _value_1[0]);
+        EXPECT_EQ(feat_1->test_value_ptr()[0], _test_value_1[0]);
+        EXPECT_EQ(feat_1->n_feats(), 0);
+
+        EXPECT_TRUE(feat_2->is_const());
+        EXPECT_FALSE(feat_2->is_nan());
+        EXPECT_STREQ(feat_2->unit().toString().c_str(), "Unitless");
+        EXPECT_STREQ(feat_2->expr().c_str(), "B");
+        EXPECT_STREQ(feat_2->postfix_expr().c_str(), "1");
+        EXPECT_EQ(feat_2->value()[0], _value_2[0]);
+        EXPECT_EQ(feat_2->test_value()[0], _test_value_2[0]);
+        EXPECT_EQ(feat_2->value_ptr()[0], _value_2[0]);
+        EXPECT_EQ(feat_2->test_value_ptr()[0], _test_value_2[0]);
+        EXPECT_EQ(feat_2->n_feats(), 0);
+
+        EXPECT_FALSE(feat_3->is_const());
+        EXPECT_TRUE(feat_3->is_nan());
+        EXPECT_STREQ(feat_3->unit().toString().c_str(), "m");
+        EXPECT_STREQ(feat_3->expr().c_str(), "C");
+        EXPECT_STREQ(feat_3->postfix_expr().c_str(), "2");
+        EXPECT_EQ(feat_3->value()[0], _value_3[0]);
+        EXPECT_EQ(feat_3->test_value()[0], _test_value_3[0]);
+        EXPECT_EQ(feat_3->value_ptr()[0], _value_3[0]);
+        EXPECT_EQ(feat_3->test_value_ptr()[0], _test_value_3[0]);
+        EXPECT_EQ(feat_3->n_feats(), 0);
+    }
+}
diff --git a/tests/googletest/feature_creation/feature_generation/test_inv_node.cc b/tests/googletest/feature_creation/feature_generation/test_inv_node.cc
new file mode 100644
index 0000000000000000000000000000000000000000..dae10cde8f2efd1793d9b5d074c0dac1abc9601b
--- /dev/null
+++ b/tests/googletest/feature_creation/feature_generation/test_inv_node.cc
@@ -0,0 +1,170 @@
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/div/divide.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/exp/exponential.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/div/divide.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/inv/inverse.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/neg_exp/negative_exponential.hpp>
+
+#include <feature_creation/node/value_storage/nodes_value_containers.hpp>
+#include <feature_creation/node/FeatureNode.hpp>
+#include "gtest/gtest.h"
+#include <math.h>
+
+namespace
+{
+    class InvNodeTest : public ::testing::Test
+    {
+    protected:
+        void SetUp() override
+        {
+            node_value_arrs::initialize_values_arr(4, 1, 4);
+
+            std::vector<double> value_1 = {1.0, 2.0, 3.0, 8.0};
+            std::vector<double> test_value_1 =  {2.0};
+
+            std::vector<double> value_2 = {1.0, 2.0 * M_PI, 4.0 * M_PI, 6.0 * M_PI};
+            std::vector<double> test_value_2 =  {5.0};
+
+            std::vector<double> value_3 = {0.0, 2.0, 3.0, 4.0};
+            std::vector<double> test_value_3 =  {0.0};
+
+            _feat_1 = std::make_shared<FeatureNode>(0, "A", value_1, test_value_1, Unit("m"));
+            _feat_2 = std::make_shared<FeatureNode>(1, "B", value_1, test_value_1, Unit());
+            _feat_3 = std::make_shared<FeatureNode>(2, "C", value_2, test_value_2, Unit("m"));
+            _feat_4 = std::make_shared<FeatureNode>(3, "D", value_3, test_value_3, Unit("m"));
+
+            _phi = {_feat_1, _feat_2, _feat_3, _feat_4};
+            _phi.push_back(std::make_shared<ExpNode>(_feat_2, 6, 1e-50, 1e50));
+            _phi.push_back(std::make_shared<NegExpNode>(_feat_2, 10, 1e-50, 1e50));
+            _phi.push_back(std::make_shared<DivNode>(_feat_1, _feat_3, 11, 1e-50, 1e50));
+        }
+
+        node_ptr _feat_1;
+        node_ptr _feat_2;
+        node_ptr _feat_3;
+        node_ptr _feat_4;
+
+        node_ptr _inv_test;
+
+        std::vector<node_ptr> _phi;
+    };
+
+    TEST_F(InvNodeTest, GeneratorTest)
+    {
+        int feat_ind = _phi.size();
+        int phi_sz = _phi.size();
+
+        generateInvNode(_phi, _phi[3], feat_ind, 1e-50, 1e-40);
+        EXPECT_EQ(_phi.size(), phi_sz) << " (InvNode created with a feature that has a value equal 0.0)";
+
+        generateInvNode(_phi, _phi[0], feat_ind, 1e-50, 1e-40);
+        EXPECT_EQ(_phi.size(), phi_sz) << " (InvNode created with an absolute value above the upper bound)";
+
+        generateInvNode(_phi, _phi[0], feat_ind, 1e10, 1e50);
+        EXPECT_EQ(_phi.size(), phi_sz) << " (InvNode created with an absolute value below the lower bound)";
+
+        generateInvNode(_phi, _phi[4], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), phi_sz) << " (InvNode created from a ExpNode)";
+
+        generateInvNode(_phi, _phi[5], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), phi_sz) << " (InvNode created from a NegExpNode)";
+
+        generateInvNode(_phi, _phi[6], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), phi_sz) << " (InvNode created from a DivNode)";
+
+        generateInvNode(_phi, _phi[0], feat_ind, 1e-50, 1e50);
+        ++phi_sz;
+        EXPECT_EQ(_phi.size(), phi_sz) << " (Failure to create a valid feature)";
+
+        generateInvNode(_phi, _phi[7], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), phi_sz) << " (InvNode created from another InvNode)";
+    }
+
+    TEST_F(InvNodeTest, ConstructorTest)
+    {
+        int feat_ind = _phi.size();
+
+        try
+        {
+            _inv_test = std::make_shared<InvNode>(_phi[3], feat_ind, 1e-50, 1e-40);
+            EXPECT_TRUE(false) << " (InvNode created with a feature that has a value equal to 0.0)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _inv_test = std::make_shared<InvNode>(_phi[0], feat_ind, 1e-50, 1e-40);
+            EXPECT_TRUE(false) << " (InvNode created with an absolute value above the upper bound)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _inv_test = std::make_shared<InvNode>(_phi[0], feat_ind, 1e10, 1e50);
+            EXPECT_TRUE(false) << " (InvNode created with an absolute value below the lower bound)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _inv_test = std::make_shared<InvNode>(_phi[4], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (InvNode created from a ExpNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _inv_test = std::make_shared<InvNode>(_phi[5], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (InvNode created from a NegExpNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _inv_test = std::make_shared<InvNode>(_phi[6], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (InvNode created from a DivNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _inv_test = std::make_shared<InvNode>(_phi[0], feat_ind, 1e-50, 1e50);
+            ++feat_ind;
+        }
+        catch(const InvalidFeatureException& e)
+        {
+            ASSERT_TRUE(false) << " (Failure to create a valid feature)";
+        }
+
+        try
+        {
+            _inv_test = std::make_shared<InvNode>(_inv_test, feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (InvNode created from another InvNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+    }
+
+    TEST_F(InvNodeTest, AttributesTest)
+    {
+        _inv_test = std::make_shared<InvNode>(_phi[0], 5, 1e-50, 1e50);
+
+        EXPECT_EQ(_inv_test->rung(), 1);
+
+        EXPECT_EQ(_inv_test->value_ptr()[1], 0.5);
+        EXPECT_EQ(_inv_test->test_value_ptr()[0], 0.5);
+
+        EXPECT_EQ(_inv_test->value()[1], 0.5);
+        EXPECT_EQ(_inv_test->test_value()[0], 0.50);
+
+        EXPECT_STREQ(_inv_test->unit().toString().c_str(), "m^-1");
+
+        EXPECT_STREQ(_inv_test->expr().c_str(), "1.0 / (A)");
+        EXPECT_STREQ(_inv_test->postfix_expr().c_str(), "0|inv");
+    }
+}
diff --git a/tests/googletest/feature_creation/feature_generation/test_log_node.cc b/tests/googletest/feature_creation/feature_generation/test_log_node.cc
new file mode 100644
index 0000000000000000000000000000000000000000..56ac1bbcc535ac11e4d919b0ef96c481c6f7130b
--- /dev/null
+++ b/tests/googletest/feature_creation/feature_generation/test_log_node.cc
@@ -0,0 +1,270 @@
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/cb/cube.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/cbrt/cube_root.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/div/divide.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/exp/exponential.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/inv/inverse.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/log/log.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/mult/multiply.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/neg_exp/negative_exponential.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/sp/sixth_power.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/sq/square.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/sqrt/square_root.hpp>
+
+#include <feature_creation/node/value_storage/nodes_value_containers.hpp>
+#include <feature_creation/node/FeatureNode.hpp>
+#include "gtest/gtest.h"
+#include <math.h>
+
+namespace
+{
+    class LogNodeTest : public ::testing::Test
+    {
+    protected:
+        void SetUp() override
+        {
+            node_value_arrs::initialize_values_arr(4, 1, 4);
+
+            std::vector<double> value_1 = {1.0, 2.0, 3.0, 4.0};
+            std::vector<double> test_value_1 =  {1.0};
+
+            std::vector<double> value_2 = {1.0, 2.0 * M_PI, 4.0 * M_PI, 6.0 * M_PI};
+            std::vector<double> test_value_2 =  {5.0};
+
+            std::vector<double> value_3 = {0.0, 2.0, 3.0, 4.0};
+            std::vector<double> test_value_3 =  {0.0};
+
+            _feat_1 = std::make_shared<FeatureNode>(0, "A", value_1, test_value_1, Unit());
+            _feat_2 = std::make_shared<FeatureNode>(1, "B", value_1, test_value_1, Unit("m"));
+            _feat_3 = std::make_shared<FeatureNode>(2, "C", value_2, test_value_2, Unit());
+            _feat_4 = std::make_shared<FeatureNode>(3, "D", value_3, test_value_3, Unit());
+
+            _phi = {_feat_1, _feat_2, _feat_3, _feat_4};
+            _phi.push_back(std::make_shared<ExpNode>(_feat_1, 4, 1e-50, 1e50));
+            _phi.push_back(std::make_shared<NegExpNode>(_feat_1, 5, 1e-50, 1e50));
+            _phi.push_back(std::make_shared<InvNode>(_feat_1, 6, 1e-50, 1e50));
+            _phi.push_back(std::make_shared<MultNode>(_feat_1, _feat_3, 7, 1e-50, 1e50));
+            _phi.push_back(std::make_shared<DivNode>(_feat_1, _feat_3, 8, 1e-50, 1e50));
+            _phi.push_back(std::make_shared<CbrtNode>(_feat_1, 9, 1e-50, 1e50));
+            _phi.push_back(std::make_shared<SqrtNode>(_feat_1, 10, 1e-50, 1e50));
+            _phi.push_back(std::make_shared<SqNode>(_feat_1, 11, 1e-50, 1e50));
+            _phi.push_back(std::make_shared<CbNode>(_feat_1, 12, 1e-50, 1e50));
+            _phi.push_back(std::make_shared<SixPowNode>(_feat_1, 13, 1e-50, 1e50));
+        }
+
+        node_ptr _feat_1;
+        node_ptr _feat_2;
+        node_ptr _feat_3;
+        node_ptr _feat_4;
+
+        node_ptr _log_test;
+
+        std::vector<node_ptr> _phi;
+    };
+
+    TEST_F(LogNodeTest, GeneratorTest)
+    {
+        int feat_ind = _phi.size();
+
+        generateLogNode(_phi, _phi[1], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 14) << "(LogNode created with a feature that is not unitless)";
+
+        generateLogNode(_phi, _phi[3], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 14) << "(LogNode created with a feature that has a non-positive value)";
+
+        generateLogNode(_phi, _phi[0], feat_ind, 1e-50, 1e-40);
+        EXPECT_EQ(_phi.size(), 14) << " (LogNode created with an absolute value above the upper bound)";
+
+        generateLogNode(_phi, _phi[0], feat_ind, 1e3, 1e50);
+        EXPECT_EQ(_phi.size(), 14) << " (LogNode created with an absolute value below the lower bound)";
+
+        generateLogNode(_phi, _phi[4], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 14) << " (LogNode created from a ExpNode)";
+
+        generateLogNode(_phi, _phi[5], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 14) << " (LogNode created from a NegExpNode)";
+
+        generateLogNode(_phi, _phi[6], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 14) << " (LogNode created from a InvNode)";
+
+        generateLogNode(_phi, _phi[7], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 14) << " (LogNode created from a MultNode)";
+
+        generateLogNode(_phi, _phi[8], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 14) << " (LogNode created from a DivNode)";
+
+        generateLogNode(_phi, _phi[9], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 14) << " (LogNode created from a CbrtNode)";
+
+        generateLogNode(_phi, _phi[10], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 14) << " (LogNode created from a SqrtNode)";
+
+        generateLogNode(_phi, _phi[11], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 14) << " (LogNode created from a SqNode)";
+
+        generateLogNode(_phi, _phi[12], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 14) << " (LogNode created from a CbNode)";
+
+        generateLogNode(_phi, _phi[13], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 14) << " (LogNode created from a SixPowNode)";
+
+        generateLogNode(_phi, _phi[0], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 15) << " (Failure to create a valid feature)";
+
+        generateLogNode(_phi, _phi[14], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 15) << " (LogNode created from another LogNode)";
+
+    }
+
+    TEST_F(LogNodeTest, ConstructorTest)
+    {
+        int feat_ind = _phi.size();
+
+        try
+        {
+            _log_test = std::make_shared<LogNode>(_phi[1], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << "(LogNode created with a feature that is not unitless)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _log_test = std::make_shared<LogNode>(_feat_4, feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << "(LogNode created with a feature that has a non-positive value)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _log_test = std::make_shared<LogNode>(_phi[0], feat_ind, 1e-50, 1e-40);
+            EXPECT_TRUE(false) << " (LogNode created with an absolute value above the upper bound)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _log_test = std::make_shared<LogNode>(_phi[0], feat_ind, 1e3, 1e50);
+            EXPECT_TRUE(false) << " (LogNode created with an absolute value below the lower bound)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _log_test = std::make_shared<LogNode>(_phi[4], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (LogNode created from a ExpNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _log_test = std::make_shared<LogNode>(_phi[5], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (LogNode created from a NegExpNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _log_test = std::make_shared<LogNode>(_phi[6], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (LogNode created from a InvNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _log_test = std::make_shared<LogNode>(_phi[7], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (LogNode created from a MultNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _log_test = std::make_shared<LogNode>(_phi[8], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (LogNode created from a DivNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _log_test = std::make_shared<LogNode>(_phi[9], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (LogNode created from a CbrtNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _log_test = std::make_shared<LogNode>(_phi[10], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (LogNode created from a SqrtNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _log_test = std::make_shared<LogNode>(_phi[11], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (LogNode created from a SqNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _log_test = std::make_shared<LogNode>(_phi[12], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (LogNode created from a CbNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _log_test = std::make_shared<LogNode>(_phi[13], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (LogNode created from a SixPowNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _log_test = std::make_shared<LogNode>(_phi[0], feat_ind, 1e-50, 1e50);
+            ++feat_ind;
+        }
+        catch(const InvalidFeatureException& e)
+        {
+            ASSERT_TRUE(false) << " (Failure to create a valid feature)";
+        }
+
+        try
+        {
+            _log_test = std::make_shared<LogNode>(_log_test, feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (LogNode created from another LogNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+    }
+
+    TEST_F(LogNodeTest, AttributesTest)
+    {
+        _log_test = std::make_shared<LogNode>(_phi[0], 5, 1e-50, 1e50);
+
+        EXPECT_EQ(_log_test->rung(), 1);
+
+        EXPECT_EQ(_log_test->value_ptr()[0], 0.0);
+        EXPECT_EQ(_log_test->test_value_ptr()[0], 0.0);
+
+        EXPECT_EQ(_log_test->value()[0], 0.0);
+        EXPECT_EQ(_log_test->test_value()[0], 0.0);
+
+        EXPECT_STREQ(_log_test->unit().toString().c_str(), "Unitless");
+
+        EXPECT_STREQ(_log_test->expr().c_str(), "log(A)");
+        EXPECT_STREQ(_log_test->postfix_expr().c_str(), "0|log");
+    }
+}
diff --git a/tests/googletest/feature_creation/feature_generation/test_model_node.cc b/tests/googletest/feature_creation/feature_generation/test_model_node.cc
new file mode 100644
index 0000000000000000000000000000000000000000..277b3de34071b6894818d3ea44d0e5b6af6c2d74
--- /dev/null
+++ b/tests/googletest/feature_creation/feature_generation/test_model_node.cc
@@ -0,0 +1,80 @@
+#include <feature_creation/node/ModelNode.hpp>
+#include <feature_creation/node/value_storage/nodes_value_containers.hpp>
+#include <feature_creation/node/FeatureNode.hpp>
+#include "gtest/gtest.h"
+
+namespace
+{
+    class ModelNodeTest : public ::testing::Test
+    {
+    protected:
+        void SetUp() override
+        {
+            node_value_arrs::initialize_values_arr(4, 1, 3);
+
+            _value_1 = {1.0, 2.0, 3.0, 4.0};
+            _test_value_1 =  {5.0};
+
+            _value_2 = {10.0, 10.0, 10.0, 1.0};
+            _test_value_2 =  {10.0};
+
+            _value_3 = {1.0, 2.0, 3.0, 1.0};
+            _test_value_3 =  {5.0};
+        }
+
+        std::vector<double> _value_1;
+        std::vector<double> _test_value_1;
+
+        std::vector<double> _value_2;
+        std::vector<double> _test_value_2;
+
+        std::vector<double> _value_3;
+        std::vector<double> _test_value_3;
+    };
+
+    TEST_F(ModelNodeTest, ConstructorTest)
+    {
+        node_ptr feat_1 = std::make_shared<ModelNode>(0, 1, "A", "$A$", "0", _value_1, _test_value_1, Unit("m"));
+        node_ptr feat_2 = std::make_shared<ModelNode>(1, 1, "B", "$B$", "1", _value_2, _test_value_2, Unit());
+        node_ptr feat_3 = std::make_shared<ModelNode>(2, 1, "C", "$C$", "2", _value_3, _test_value_3, Unit("m"));
+
+        EXPECT_FALSE(feat_1->is_const());
+        EXPECT_FALSE(feat_1->is_nan());
+        EXPECT_STREQ(feat_1->unit().toString().c_str(), "m");
+        EXPECT_STREQ(feat_1->expr().c_str(), "A");
+        EXPECT_STREQ(feat_1->postfix_expr().c_str(), "0");
+        EXPECT_EQ(feat_1->value()[0], _value_1[0]);
+        EXPECT_EQ(feat_1->test_value()[0], _test_value_1[0]);
+        EXPECT_EQ(feat_1->value_ptr()[0], _value_1[0]);
+        EXPECT_EQ(feat_1->test_value_ptr()[0], _test_value_1[0]);
+        EXPECT_EQ(feat_1->rung(), 1);
+        EXPECT_EQ(feat_1->n_feats(), 0);
+        EXPECT_EQ(feat_1->n_feats(), 0);
+
+        EXPECT_FALSE(feat_2->is_const());
+        EXPECT_FALSE(feat_2->is_nan());
+        EXPECT_STREQ(feat_2->unit().toString().c_str(), "Unitless");
+        EXPECT_STREQ(feat_2->expr().c_str(), "B");
+        EXPECT_STREQ(feat_2->postfix_expr().c_str(), "1");
+        EXPECT_EQ(feat_2->value()[0], _value_2[0]);
+        EXPECT_EQ(feat_2->test_value()[0], _test_value_2[0]);
+        EXPECT_EQ(feat_2->value_ptr()[0], _value_2[0]);
+        EXPECT_EQ(feat_2->test_value_ptr()[0], _test_value_2[0]);
+        EXPECT_EQ(feat_2->rung(), 1);
+        EXPECT_EQ(feat_2->n_feats(), 0);
+        EXPECT_EQ(feat_2->n_feats(), 0);
+
+        EXPECT_FALSE(feat_3->is_const());
+        EXPECT_FALSE(feat_3->is_nan());
+        EXPECT_STREQ(feat_3->unit().toString().c_str(), "m");
+        EXPECT_STREQ(feat_3->expr().c_str(), "C");
+        EXPECT_STREQ(feat_3->postfix_expr().c_str(), "2");
+        EXPECT_EQ(feat_3->value()[0], _value_3[0]);
+        EXPECT_EQ(feat_3->test_value()[0], _test_value_3[0]);
+        EXPECT_EQ(feat_3->value_ptr()[0], _value_3[0]);
+        EXPECT_EQ(feat_3->test_value_ptr()[0], _test_value_3[0]);
+        EXPECT_EQ(feat_3->rung(), 1);
+        EXPECT_EQ(feat_3->n_feats(), 0);
+        EXPECT_EQ(feat_3->n_feats(), 0);
+    }
+}
diff --git a/tests/googletest/feature_creation/feature_generation/test_mult_node.cc b/tests/googletest/feature_creation/feature_generation/test_mult_node.cc
new file mode 100644
index 0000000000000000000000000000000000000000..a582390c01c057a774629a32fe19654587a7377d
--- /dev/null
+++ b/tests/googletest/feature_creation/feature_generation/test_mult_node.cc
@@ -0,0 +1,160 @@
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/mult/multiply.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/div/divide.hpp>
+#include <feature_creation/node/value_storage/nodes_value_containers.hpp>
+#include <feature_creation/node/FeatureNode.hpp>
+#include "gtest/gtest.h"
+
+namespace
+{
+    class MultNodeTest : public ::testing::Test
+    {
+    protected:
+        void SetUp() override
+        {
+            node_value_arrs::initialize_values_arr(4, 1, 4);
+
+            std::vector<double> value_1 = {1.0, 2.0, 3.0, 4.0};
+            std::vector<double> test_value_1 =  {5.0};
+
+            std::vector<double> value_2 = {10.0, 25.0, 30.0, 40.0};
+            std::vector<double> test_value_2 =  {50.0};
+
+            std::vector<double> value_3 = {1.0, 0.5, 1.0/3.0, 0.25};
+            std::vector<double> test_value_3 =  {-5.0};
+
+            _feat_1 = std::make_shared<FeatureNode>(0, "A", value_1, test_value_1, Unit("m"));
+            _feat_2 = std::make_shared<FeatureNode>(1, "B", value_2, test_value_2, Unit("m"));
+            _feat_3 = std::make_shared<FeatureNode>(2, "C", value_2, test_value_2, Unit("s"));
+            _feat_4 = std::make_shared<FeatureNode>(3, "D", value_3, test_value_3, Unit("s"));
+
+            _phi = {_feat_1, _feat_2, _feat_3, _feat_4};
+            _phi.push_back(std::make_shared<DivNode>(_feat_1, _feat_2, 4, -1e-50, 1e50));
+        }
+
+        node_ptr _feat_1;
+        node_ptr _feat_2;
+        node_ptr _feat_3;
+        node_ptr _feat_4;
+        node_ptr _mult_test;
+
+        std::vector<node_ptr> _phi;
+    };
+
+    TEST_F(MultNodeTest, GeneratorTest)
+    {
+        int feat_ind = _phi.size();
+
+        generateMultNode(_phi, _phi[0], _phi[3], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 5) << " (Creation of MultNode led to a feature with a constant value)";
+
+        generateMultNode(_phi, _phi[0], _phi[1], feat_ind, 1e-50, 1.0);
+        EXPECT_EQ(_phi.size(), 5) << " (MultNode created with an absolute value above the upper bound)";
+
+        generateMultNode(_phi, _phi[0], _phi[1], feat_ind, 1e3, 1e50);
+        EXPECT_EQ(_phi.size(), 5) << " (MultNode created with an absolute value below the lower bound)";
+
+        generateMultNode(_phi, _phi[0], _phi[0], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 5) << " (MultNode created with only one primary feature present)";
+
+        generateMultNode(_phi, _phi[4], _phi[4], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 5) << " (MultNode created when some terms cancel out)";
+
+        generateMultNode(_phi, _phi[0], _phi[1], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 6) << " (Failure to create a valid feature)";
+
+        generateMultNode(_phi, _phi[5], _phi[4], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 6) << " (MultNode created that is a duplicate of a second feature)";
+
+        generateMultNode(_phi, _phi[0], _phi[2], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 7) << " (MultNode not created when units do not match.)";
+    }
+
+    TEST_F(MultNodeTest, ConstructorTest)
+    {
+        int feat_ind = _phi.size();
+
+        try
+        {
+            _mult_test = std::make_shared<MultNode>(_phi[0], _phi[2], feat_ind, 1e-50, 1e50);
+        }
+        catch(const InvalidFeatureException& e)
+        {
+            EXPECT_TRUE(false) << " (MultNode not created when units do not match.)";
+        }
+
+        try
+        {
+            _mult_test = std::make_shared<MultNode>(_phi[0], _phi[3], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (Creation of MultNode led to a feature with a constant value)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _mult_test = std::make_shared<MultNode>(_phi[0], _phi[1], feat_ind, 1e-50, 1.0);
+            EXPECT_TRUE(false) << " (MultNode created with an absolute value above the upper bound)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _mult_test = std::make_shared<MultNode>(_phi[0], _phi[1], feat_ind, 1e3, 1e50);
+            EXPECT_TRUE(false) << " (MultNode created with an absolute value below the lower bound)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _mult_test = std::make_shared<MultNode>(_phi[0], _phi[0], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (MultNode created with only one primary feature present)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+        try
+        {
+            _mult_test = std::make_shared<MultNode>(_phi[1], _phi[4], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (MultNode created when some terms cancel out)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _mult_test = std::make_shared<MultNode>(_phi[0], _phi[1], feat_ind, 1e-50, 1e50);
+            ++feat_ind;
+        }
+        catch(const InvalidFeatureException& e)
+        {
+            ASSERT_TRUE(false) << " (Failure to create a valid feature)";
+        }
+        try
+        {
+            _mult_test = std::make_shared<MultNode>(_mult_test, _mult_test, feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (MultNode created that is a duplicate of a second feature)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+    }
+
+    TEST_F(MultNodeTest, AttributesTest)
+    {
+        _mult_test = std::make_shared<MultNode>(_phi[0], _phi[1], 5, 1e-50, 1e50);
+        _mult_test = std::make_shared<MultNode>(_mult_test, _phi[1], 6, 1e-50, 1e50);
+
+        EXPECT_EQ(_mult_test->rung(), 2);
+
+        EXPECT_EQ(_mult_test->value_ptr()[0], 100.0);
+        EXPECT_EQ(_mult_test->test_value_ptr()[0], 12500);
+
+        EXPECT_EQ(_mult_test->value()[0], 100.0);
+        EXPECT_EQ(_mult_test->test_value()[0], 12500.0);
+
+        EXPECT_STREQ(_mult_test->unit().toString().c_str(), "m^3");
+
+        EXPECT_STREQ(_mult_test->expr().c_str(), "[([(A) * (B)]) * (B)]");
+        EXPECT_STREQ(_mult_test->postfix_expr().c_str(), "0|1|mult|1|mult");
+    }
+}
diff --git a/tests/googletest/feature_creation/feature_generation/test_neg_exp_node.cc b/tests/googletest/feature_creation/feature_generation/test_neg_exp_node.cc
new file mode 100644
index 0000000000000000000000000000000000000000..205d7a92703b9cab7237d4ddf3bee62e7d6e25d1
--- /dev/null
+++ b/tests/googletest/feature_creation/feature_generation/test_neg_exp_node.cc
@@ -0,0 +1,176 @@
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/add/add.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/exp/exponential.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/log/log.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/neg_exp/negative_exponential.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/sub/subtract.hpp>
+
+#include <feature_creation/node/value_storage/nodes_value_containers.hpp>
+#include <feature_creation/node/FeatureNode.hpp>
+#include "gtest/gtest.h"
+#include <math.h>
+
+namespace
+{
+    class NegExpNodeTest : public ::testing::Test
+    {
+    protected:
+        void SetUp() override
+        {
+            node_value_arrs::initialize_values_arr(4, 1, 3);
+
+            std::vector<double> value_1 = {0.0, 2.0, 3.0, 4.0};
+            std::vector<double> test_value_1 =  {0.0};
+
+            std::vector<double> value_2 = {1.0, 2.0 * M_PI, 4.0 * M_PI, 6.0 * M_PI};
+            std::vector<double> test_value_2 =  {5.0};
+
+            _feat_1 = std::make_shared<FeatureNode>(0, "A", value_1, test_value_1, Unit());
+            _feat_2 = std::make_shared<FeatureNode>(1, "B", value_1, test_value_1, Unit("m"));
+            _feat_3 = std::make_shared<FeatureNode>(2, "C", value_2, test_value_2, Unit());
+
+            _phi = {_feat_1, _feat_2, _feat_3};
+            _phi.push_back(std::make_shared<LogNode>(_feat_3, 3, 1e-50, 1e50));
+            _phi.push_back(std::make_shared<ExpNode>(_feat_1, 3, 1e-50, 1e50));
+            _phi.push_back(std::make_shared<AddNode>(_feat_1, _feat_3, 3, 1e-50, 1e50));
+            _phi.push_back(std::make_shared<SubNode>(_feat_1, _feat_3, 3, 1e-50, 1e50));
+        }
+
+        node_ptr _feat_1;
+        node_ptr _feat_2;
+        node_ptr _feat_3;
+
+        node_ptr _neg_exp_test;
+
+        std::vector<node_ptr> _phi;
+    };
+
+    TEST_F(NegExpNodeTest, GeneratorTest)
+    {
+        int feat_ind = _phi.size();
+
+        generateNegExpNode(_phi, _phi[1], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 7) << "(NegExpNode created with a feature that is not unitless)";
+
+        generateNegExpNode(_phi, _phi[0], feat_ind, 1e-50, 1e-40);
+        EXPECT_EQ(_phi.size(), 7) << " (NegExpNode created with an absolute value above the upper bound)";
+
+        generateNegExpNode(_phi, _phi[0], feat_ind, 1e3, 1e50);
+        EXPECT_EQ(_phi.size(), 7) << " (NegExpNode created with an absolute value below the lower bound)";
+
+        generateNegExpNode(_phi, _phi[3], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 7) << " (NegExpNode created from a LogNode)";
+
+        generateNegExpNode(_phi, _phi[4], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 7) << " (NegExpNode created from a ExpNode)";
+
+        generateNegExpNode(_phi, _phi[5], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 7) << " (NegExpNode created from a AddNode)";
+
+        generateNegExpNode(_phi, _phi[6], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 7) << " (NegExpNode created from a SubNode)";
+
+        generateNegExpNode(_phi, _phi[0], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 8) << " (Failure to create a valid feature)";
+
+        generateNegExpNode(_phi, _phi[4], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 8) << " (NegExpNode created from another NegExpNode)";
+
+    }
+
+    TEST_F(NegExpNodeTest, ConstructorTest)
+    {
+        int feat_ind = _phi.size();
+
+        try
+        {
+            _neg_exp_test = std::make_shared<NegExpNode>(_phi[1], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << "(NegExpNode created with a feature that is not unitless)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _neg_exp_test = std::make_shared<NegExpNode>(_phi[0], feat_ind, 1e-50, 1e-40);
+            EXPECT_TRUE(false) << " (NegExpNode created with an absolute value above the upper bound)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _neg_exp_test = std::make_shared<NegExpNode>(_phi[0], feat_ind, 1e3, 1e50);
+            EXPECT_TRUE(false) << " (NegExpNode created with an absolute value below the lower bound)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _neg_exp_test = std::make_shared<NegExpNode>(_phi[3], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (NegExpNode created from a LogNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _neg_exp_test = std::make_shared<NegExpNode>(_phi[4], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (NegExpNode created from a ExpNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _neg_exp_test = std::make_shared<NegExpNode>(_phi[5], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (NegExpNode created from a AddNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _neg_exp_test = std::make_shared<NegExpNode>(_phi[6], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (NegExpNode created from a SubNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _neg_exp_test = std::make_shared<NegExpNode>(_phi[0], feat_ind, 1e-50, 1e50);
+            ++feat_ind;
+        }
+        catch(const InvalidFeatureException& e)
+        {
+            ASSERT_TRUE(false) << " (Failure to create a valid feature)";
+        }
+
+        try
+        {
+            _neg_exp_test = std::make_shared<NegExpNode>(_neg_exp_test, feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (NegExpNode created from another NegExpNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+    }
+
+    TEST_F(NegExpNodeTest, AttributesTest)
+    {
+        _neg_exp_test = std::make_shared<NegExpNode>(_phi[0], 5, 1e-50, 1e50);
+
+        EXPECT_EQ(_neg_exp_test->rung(), 1);
+
+        EXPECT_EQ(_neg_exp_test->value_ptr()[0], 1.0);
+        EXPECT_EQ(_neg_exp_test->test_value_ptr()[0], 1.0);
+
+        EXPECT_EQ(_neg_exp_test->value()[0], 1.0);
+        EXPECT_EQ(_neg_exp_test->test_value()[0], 1.0);
+
+        EXPECT_STREQ(_neg_exp_test->unit().toString().c_str(), "Unitless");
+
+        EXPECT_STREQ(_neg_exp_test->expr().c_str(), "exp[-1.0*(A)]");
+        EXPECT_STREQ(_neg_exp_test->postfix_expr().c_str(), "0|nexp");
+    }
+}
diff --git a/tests/googletest/feature_creation/feature_generation/test_sin_node.cc b/tests/googletest/feature_creation/feature_generation/test_sin_node.cc
new file mode 100644
index 0000000000000000000000000000000000000000..79325c785bd70dd6a195b772ee879df0a5341965
--- /dev/null
+++ b/tests/googletest/feature_creation/feature_generation/test_sin_node.cc
@@ -0,0 +1,147 @@
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/cos/cos.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/sin/sin.hpp>
+#include <feature_creation/node/value_storage/nodes_value_containers.hpp>
+#include <feature_creation/node/FeatureNode.hpp>
+#include "gtest/gtest.h"
+#include <math.h>
+
+namespace
+{
+    class SinNodeTest : public ::testing::Test
+    {
+    protected:
+        void SetUp() override
+        {
+            node_value_arrs::initialize_values_arr(4, 1, 4);
+
+            std::vector<double> value_1 = {0.0, 2.0, 3.0, 4.0};
+            std::vector<double> test_value_1 =  {0.0};
+
+            std::vector<double> value_2 = {0.0, 2.0 * M_PI, 4.0 * M_PI, 6.0 * M_PI};
+            std::vector<double> test_value_2 =  {5.0};
+
+            _feat_1 = std::make_shared<FeatureNode>(0, "A", value_1, test_value_1, Unit());
+            _feat_2 = std::make_shared<FeatureNode>(1, "B", value_1, test_value_1, Unit("m"));
+            _feat_3 = std::make_shared<FeatureNode>(2, "C", value_2, test_value_2, Unit());
+
+            _phi = {_feat_1, _feat_2, _feat_3};
+            _phi.push_back(std::make_shared<CosNode>(_feat_1, 3, 1e-50, 1e50));
+        }
+
+        node_ptr _feat_1;
+        node_ptr _feat_2;
+        node_ptr _feat_3;
+
+        node_ptr _sin_test;
+
+        std::vector<node_ptr> _phi;
+    };
+
+    TEST_F(SinNodeTest, GeneratorTest)
+    {
+        int feat_ind = _phi.size();
+
+        generateSinNode(_phi, _phi[2], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 4) << " (Creation of SinNode led to a feature with a constant value)";
+
+        generateSinNode(_phi, _phi[1], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 4) << "(SinNode created with a feature that is not unitless)";
+
+        generateSinNode(_phi, _phi[0], feat_ind, 1e-50, 1e-40);
+        EXPECT_EQ(_phi.size(), 4) << " (SinNode created with an absolute value above the upper bound)";
+
+        generateSinNode(_phi, _phi[0], feat_ind, 1e3, 1e50);
+        EXPECT_EQ(_phi.size(), 4) << " (SinNode created with an absolute value below the lower bound)";
+
+        generateSinNode(_phi, _phi[3], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 4) << " (SinNode created from another a CosNode)";
+
+        generateSinNode(_phi, _phi[0], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 5) << " (Failure to create a valid feature)";
+
+        generateSinNode(_phi, _phi[4], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 5) << " (SinNode created from another SinNode)";
+
+    }
+
+    TEST_F(SinNodeTest, ConstructorTest)
+    {
+        int feat_ind = _phi.size();
+
+        try
+        {
+            _sin_test = std::make_shared<SinNode>(_phi[2], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (Creation of SinNode led to a feature with a constant value)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _sin_test = std::make_shared<SinNode>(_phi[1], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << "(SinNode created with a feature that is not unitless)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _sin_test = std::make_shared<SinNode>(_phi[0], feat_ind, 1e-50, 1e-40);
+            EXPECT_TRUE(false) << " (SinNode created with an absolute value above the upper bound)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _sin_test = std::make_shared<SinNode>(_phi[0], feat_ind, 1e3, 1e50);
+            EXPECT_TRUE(false) << " (SinNode created with an absolute value below the lower bound)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _sin_test = std::make_shared<SinNode>(_phi[3], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (SinNode created from another a CosNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _sin_test = std::make_shared<SinNode>(_phi[0], feat_ind, 1e-50, 1e50);
+            ++feat_ind;
+        }
+        catch(const InvalidFeatureException& e)
+        {
+            ASSERT_TRUE(false) << " (Failure to create a valid feature)";
+        }
+
+        try
+        {
+            _sin_test = std::make_shared<SinNode>(_sin_test, feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (SinNode created from another SinNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+    }
+
+    TEST_F(SinNodeTest, AttributesTest)
+    {
+        _sin_test = std::make_shared<SinNode>(_phi[0], 5, 1e-50, 1e50);
+
+        EXPECT_EQ(_sin_test->rung(), 1);
+
+        EXPECT_EQ(_sin_test->value_ptr()[0], 0.0);
+        EXPECT_EQ(_sin_test->test_value_ptr()[0], 0.0);
+
+        EXPECT_EQ(_sin_test->value()[0], 0.0);
+        EXPECT_EQ(_sin_test->test_value()[0], 0.0);
+
+        EXPECT_STREQ(_sin_test->unit().toString().c_str(), "Unitless");
+
+        EXPECT_STREQ(_sin_test->expr().c_str(), "sin(A)");
+        EXPECT_STREQ(_sin_test->postfix_expr().c_str(), "0|sin");
+    }
+}
diff --git a/tests/googletest/feature_creation/feature_generation/test_six_pow_node.cc b/tests/googletest/feature_creation/feature_generation/test_six_pow_node.cc
new file mode 100644
index 0000000000000000000000000000000000000000..37484d12214a170353c9dcd3acc16bff9831b764
--- /dev/null
+++ b/tests/googletest/feature_creation/feature_generation/test_six_pow_node.cc
@@ -0,0 +1,173 @@
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/cb/cube.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/cbrt/cube_root.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/inv/inverse.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/sp/sixth_power.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/sq/square.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/sqrt/square_root.hpp>
+
+#include <feature_creation/node/value_storage/nodes_value_containers.hpp>
+#include <feature_creation/node/FeatureNode.hpp>
+#include "gtest/gtest.h"
+#include <math.h>
+
+namespace
+{
+    class SixPowNodeTest : public ::testing::Test
+    {
+    protected:
+        void SetUp() override
+        {
+            node_value_arrs::initialize_values_arr(4, 1, 4);
+
+            std::vector<double> value_1 = {1.0, 2.0, 3.0, 4.0};
+            std::vector<double> test_value_1 =  {2.0};
+
+            std::vector<double> value_2 = {1.0, 2.0 * M_PI, 4.0 * M_PI, 6.0 * M_PI};
+            std::vector<double> test_value_2 =  {5.0};
+
+            std::vector<double> value_3 = {0.0, 2.0, 3.0, 4.0};
+            std::vector<double> test_value_3 =  {0.0};
+
+            _feat_1 = std::make_shared<FeatureNode>(0, "A", value_1, test_value_1, Unit("m"));
+            _feat_2 = std::make_shared<FeatureNode>(1, "B", value_1, test_value_1, Unit("m"));
+            _feat_3 = std::make_shared<FeatureNode>(2, "C", value_2, test_value_2, Unit("m"));
+            _feat_4 = std::make_shared<FeatureNode>(3, "D", value_3, test_value_3, Unit("m"));
+
+            _phi = {_feat_1, _feat_2, _feat_3, _feat_4};
+            _phi.push_back(std::make_shared<InvNode>(_feat_1, 6, 1e-50, 1e50));
+            _phi.push_back(std::make_shared<CbrtNode>(_feat_1, 9, 1e-50, 1e50));
+            _phi.push_back(std::make_shared<SqrtNode>(_feat_1, 10, 1e-50, 1e50));
+            _phi.push_back(std::make_shared<SqNode>(_feat_1, 11, 1e-50, 1e50));
+            _phi.push_back(std::make_shared<CbNode>(_feat_1, 12, 1e-50, 1e50));
+        }
+
+        node_ptr _feat_1;
+        node_ptr _feat_2;
+        node_ptr _feat_3;
+        node_ptr _feat_4;
+
+        node_ptr _six_pow_test;
+
+        std::vector<node_ptr> _phi;
+    };
+
+    TEST_F(SixPowNodeTest, GeneratorTest)
+    {
+        int feat_ind = _phi.size();
+        int phi_sz = _phi.size();
+
+        generateSixPowNode(_phi, _phi[0], feat_ind, 1e-50, 1e-40);
+        EXPECT_EQ(_phi.size(), phi_sz) << " (SixPowNode created with an absolute value above the upper bound)";
+
+        generateSixPowNode(_phi, _phi[0], feat_ind, 1e10, 1e50);
+        EXPECT_EQ(_phi.size(), phi_sz) << " (SixPowNode created with an absolute value below the lower bound)";
+
+        generateSixPowNode(_phi, _phi[4], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), phi_sz) << " (SixPowNode created from a InvNode)";
+
+        generateSixPowNode(_phi, _phi[5], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), phi_sz) << " (SixPowNode created from a CbrtNode)";
+
+        generateSixPowNode(_phi, _phi[6], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), phi_sz) << " (SixPowNode created from a SqrtNode)";
+
+        generateSixPowNode(_phi, _phi[7], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), phi_sz) << " (SixPowNode created from a SqNode)";
+
+        generateSixPowNode(_phi, _phi[8], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), phi_sz) << " (SixPowNode created from a CbNode)";
+
+        generateSixPowNode(_phi, _phi[0], feat_ind, 1e-50, 1e50);
+        ++phi_sz;
+        EXPECT_EQ(_phi.size(), phi_sz) << " (Failure to create a valid feature)";
+    }
+
+    TEST_F(SixPowNodeTest, ConstructorTest)
+    {
+        int feat_ind = _phi.size();
+
+        try
+        {
+            _six_pow_test = std::make_shared<SixPowNode>(_phi[0], feat_ind, 1e-50, 1e-40);
+            EXPECT_TRUE(false) << " (SixPowNode created with an absolute value above the upper bound)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _six_pow_test = std::make_shared<SixPowNode>(_phi[0], feat_ind, 1e10, 1e50);
+            EXPECT_TRUE(false) << " (SixPowNode created with an absolute value below the lower bound)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _six_pow_test = std::make_shared<SixPowNode>(_phi[4], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (SixPowNode created from a InvNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _six_pow_test = std::make_shared<SixPowNode>(_phi[5], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (SixPowNode created from a CbrtNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _six_pow_test = std::make_shared<SixPowNode>(_phi[6], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (SixPowNode created from a SqrtNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _six_pow_test = std::make_shared<SixPowNode>(_phi[7], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (SixPowNode created from a SqNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _six_pow_test = std::make_shared<SixPowNode>(_phi[8], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (SixPowNode created from a CbNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _six_pow_test = std::make_shared<SixPowNode>(_phi[0], feat_ind, 1e-50, 1e50);
+            ++feat_ind;
+        }
+        catch(const InvalidFeatureException& e)
+        {
+            ASSERT_TRUE(false) << " (Failure to create a valid feature)";
+        }
+    }
+
+    TEST_F(SixPowNodeTest, AttributesTest)
+    {
+        _six_pow_test = std::make_shared<SixPowNode>(_phi[0], 5, 1e-50, 1e50);
+
+        EXPECT_EQ(_six_pow_test->rung(), 1);
+
+        EXPECT_EQ(_six_pow_test->value_ptr()[1], 64.0);
+        EXPECT_EQ(_six_pow_test->test_value_ptr()[0], 64.0);
+
+        EXPECT_EQ(_six_pow_test->value()[1], 64.0);
+        EXPECT_EQ(_six_pow_test->test_value()[0], 64.0);
+
+        EXPECT_STREQ(_six_pow_test->unit().toString().c_str(), "m^6");
+
+        EXPECT_STREQ(_six_pow_test->expr().c_str(), "(A)^6");
+        EXPECT_STREQ(_six_pow_test->postfix_expr().c_str(), "0|sp");
+    }
+}
diff --git a/tests/googletest/feature_creation/feature_generation/test_sq_node.cc b/tests/googletest/feature_creation/feature_generation/test_sq_node.cc
new file mode 100644
index 0000000000000000000000000000000000000000..8864b39d3f45da20077977c888f5dc846fda7832
--- /dev/null
+++ b/tests/googletest/feature_creation/feature_generation/test_sq_node.cc
@@ -0,0 +1,133 @@
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/inv/inverse.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/sq/square.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/sqrt/square_root.hpp>
+
+#include <feature_creation/node/value_storage/nodes_value_containers.hpp>
+#include <feature_creation/node/FeatureNode.hpp>
+#include "gtest/gtest.h"
+#include <math.h>
+
+namespace
+{
+    class SqNodeTest : public ::testing::Test
+    {
+    protected:
+        void SetUp() override
+        {
+            node_value_arrs::initialize_values_arr(4, 1, 4);
+
+            std::vector<double> value_1 = {1.0, 2.0, 3.0, 8.0};
+            std::vector<double> test_value_1 =  {2.0};
+
+            std::vector<double> value_2 = {1.0, 2.0 * M_PI, 4.0 * M_PI, 6.0 * M_PI};
+            std::vector<double> test_value_2 =  {5.0};
+
+            std::vector<double> value_3 = {-1.0, 2.0, 3.0, 4.0};
+            std::vector<double> test_value_3 =  {0.0};
+
+            _feat_1 = std::make_shared<FeatureNode>(0, "A", value_1, test_value_1, Unit("m"));
+            _feat_2 = std::make_shared<FeatureNode>(1, "B", value_1, test_value_1, Unit("m"));
+            _feat_3 = std::make_shared<FeatureNode>(2, "C", value_2, test_value_2, Unit("m"));
+            _feat_4 = std::make_shared<FeatureNode>(3, "D", value_3, test_value_3, Unit("m"));
+
+            _phi = {_feat_1, _feat_2, _feat_3, _feat_4};
+            _phi.push_back(std::make_shared<InvNode>(_feat_1, 6, 1e-50, 1e50));
+            _phi.push_back(std::make_shared<SqrtNode>(_feat_1, 11, 1e-50, 1e50));
+        }
+
+        node_ptr _feat_1;
+        node_ptr _feat_2;
+        node_ptr _feat_3;
+        node_ptr _feat_4;
+
+        node_ptr _sq_test;
+
+        std::vector<node_ptr> _phi;
+    };
+
+    TEST_F(SqNodeTest, GeneratorTest)
+    {
+        int feat_ind = _phi.size();
+        int phi_sz = _phi.size();
+
+        generateSqNode(_phi, _phi[0], feat_ind, 1e-50, 1e-40);
+        EXPECT_EQ(_phi.size(), phi_sz) << " (SqNode created with an absolute value above the upper bound)";
+
+        generateSqNode(_phi, _phi[0], feat_ind, 1e10, 1e50);
+        EXPECT_EQ(_phi.size(), phi_sz) << " (SqNode created with an absolute value below the lower bound)";
+
+        generateSqNode(_phi, _phi[4], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), phi_sz) << " (SqNode created from a InvNode)";
+
+        generateSqNode(_phi, _phi[5], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), phi_sz) << " (SqNode created from a SqrtNode)";
+
+        generateSqNode(_phi, _phi[0], feat_ind, 1e-50, 1e50);
+        ++phi_sz;
+        EXPECT_EQ(_phi.size(), phi_sz) << " (Failure to create a valid feature)";
+    }
+
+    TEST_F(SqNodeTest, ConstructorTest)
+    {
+        int feat_ind = _phi.size();
+        try
+        {
+            _sq_test = std::make_shared<SqNode>(_phi[0], feat_ind, 1e-50, 1e-40);
+            EXPECT_TRUE(false) << " (SqNode created with an absolute value above the upper bound)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _sq_test = std::make_shared<SqNode>(_phi[0], feat_ind, 1e10, 1e50);
+            EXPECT_TRUE(false) << " (SqNode created with an absolute value below the lower bound)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _sq_test = std::make_shared<SqNode>(_phi[4], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (SqNode created from a InvNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _sq_test = std::make_shared<SqNode>(_phi[5], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (SqNode created from a SqrtNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _sq_test = std::make_shared<SqNode>(_phi[0], feat_ind, 1e-50, 1e50);
+            ++feat_ind;
+        }
+        catch(const InvalidFeatureException& e)
+        {
+            ASSERT_TRUE(false) << " (Failure to create a valid feature)";
+        }
+    }
+
+    TEST_F(SqNodeTest, AttributesTest)
+    {
+        _sq_test = std::make_shared<SqNode>(_phi[0], 5, 1e-50, 1e50);
+
+        EXPECT_EQ(_sq_test->rung(), 1);
+
+        EXPECT_EQ(_sq_test->value_ptr()[1], 4.0);
+        EXPECT_EQ(_sq_test->test_value_ptr()[0], 4.0);
+
+        EXPECT_EQ(_sq_test->value()[1], 4.0);
+        EXPECT_EQ(_sq_test->test_value()[0], 4.0);
+
+        EXPECT_STREQ(_sq_test->unit().toString().c_str(), "m^2");
+
+        EXPECT_STREQ(_sq_test->expr().c_str(), "(A)^2");
+        EXPECT_STREQ(_sq_test->postfix_expr().c_str(), "0|sq");
+    }
+}
diff --git a/tests/googletest/feature_creation/feature_generation/test_sqrt_node.cc b/tests/googletest/feature_creation/feature_generation/test_sqrt_node.cc
new file mode 100644
index 0000000000000000000000000000000000000000..8c02401d04ef7c533a5da6430468a12886d779d0
--- /dev/null
+++ b/tests/googletest/feature_creation/feature_generation/test_sqrt_node.cc
@@ -0,0 +1,184 @@
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/cb/cube.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/cbrt/cube_root.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/inv/inverse.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/sp/sixth_power.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/sq/square.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/sqrt/square_root.hpp>
+
+#include <feature_creation/node/value_storage/nodes_value_containers.hpp>
+#include <feature_creation/node/FeatureNode.hpp>
+#include "gtest/gtest.h"
+#include <math.h>
+
+namespace
+{
+    class SqrtNodeTest : public ::testing::Test
+    {
+    protected:
+        void SetUp() override
+        {
+            node_value_arrs::initialize_values_arr(4, 1, 4);
+
+            std::vector<double> value_1 = {1.0, 2.0, 3.0, 4.0};
+            std::vector<double> test_value_1 =  {4.0};
+
+            std::vector<double> value_2 = {1.0, 2.0 * M_PI, 4.0 * M_PI, 6.0 * M_PI};
+            std::vector<double> test_value_2 =  {5.0};
+
+            std::vector<double> value_3 = {-1.0, 2.0, 3.0, 4.0};
+            std::vector<double> test_value_3 =  {0.0};
+
+            _feat_1 = std::make_shared<FeatureNode>(0, "A", value_1, test_value_1, Unit("m"));
+            _feat_2 = std::make_shared<FeatureNode>(1, "B", value_1, test_value_1, Unit("m"));
+            _feat_3 = std::make_shared<FeatureNode>(2, "C", value_2, test_value_2, Unit("m"));
+            _feat_4 = std::make_shared<FeatureNode>(3, "D", value_3, test_value_3, Unit("m"));
+
+            _phi = {_feat_1, _feat_2, _feat_3, _feat_4};
+            _phi.push_back(std::make_shared<InvNode>(_feat_1, 6, 1e-50, 1e50));
+            _phi.push_back(std::make_shared<CbrtNode>(_feat_1, 9, 1e-50, 1e50));
+            _phi.push_back(std::make_shared<SqNode>(_feat_1, 11, 1e-50, 1e50));
+            _phi.push_back(std::make_shared<CbNode>(_feat_1, 12, 1e-50, 1e50));
+            _phi.push_back(std::make_shared<SixPowNode>(_feat_1, 12, 1e-50, 1e50));
+        }
+
+        node_ptr _feat_1;
+        node_ptr _feat_2;
+        node_ptr _feat_3;
+        node_ptr _feat_4;
+
+        node_ptr _sqrt_test;
+
+        std::vector<node_ptr> _phi;
+    };
+
+    TEST_F(SqrtNodeTest, GeneratorTest)
+    {
+        int feat_ind = _phi.size();
+        int phi_sz = _phi.size();
+
+        generateSqrtNode(_phi, _phi[3], feat_ind, 1e-50, 1e-40);
+        EXPECT_EQ(_phi.size(), phi_sz) << " (SqrtNode created with a feature that has a value < 0.0)";
+
+        generateSqrtNode(_phi, _phi[0], feat_ind, 1e-50, 1e-40);
+        EXPECT_EQ(_phi.size(), phi_sz) << " (SqrtNode created with an absolute value above the upper bound)";
+
+        generateSqrtNode(_phi, _phi[0], feat_ind, 1e10, 1e50);
+        EXPECT_EQ(_phi.size(), phi_sz) << " (SqrtNode created with an absolute value below the lower bound)";
+
+        generateSqrtNode(_phi, _phi[4], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), phi_sz) << " (SqrtNode created from a InvNode)";
+
+        generateSqrtNode(_phi, _phi[5], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), phi_sz) << " (SqrtNode created from a CbrtNode)";
+
+        generateSqrtNode(_phi, _phi[6], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), phi_sz) << " (SqrtNode created from a SqNode)";
+
+        generateSqrtNode(_phi, _phi[7], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), phi_sz) << " (SqrtNode created from a CbNode)";
+
+        generateSqrtNode(_phi, _phi[8], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), phi_sz) << " (SqrtNode created from a SixPowNode)";
+
+        generateSqrtNode(_phi, _phi[0], feat_ind, 1e-50, 1e50);
+        ++phi_sz;
+        EXPECT_EQ(_phi.size(), phi_sz) << " (Failure to create a valid feature)";
+    }
+
+    TEST_F(SqrtNodeTest, ConstructorTest)
+    {
+        int feat_ind = _phi.size();
+
+        try
+        {
+            _sqrt_test = std::make_shared<SqrtNode>(_phi[3], feat_ind, 1e-50, 1e-40);
+            EXPECT_TRUE(false) << " (SqrtNode created with a feature that has a value < 0.0)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _sqrt_test = std::make_shared<SqrtNode>(_phi[0], feat_ind, 1e-50, 1e-40);
+            EXPECT_TRUE(false) << " (SqrtNode created with an absolute value above the upper bound)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _sqrt_test = std::make_shared<SqrtNode>(_phi[0], feat_ind, 1e10, 1e50);
+            EXPECT_TRUE(false) << " (SqrtNode created with an absolute value below the lower bound)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _sqrt_test = std::make_shared<SqrtNode>(_phi[4], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (SqrtNode created from a InvNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _sqrt_test = std::make_shared<SqrtNode>(_phi[5], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (SqrtNode created from a CbrtNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _sqrt_test = std::make_shared<SqrtNode>(_phi[6], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (SqrtNode created from a SqNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _sqrt_test = std::make_shared<SqrtNode>(_phi[7], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (SqrtNode created from a CbNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _sqrt_test = std::make_shared<SqrtNode>(_phi[8], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (SqrtNode created from a SixPowNode)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _sqrt_test = std::make_shared<SqrtNode>(_phi[0], feat_ind, 1e-50, 1e50);
+            ++feat_ind;
+        }
+        catch(const InvalidFeatureException& e)
+        {
+            ASSERT_TRUE(false) << " (Failure to create a valid feature)";
+        }
+    }
+
+    TEST_F(SqrtNodeTest, AttributesTest)
+    {
+        _sqrt_test = std::make_shared<SqrtNode>(_phi[0], 5, 1e-50, 1e50);
+
+        EXPECT_EQ(_sqrt_test->rung(), 1);
+
+        EXPECT_EQ(_sqrt_test->value_ptr()[3], 2.0);
+        EXPECT_EQ(_sqrt_test->test_value_ptr()[0], 2.0);
+
+        EXPECT_EQ(_sqrt_test->value()[3], 2.0);
+        EXPECT_EQ(_sqrt_test->test_value()[0], 2.0);
+
+        EXPECT_STREQ(_sqrt_test->unit().toString().c_str(), "m^0.5");
+
+        EXPECT_STREQ(_sqrt_test->expr().c_str(), "sqrt(A)");
+        EXPECT_STREQ(_sqrt_test->postfix_expr().c_str(), "0|sqrt");
+    }
+}
diff --git a/tests/googletest/feature_creation/feature_generation/test_sub_node.cc b/tests/googletest/feature_creation/feature_generation/test_sub_node.cc
new file mode 100644
index 0000000000000000000000000000000000000000..1a63cffb61200feedf08832bfac7e4ad4ccf6b48
--- /dev/null
+++ b/tests/googletest/feature_creation/feature_generation/test_sub_node.cc
@@ -0,0 +1,159 @@
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/add/add.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/sub/subtract.hpp>
+#include <feature_creation/node/value_storage/nodes_value_containers.hpp>
+#include <feature_creation/node/FeatureNode.hpp>
+#include "gtest/gtest.h"
+
+namespace
+{
+    class SubNodeTest : public ::testing::Test
+    {
+    protected:
+        void SetUp() override
+        {
+            node_value_arrs::initialize_values_arr(4, 1, 4);
+
+            std::vector<double> value_1 = {1.0, 2.0, 3.0, 4.0};
+            std::vector<double> test_value_1 =  {5.0};
+
+            std::vector<double> value_2 = {10.0, 20.0, 30.0, 40.0};
+            std::vector<double> test_value_2 =  {50.0};
+
+            std::vector<double> value_3 = {1.0, 2.0, 3.0, 4.0};
+            std::vector<double> test_value_3 =  {5.0};
+
+            _feat_1 = std::make_shared<FeatureNode>(0, "A", value_1, test_value_1, Unit("m"));
+            _feat_2 = std::make_shared<FeatureNode>(1, "B", value_2, test_value_2, Unit("m"));
+            _feat_3 = std::make_shared<FeatureNode>(2, "C", value_2, test_value_2, Unit("s"));
+            _feat_4 = std::make_shared<FeatureNode>(3, "C", value_3, test_value_3, Unit("s"));
+
+            _phi = {_feat_1, _feat_2, _feat_3, _feat_4};
+            _phi.push_back(std::make_shared<AddNode>(_feat_1, _feat_2, 4, -1e-50, 1e50));
+        }
+
+        node_ptr _feat_1;
+        node_ptr _feat_2;
+        node_ptr _feat_3;
+        node_ptr _feat_4;
+        node_ptr _sub_test;
+
+        std::vector<node_ptr> _phi;
+    };
+
+    TEST_F(SubNodeTest, GeneratorTest)
+    {
+        int feat_ind = _phi.size();
+
+        generateSubNode(_phi, _phi[0], _phi[2], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 5) << " (SubNode created when units do not match.)";
+
+        generateSubNode(_phi, _phi[0], _phi[3], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 5) << " (Creation of SubNode led to a feature with a constant value)";
+
+        generateSubNode(_phi, _phi[0], _phi[1], feat_ind, 1e-50, 1.0);
+        EXPECT_EQ(_phi.size(), 5) << " (SubNode created with an absolute value above the upper bound)";
+
+        generateSubNode(_phi, _phi[0], _phi[1], feat_ind, 1e3, 1e50);
+        EXPECT_EQ(_phi.size(), 5) << " (SubNode created with an absolute value below the lower bound)";
+
+        generateSubNode(_phi, _phi[0], _phi[0], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 5) << " (SubNode created with only one primary feature present)";
+
+        generateSubNode(_phi, _phi[4], _phi[4], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 5) << " (SubNode created when some terms cancel out)";
+
+        generateSubNode(_phi, _phi[0], _phi[1], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 6) << " (Failure to create a valid feature)";
+
+        generateSubNode(_phi, _phi[5], _phi[4], feat_ind, 1e-50, 1e50);
+        EXPECT_EQ(_phi.size(), 6) << " (SubNode created that is a duplicate of a second feature)";
+    }
+
+    TEST_F(SubNodeTest, ConstructorTest)
+    {
+        int feat_ind = _phi.size();
+
+        try
+        {
+            _sub_test = std::make_shared<SubNode>(_phi[0], _phi[2], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (SubNode created when units do not match.)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _sub_test = std::make_shared<SubNode>(_phi[0], _phi[3], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (Creation of SubNode led to a feature with a constant value)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _sub_test = std::make_shared<SubNode>(_phi[0], _phi[1], feat_ind, 1e-50, 1.0);
+            EXPECT_TRUE(false) << " (SubNode created with an absolute value above the upper bound)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _sub_test = std::make_shared<SubNode>(_phi[0], _phi[1], feat_ind, 1e3, 1e50);
+            EXPECT_TRUE(false) << " (SubNode created with an absolute value below the lower bound)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _sub_test = std::make_shared<SubNode>(_phi[0], _phi[0], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (SubNode created with only one primary feature present)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+        try
+        {
+            _sub_test = std::make_shared<SubNode>(_phi[1], _phi[4], feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (SubNode created when some terms cancel out)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+
+        try
+        {
+            _sub_test = std::make_shared<SubNode>(_phi[0], _phi[1], feat_ind, 1e-50, 1e50);
+            ++feat_ind;
+        }
+        catch(const InvalidFeatureException& e)
+        {
+            ASSERT_TRUE(false) << " (Failure to create a valid feature)";
+        }
+        try
+        {
+            _sub_test = std::make_shared<SubNode>(_sub_test, _sub_test, feat_ind, 1e-50, 1e50);
+            EXPECT_TRUE(false) << " (SubNode created that is a duplicate of a second feature)";
+        }
+        catch(const InvalidFeatureException& e)
+        {}
+    }
+
+    TEST_F(SubNodeTest, AttributesTest)
+    {
+        _sub_test = std::make_shared<SubNode>(_phi[0], _phi[1], 5, 1e-50, 1e50);
+        _sub_test = std::make_shared<SubNode>(_sub_test, _phi[1], 6, 1e-50, 1e50);
+
+        EXPECT_EQ(_sub_test->rung(), 2);
+
+        EXPECT_EQ(_sub_test->value_ptr()[0], -19.0);
+        EXPECT_EQ(_sub_test->test_value_ptr()[0], -95.0);
+
+        EXPECT_EQ(_sub_test->value()[0], -19.0);
+        EXPECT_EQ(_sub_test->test_value()[0], -95.0);
+
+        EXPECT_STREQ(_sub_test->unit().toString().c_str(), "m");
+
+        EXPECT_STREQ(_sub_test->expr().c_str(), "[([(A) - (B)]) - (B)]");
+        EXPECT_STREQ(_sub_test->postfix_expr().c_str(), "0|1|sub|1|sub");
+    }
+}
diff --git a/tests/googletest/feature_creation/feature_space/test_feat_space.cc b/tests/googletest/feature_creation/feature_space/test_feat_space.cc
new file mode 100644
index 0000000000000000000000000000000000000000..cb92425fe3bb0fd6f4247e187d54b4d1debc9e1f
--- /dev/null
+++ b/tests/googletest/feature_creation/feature_space/test_feat_space.cc
@@ -0,0 +1,230 @@
+#include <feature_creation/feature_space/FeatureSpace.hpp>
+#include <boost/filesystem.hpp>
+#include "gtest/gtest.h"
+
+namespace
+{
+    class FeatSpaceTest : public ::testing::Test
+    {
+    protected:
+        void SetUp() override
+        {
+            allowed_op_maps::set_node_maps();
+            node_value_arrs::initialize_d_matrix_arr();
+            mpi_setup::init_mpi_env();
+
+            _task_sizes = {5, 5};
+            node_value_arrs::initialize_values_arr(10, 0, 3);
+
+            std::vector<double> value_1 = {3.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0};
+            std::vector<double> value_2 = {1.10, 2.20, 3.10, 4.20, 5.10, 6.20, 7.10, 8.20, 9.10, 10.20};
+            std::vector<double> value_3 = {3.0, -3.0, 5.0, -7.0, 9.0, -2.0, 4.0, -6.0, 8.0, -10.0};
+
+            node_ptr feat_1 = std::make_shared<FeatureNode>(0, "A", value_1, std::vector<double>(), Unit("m"));
+            node_ptr feat_2 = std::make_shared<FeatureNode>(1, "B", value_2, std::vector<double>(), Unit("m"));
+            node_ptr feat_3 = std::make_shared<FeatureNode>(2, "C", value_3, std::vector<double>(), Unit("s"));
+
+            _phi_0 = {feat_1, feat_2, feat_3};
+            _prop = std::vector<double>(10, 0.0);
+            _prop_class = {0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0};
+            _prop_log_reg = std::vector<double>(10, 0.0);
+            std::transform(value_2.begin(), value_2.begin() + _task_sizes[0], value_3.begin(), _prop.begin(), [](double v1, double v2){return v1 / (v2 * v2);});
+            std::transform(value_2.begin() + _task_sizes[0], value_2.end(), value_3.begin() + _task_sizes[0], _prop.begin() + _task_sizes[0], [](double v1, double v2){return -6.5 + 1.25 * v1 / (v2 * v2);});
+
+            std::transform(value_2.begin(), value_2.end(), value_3.begin(), _prop_log_reg.begin(), [](double v1, double v2){return v1 / (v2 * v2);});
+
+            _allowed_ops = {"sq", "cb", "div", "add"};
+        }
+
+        std::vector<node_ptr> _phi_0;
+        std::vector<std::string> _allowed_ops;
+        std::vector<double> _prop;
+        std::vector<double> _prop_log_reg;
+        std::vector<double> _prop_class;
+        std::vector<int> _task_sizes;
+    };
+
+    TEST_F(FeatSpaceTest, RegTest)
+    {
+        FeatureSpace feat_space(
+            mpi_setup::comm,
+            _phi_0,
+            _allowed_ops,
+            _prop,
+            _task_sizes,
+            "regression",
+            2,
+            10,
+            1,
+            0,
+            1.0,
+            1e-50,
+            1e50
+        );
+        feat_space.sis(_prop);
+        EXPECT_EQ(feat_space.task_sizes()[0], _task_sizes[0]);
+        EXPECT_STREQ(feat_space.feature_space_file().c_str(), "feature_space/selected_features.txt");
+        EXPECT_EQ(feat_space.l_bound(), 1e-50);
+        EXPECT_EQ(feat_space.u_bound(), 1e50);
+        EXPECT_EQ(feat_space.max_phi(), 2);
+        EXPECT_EQ(feat_space.n_sis_select(), 10);
+        EXPECT_EQ(feat_space.n_samp(), 10);
+        EXPECT_EQ(feat_space.n_feat(), 154);
+        EXPECT_EQ(feat_space.n_rung_store(), 1);
+        EXPECT_EQ(feat_space.n_rung_generate(), 0);
+
+        EXPECT_LT(std::abs(feat_space.phi_selected()[0]->value()[0] - _prop[0]), 1e-10);
+        EXPECT_LT(std::abs(feat_space.phi0()[0]->value()[0] - _phi_0[0]->value()[0]), 1e-10);
+        EXPECT_LT(std::abs(feat_space.phi()[0]->value()[0] - _phi_0[0]->value()[0]), 1e-10);
+
+        boost::filesystem::remove_all("feature_space/");
+    }
+
+    TEST_F(FeatSpaceTest, ProjectGenTest)
+    {
+        FeatureSpace feat_space(
+            mpi_setup::comm,
+            _phi_0,
+            _allowed_ops,
+            _prop,
+            _task_sizes,
+            "regression",
+            2,
+            10,
+            1,
+            1,
+            1.0,
+            1e-50,
+            1e50
+        );
+        feat_space.sis(_prop);
+        EXPECT_EQ(feat_space.task_sizes()[0], _task_sizes[0]);
+        EXPECT_STREQ(feat_space.feature_space_file().c_str(), "feature_space/selected_features.txt");
+        EXPECT_EQ(feat_space.l_bound(), 1e-50);
+        EXPECT_EQ(feat_space.u_bound(), 1e50);
+        EXPECT_EQ(feat_space.max_phi(), 2);
+        EXPECT_EQ(feat_space.n_sis_select(), 10);
+        EXPECT_EQ(feat_space.n_samp(), 10);
+        EXPECT_EQ(feat_space.n_feat(), 16);
+        EXPECT_EQ(feat_space.n_rung_store(), 1);
+        EXPECT_EQ(feat_space.n_rung_generate(), 1);
+
+        EXPECT_LT(std::abs(feat_space.phi_selected()[0]->value()[0] - _prop[0]), 1e-10);
+        EXPECT_LT(std::abs(feat_space.phi0()[0]->value()[0] - _phi_0[0]->value()[0]), 1e-10);
+        EXPECT_LT(std::abs(feat_space.phi()[0]->value()[0] - _phi_0[0]->value()[0]), 1e-10);
+
+        boost::filesystem::remove_all("feature_space/");
+    }
+
+    TEST_F(FeatSpaceTest, MaxCorrTest)
+    {
+        FeatureSpace feat_space(
+            mpi_setup::comm,
+            _phi_0,
+            _allowed_ops,
+            _prop,
+            _task_sizes,
+            "regression",
+            2,
+            10,
+            1,
+            0,
+            0.99,
+            1e-50,
+            1e50
+        );
+        feat_space.sis(_prop);
+        EXPECT_EQ(feat_space.task_sizes()[0], _task_sizes[0]);
+        EXPECT_STREQ(feat_space.feature_space_file().c_str(), "feature_space/selected_features.txt");
+        EXPECT_EQ(feat_space.l_bound(), 1e-50);
+        EXPECT_EQ(feat_space.u_bound(), 1e50);
+        EXPECT_EQ(feat_space.max_phi(), 2);
+        EXPECT_EQ(feat_space.n_sis_select(), 10);
+        EXPECT_EQ(feat_space.n_samp(), 10);
+        EXPECT_EQ(feat_space.n_feat(), 154);
+        EXPECT_EQ(feat_space.n_rung_store(), 1);
+        EXPECT_EQ(feat_space.n_rung_generate(), 0);
+
+        EXPECT_LT(std::abs(feat_space.phi_selected()[0]->value()[0] - _prop[0]), 1e-10);
+        EXPECT_LT(std::abs(feat_space.phi0()[0]->value()[0] - _phi_0[0]->value()[0]), 1e-10);
+        EXPECT_LT(std::abs(feat_space.phi()[0]->value()[0] - _phi_0[0]->value()[0]), 1e-10);
+
+        boost::filesystem::remove_all("feature_space/");
+    }
+
+    TEST_F(FeatSpaceTest, LogRegTest)
+    {
+        FeatureSpace feat_space(
+            mpi_setup::comm,
+            _phi_0,
+            _allowed_ops,
+            _prop_log_reg,
+            {10},
+            "log_regression",
+            2,
+            10,
+            1,
+            0,
+            1.0,
+            1e-50,
+            1e50
+        );
+
+        std::transform(_prop_log_reg.begin(), _prop_log_reg.end(), _prop_log_reg.begin(), [](double pl){return std::log(pl);});
+        feat_space.sis(_prop_log_reg);
+
+        EXPECT_EQ(feat_space.task_sizes()[0], 10);
+        EXPECT_STREQ(feat_space.feature_space_file().c_str(), "feature_space/selected_features.txt");
+        EXPECT_EQ(feat_space.l_bound(), 1e-50);
+        EXPECT_EQ(feat_space.u_bound(), 1e50);
+        EXPECT_EQ(feat_space.max_phi(), 2);
+        EXPECT_EQ(feat_space.n_sis_select(), 10);
+        EXPECT_EQ(feat_space.n_samp(), 10);
+        EXPECT_EQ(feat_space.n_feat(), 154);
+        EXPECT_EQ(feat_space.n_rung_store(), 1);
+        EXPECT_EQ(feat_space.n_rung_generate(), 0);
+
+        std::vector<double> log_a(10, 0.0);
+        EXPECT_LT(std::abs(1.0 - util_funcs::log_r2(feat_space.phi_selected()[0]->value_ptr(), _prop_log_reg.data(), log_a.data(), 10)), 1e-8);
+        EXPECT_LT(std::abs(feat_space.phi0()[0]->value()[0] - _phi_0[0]->value()[0]), 1e-8);
+        EXPECT_LT(std::abs(feat_space.phi()[0]->value()[0] - _phi_0[0]->value()[0]), 1e-8);
+        boost::filesystem::remove_all("feature_space/");
+    }
+
+    TEST_F(FeatSpaceTest, ClassTest)
+    {
+        FeatureSpace feat_space(
+            mpi_setup::comm,
+            _phi_0,
+            _allowed_ops,
+            _prop_class,
+            {10},
+            "classification",
+            0,
+            1,
+            0,
+            0,
+            1.0,
+            1e-50,
+            1e50
+        );
+        feat_space.sis(_prop_class);
+
+        EXPECT_EQ(feat_space.task_sizes()[0], 10);
+        EXPECT_STREQ(feat_space.feature_space_file().c_str(), "feature_space/selected_features.txt");
+        EXPECT_EQ(feat_space.l_bound(), 1e-50);
+        EXPECT_EQ(feat_space.u_bound(), 1e50);
+        EXPECT_EQ(feat_space.max_phi(), 0);
+        EXPECT_EQ(feat_space.n_sis_select(), 1);
+        EXPECT_EQ(feat_space.n_samp(), 10);
+        EXPECT_EQ(feat_space.n_feat(), 3);
+        EXPECT_EQ(feat_space.n_rung_store(), 0);
+        EXPECT_EQ(feat_space.n_rung_generate(), 0);
+
+        EXPECT_LT(std::abs(feat_space.phi_selected()[0]->value()[1] + 3.0), 1e-10);
+        EXPECT_LT(std::abs(feat_space.phi0()[0]->value()[0] - _phi_0[0]->value()[0]), 1e-10);
+        EXPECT_LT(std::abs(feat_space.phi()[0]->value()[0] - _phi_0[0]->value()[0]), 1e-10);
+
+        boost::filesystem::remove_all("feature_space/");
+    }
+}
diff --git a/tests/googletest/feature_creation/units/test_untis.cc b/tests/googletest/feature_creation/units/test_untis.cc
new file mode 100644
index 0000000000000000000000000000000000000000..5ba3d7427480281c9cd5836ab622382513187a0f
--- /dev/null
+++ b/tests/googletest/feature_creation/units/test_untis.cc
@@ -0,0 +1,62 @@
+#include <feature_creation/units/Unit.hpp>
+#include "gtest/gtest.h"
+
+namespace {
+
+    //test unit's str constructor
+    TEST(Unit, StringConstructor)
+    {
+        Unit u_1("m");
+        Unit u_2("s");
+
+        EXPECT_NE(u_1, u_2);
+        EXPECT_EQ(u_1, Unit(u_1));
+        EXPECT_EQ(u_2, Unit(u_2));
+
+        EXPECT_STREQ(u_1.toString().c_str(), "m");
+        EXPECT_STREQ(u_2.toString().c_str(), "s");
+
+        EXPECT_EQ(u_1 / u_2, Unit("m/s"));
+        EXPECT_EQ(u_1 / u_1, Unit());
+        EXPECT_EQ(u_1 * u_2, Unit("m*s"));
+        EXPECT_EQ(u_1 * u_1, Unit("m^2.0"));
+        EXPECT_EQ(u_1 ^ 2.0, Unit("m^2.0"));
+        EXPECT_EQ(u_1.inverse(), Unit("1 / m"));
+
+        u_2 /= u_1;
+        EXPECT_EQ(u_2, Unit("s/m"));
+        u_2 *= u_2;
+        EXPECT_EQ(u_2, Unit("s * s/m^2"));
+    }
+
+    //test mean calculations
+    TEST(Unit, MapConstructor)
+    {
+        std::map<std::string, double> dct_1;
+        std::map<std::string, double> dct_2;
+
+        dct_1["m"] = 1.0;
+        dct_2["s"] = 1.0;
+        Unit u_1(dct_1);
+        Unit u_2(dct_2);
+
+        EXPECT_NE(u_1, u_2);
+        EXPECT_EQ(u_1, Unit(u_1));
+        EXPECT_EQ(u_2, Unit(u_2));
+
+        EXPECT_STREQ(u_1.toString().c_str(), "m");
+        EXPECT_STREQ(u_2.toString().c_str(), "s");
+
+        EXPECT_EQ(u_1 / u_2, Unit("m/s"));
+        EXPECT_EQ(u_1 / u_1, Unit());
+        EXPECT_EQ(u_1 * u_2, Unit("m*s"));
+        EXPECT_EQ(u_1 * u_1, Unit("m^2.0"));
+        EXPECT_EQ(u_1 ^ 2.0, Unit("m^2.0"));
+        EXPECT_EQ(u_1.inverse(), Unit("1 / m"));
+
+        u_2 /= u_1;
+        EXPECT_EQ(u_2, Unit("s/m"));
+        u_2 *= u_2;
+        EXPECT_EQ(u_2, Unit("s * s/m^2"));
+    }
+}
diff --git a/tests/googletest/feature_creation/utils/test_utils.cc b/tests/googletest/feature_creation/utils/test_utils.cc
new file mode 100644
index 0000000000000000000000000000000000000000..ef6b4240bed6293330717685df141f59d0d8bb69
--- /dev/null
+++ b/tests/googletest/feature_creation/utils/test_utils.cc
@@ -0,0 +1,117 @@
+#include <feature_creation/node/utils.hpp>
+#include <boost/filesystem.hpp>
+#include "gtest/gtest.h"
+
+namespace
+{
+    class FeatCreationUtilsTest : public ::testing::Test
+    {
+    protected:
+        void SetUp() override
+        {
+            node_value_arrs::initialize_values_arr(4, 1, 3);
+
+            std::vector<double> value_1 = {-1.0, -2.0, -3.0, -4.0};
+            std::vector<double> test_value_1 =  {50.0};
+
+            std::vector<double> value_2 = {1.0, 2.0, 3.0, 4.0};
+            std::vector<double> test_value_2 =  {5.0};
+
+            std::vector<double> value_3 = {1.0, -1.0, 1.0, -1.0};
+            std::vector<double> test_value_3 =  {1.0};
+
+            node_ptr feat_1 = std::make_shared<FeatureNode>(0, "A", value_1, test_value_1, Unit());
+            node_ptr feat_2 = std::make_shared<FeatureNode>(1, "B", value_2, test_value_2, Unit());
+            node_ptr feat_3 = std::make_shared<FeatureNode>(2, "C", value_3, test_value_3, Unit());
+
+            _phi0 = {feat_1, feat_2, feat_3};
+            _feat_ind = 3;
+        }
+
+        std::vector<node_ptr> _phi0;
+        int _feat_ind;
+    };
+
+    TEST_F(FeatCreationUtilsTest, TestPostfix2Node)
+    {
+        node_ptr test = str2node::postfix2node("0|2|div|exp|1|add", _phi0, _feat_ind);
+        EXPECT_EQ(test->type(), NODE_TYPE::ADD);
+        EXPECT_EQ(test->rung(), 3);
+        EXPECT_LT(abs(test->value()[1] - (std::exp(2.0) + 2.0)), 1e-10);
+        EXPECT_STREQ(test->expr().c_str(), "(exp([(A) / (C)]) + B)");
+    }
+
+    TEST_F(FeatCreationUtilsTest, TestPhiSelFromFile)
+    {
+        std::ofstream out_file_stream = std::ofstream();
+        out_file_stream.open("phi_sel.txt");
+        out_file_stream << std::setw(14) <<std::left << "# FEAT_ID" << "Feature Postfix Expression (RPN)" << std::endl;
+        out_file_stream << std::setw(14) <<std::left << 0 << "0|2|div|exp|1|add" << std::endl;
+        out_file_stream << std::setw(14) <<std::left << 1 << "0|2|div|nexp|1|add" << std::endl;
+        out_file_stream << std::setw(14) <<std::left << 2 << "0|2|div|sq|1|add" << std::endl;
+        out_file_stream << std::setw(14) <<std::left << 3 << "0|2|div|cb|1|add" << std::endl;
+        out_file_stream << std::setw(14) <<std::left << 4 << "0|2|div|sp|1|add" << std::endl;
+        out_file_stream << "#";
+        for(int dd = 0; dd < 71; ++dd)
+            out_file_stream << "-";
+        out_file_stream << std::endl;
+        out_file_stream << std::setw(14) <<std::left << 5 << "0|2|div|exp|1|sub" << std::endl;
+        out_file_stream << std::setw(14) <<std::left << 6 << "0|2|div|nexp|1|sub" << std::endl;
+        out_file_stream << std::setw(14) <<std::left << 7 << "0|2|div|sq|1|sub" << std::endl;
+        out_file_stream << std::setw(14) <<std::left << 8 << "0|2|div|cb|1|sub" << std::endl;
+        out_file_stream << std::setw(14) <<std::left << 9 << "0|2|div|sp|1|sub" << std::endl;
+        out_file_stream << "#";
+        for(int dd = 0; dd < 71; ++dd)
+            out_file_stream << "-";
+        out_file_stream << std::endl;
+        out_file_stream.close();
+
+        std::vector<node_ptr> phi_sel = str2node::phi_selected_from_file("phi_sel.txt", _phi0);
+
+        ASSERT_EQ(phi_sel.size(), 10);
+        EXPECT_EQ(phi_sel[0]->type(), NODE_TYPE::MODEL_FEATURE);
+        EXPECT_EQ(phi_sel[0]->rung(), 3);
+        EXPECT_LT(abs(phi_sel[0]->value()[1] - (std::exp(2.0) + 2.0)), 1e-10);
+        EXPECT_STREQ(phi_sel[0]->expr().c_str(), "(exp([(A) / (C)]) + B)");
+
+        EXPECT_EQ(phi_sel[5]->type(), NODE_TYPE::MODEL_FEATURE);
+        EXPECT_EQ(phi_sel[5]->rung(), 3);
+        EXPECT_LT(abs(phi_sel[5]->value()[1] - (std::exp(2.0) - 2.0)), 1e-10);
+        EXPECT_STREQ(phi_sel[5]->expr().c_str(), "[(exp([(A) / (C)])) - (B)]");
+        boost::filesystem::remove("phi_sel.txt");
+    }
+
+    TEST_F(FeatCreationUtilsTest, TestPhiFromFile)
+    {
+        std::ofstream out_file_stream = std::ofstream();
+        out_file_stream.open("phi.txt");
+        out_file_stream << "0|2|div|exp|1|add" << std::endl;
+        out_file_stream << "0|2|div|nexp|1|add" << std::endl;
+        out_file_stream << "0|2|div|sq|1|add" << std::endl;
+        out_file_stream << "0|2|div|cb|1|add" << std::endl;
+        out_file_stream << "0|2|div|sp|1|add" << std::endl;
+        out_file_stream << "0|2|div|exp|1|sub" << std::endl;
+        out_file_stream << "0|2|div|nexp|1|sub" << std::endl;
+        out_file_stream << "0|2|div|sq|1|sub" << std::endl;
+        out_file_stream << "0|2|div|cb|1|sub" << std::endl;
+        out_file_stream << "0|2|div|sp|1|sub" << std::endl;
+        out_file_stream << "#";
+        for(int dd = 0; dd < 71; ++dd)
+            out_file_stream << "-";
+        out_file_stream << std::endl;
+        out_file_stream.close();
+        std::vector<node_ptr> phi = str2node::phi_from_file("phi.txt", _phi0);
+
+        ASSERT_EQ(phi.size(), 10);
+        EXPECT_EQ(phi[0]->type(), NODE_TYPE::ADD);
+        EXPECT_EQ(phi[0]->rung(), 3);
+        EXPECT_LT(abs(phi[0]->value()[1] - (std::exp(2.0) + 2.0)), 1e-10);
+        EXPECT_STREQ(phi[0]->expr().c_str(), "(exp([(A) / (C)]) + B)");
+
+        EXPECT_EQ(phi[5]->type(), NODE_TYPE::SUB);
+        EXPECT_EQ(phi[5]->rung(), 3);
+        EXPECT_LT(abs(phi[5]->value()[1] - (std::exp(2.0) - 2.0)), 1e-10);
+        EXPECT_STREQ(phi[5]->expr().c_str(), "[(exp([(A) / (C)])) - (B)]");
+        boost::filesystem::remove("phi.txt");
+    }
+}
diff --git a/tests/googletest/feature_creation/value_storage/test_value_storage.cc b/tests/googletest/feature_creation/value_storage/test_value_storage.cc
new file mode 100644
index 0000000000000000000000000000000000000000..de08d837a85461a0179d1be9064eab9388a77c09
--- /dev/null
+++ b/tests/googletest/feature_creation/value_storage/test_value_storage.cc
@@ -0,0 +1,91 @@
+#include "gtest/gtest.h"
+#include <utils/project.hpp>
+#include <feature_creation/node/value_storage/nodes_value_containers.hpp>
+
+namespace {
+
+    //test mean calculations
+    TEST(ValueStorage, ValueStorageTest)
+    {
+        node_value_arrs::initialize_values_arr(5, 2, 1);
+        EXPECT_EQ(node_value_arrs::N_SAMPLES, 5);
+        EXPECT_EQ(node_value_arrs::N_SAMPLES_TEST, 2);
+        EXPECT_EQ(node_value_arrs::N_RUNGS_STORED, 0);
+        EXPECT_EQ(node_value_arrs::N_STORE_FEATURES, 1);
+        EXPECT_EQ(node_value_arrs::VALUES_ARR.size(), 5);
+        EXPECT_EQ(node_value_arrs::TEST_VALUES_ARR.size(), 2);
+        EXPECT_EQ(node_value_arrs::TEMP_STORAGE_ARR.size(), node_value_arrs::MAX_N_THREADS * (3 * 1 + 1) * 5);
+        EXPECT_EQ(node_value_arrs::TEMP_STORAGE_REG.size(), node_value_arrs::MAX_N_THREADS * (3 * 1 + 1));
+        EXPECT_EQ(node_value_arrs::TEMP_STORAGE_TEST_ARR.size(), node_value_arrs::MAX_N_THREADS * (3 * 1 + 1) * 2);
+        EXPECT_EQ(node_value_arrs::TEMP_STORAGE_TEST_REG.size(), node_value_arrs::MAX_N_THREADS * (3 * 1 + 1));
+
+        node_value_arrs::resize_values_arr(1, 2, false);
+        EXPECT_EQ(node_value_arrs::N_SAMPLES, 5);
+        EXPECT_EQ(node_value_arrs::N_SAMPLES_TEST, 2);
+        EXPECT_EQ(node_value_arrs::N_RUNGS_STORED, 1);
+        EXPECT_EQ(node_value_arrs::N_STORE_FEATURES, 2);
+        EXPECT_EQ(node_value_arrs::VALUES_ARR.size(), 10);
+        EXPECT_EQ(node_value_arrs::TEST_VALUES_ARR.size(), 4);
+        EXPECT_EQ(node_value_arrs::TEMP_STORAGE_ARR.size(), 0);
+        EXPECT_EQ(node_value_arrs::TEMP_STORAGE_REG.size(), 0);
+        EXPECT_EQ(node_value_arrs::TEMP_STORAGE_TEST_ARR.size(), 0);
+        EXPECT_EQ(node_value_arrs::TEMP_STORAGE_TEST_REG.size(), 0);
+
+        node_value_arrs::resize_values_arr(1, 2, true);
+        EXPECT_EQ(node_value_arrs::N_SAMPLES, 5);
+        EXPECT_EQ(node_value_arrs::N_SAMPLES_TEST, 2);
+        EXPECT_EQ(node_value_arrs::N_RUNGS_STORED, 1);
+        EXPECT_EQ(node_value_arrs::N_STORE_FEATURES, 2);
+        EXPECT_EQ(node_value_arrs::VALUES_ARR.size(), 10);
+        EXPECT_EQ(node_value_arrs::TEST_VALUES_ARR.size(), 4);
+        EXPECT_EQ(node_value_arrs::TEMP_STORAGE_ARR.size(), node_value_arrs::MAX_N_THREADS * (3 * 2 + 1) * 5);
+        EXPECT_EQ(node_value_arrs::TEMP_STORAGE_REG.size(), node_value_arrs::MAX_N_THREADS * (3 * 2 + 1));
+        EXPECT_EQ(node_value_arrs::TEMP_STORAGE_TEST_ARR.size(), node_value_arrs::MAX_N_THREADS * (3 * 2 + 1) * 2);
+        EXPECT_EQ(node_value_arrs::TEMP_STORAGE_TEST_REG.size(), node_value_arrs::MAX_N_THREADS * (3 * 2 + 1));
+
+        node_value_arrs::initialize_d_matrix_arr();
+        EXPECT_EQ(node_value_arrs::N_SELECTED, 0);
+        EXPECT_EQ(node_value_arrs::D_MATRIX.size(), 0);
+
+        node_value_arrs::resize_d_matrix_arr(2);
+        EXPECT_EQ(node_value_arrs::N_SELECTED, 2);
+        EXPECT_EQ(node_value_arrs::D_MATRIX.size(), 10);
+
+        node_value_arrs::resize_d_matrix_arr(3);
+        EXPECT_EQ(node_value_arrs::N_SELECTED, 5);
+        EXPECT_EQ(node_value_arrs::D_MATRIX.size(), 25);
+
+        node_value_arrs::get_value_ptr(1, 1, 0)[1] = 1.0;
+        EXPECT_EQ(node_value_arrs::VALUES_ARR[6], 1.0);
+
+        node_value_arrs::get_test_value_ptr(1, 1, 0)[1] = 1.0;
+        EXPECT_EQ(node_value_arrs::TEST_VALUES_ARR[3], 1.0);
+
+        node_value_arrs::get_value_ptr(10, 141, 1)[1] = 1.0;
+        EXPECT_EQ(node_value_arrs::temp_storage_reg(10, 1), 141);
+        EXPECT_EQ(node_value_arrs::access_temp_storage((10 % 2) + 2 + omp_get_thread_num() * (2 * 3 + 1))[1], 1.0);
+
+        node_value_arrs::get_test_value_ptr(10, 141, 1)[1] = 1.0;
+        EXPECT_EQ(node_value_arrs::temp_storage_test_reg(10, 1), 141);
+        EXPECT_EQ(node_value_arrs::access_temp_storage_test((10 % 2) + 2 + omp_get_thread_num() * (2 * 3 + 1))[1], 1.0);
+
+        node_value_arrs::get_d_matrix_ptr(1)[0] = 1.0;
+        EXPECT_EQ(node_value_arrs::D_MATRIX[5], 1.0);
+
+        #pragma omp parallel
+        {
+            std::fill_n(node_value_arrs::TEMP_STORAGE_REG.data() + 6 * omp_get_thread_num(), 6, omp_get_thread_num());
+            EXPECT_EQ(node_value_arrs::TEMP_STORAGE_REG[6 * omp_get_thread_num()], omp_get_thread_num());
+            node_value_arrs::clear_temp_reg_thread();
+            EXPECT_EQ(node_value_arrs::TEMP_STORAGE_REG[6 * omp_get_thread_num()], -1);
+        }
+
+        std::fill_n(node_value_arrs::TEMP_STORAGE_REG.data(), node_value_arrs::TEMP_STORAGE_REG.size(), 2.0);
+        node_value_arrs::clear_temp_reg();
+        EXPECT_EQ(node_value_arrs::TEMP_STORAGE_REG[0], -1);
+
+        std::fill_n(node_value_arrs::TEMP_STORAGE_TEST_REG.data(), node_value_arrs::TEMP_STORAGE_REG.size(), 2.0);
+        node_value_arrs::clear_temp_test_reg();
+        EXPECT_EQ(node_value_arrs::TEMP_STORAGE_TEST_REG[0], -1);
+    }
+}
\ No newline at end of file
diff --git a/tests/googletest/test_main.cpp b/tests/googletest/test_main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e4db8d28e8ae6f9113652df45b00ca27a8be51f4
--- /dev/null
+++ b/tests/googletest/test_main.cpp
@@ -0,0 +1,6 @@
+#include <googletest/utils/test_math_utils.cc>
+
+int main(int argc, char **argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+    return RUN_ALL_TESTS();
+}
\ No newline at end of file
diff --git a/tests/googletest/utils/test_compare_features.cc b/tests/googletest/utils/test_compare_features.cc
new file mode 100644
index 0000000000000000000000000000000000000000..826469278a3b09f45cb7b407e6dcf4c1a6714087
--- /dev/null
+++ b/tests/googletest/utils/test_compare_features.cc
@@ -0,0 +1,34 @@
+#include "gtest/gtest.h"
+#include <utils/compare_features.hpp>
+#include <feature_creation/node/ModelNode.hpp>
+
+namespace {
+    //test mean calculations
+    TEST(CompFeats, CompFeatTest)
+    {
+        std::vector<double> val_1 = {1.0, 2.0, 3.0, 4.0};
+        std::vector<double> val_2 = {2.0, 2.0, 3.0, 4.0};
+        std::vector<double> val_3 = {2.0, 4.0, 6.0, 8.0};
+        std::vector<double> target = {1.0, 3.0, 5.0, 6.0};
+        std::vector<double> scores = {0.9897782665572893};
+        std::vector<node_ptr> selected(1);
+
+        selected[0] = std::make_shared<ModelNode>(0, 0, "A", "$A$", "0", val_3, std::vector<double>(), Unit());
+
+        node_value_arrs::initialize_values_arr(4, 0, 1);
+        node_value_arrs::initialize_d_matrix_arr();
+        node_value_arrs::resize_d_matrix_arr(1);
+
+        std::copy_n(val_3.data(), val_3.size(), node_value_arrs::get_d_matrix_ptr(0));
+
+        EXPECT_FALSE(comp_feats::valid_feature_against_selected_max_corr_1(val_1.data(), 4, 1.0, scores, 0.9897782665572893, 1, 0));
+        EXPECT_FALSE(comp_feats::valid_feature_against_selected_max_corr_1_feat_list(val_1.data(), 4, 1.0, selected, scores, 0.9897782665572893));
+        EXPECT_FALSE(comp_feats::valid_feature_against_selected(val_1.data(), 4, 1.0, scores, 0.9897782665572893, 1, 0));
+        EXPECT_FALSE(comp_feats::valid_feature_against_selected_feat_list(val_1.data(), 4, 1.0, selected, scores, 0.9897782665572893));
+
+        EXPECT_TRUE(comp_feats::valid_feature_against_selected_max_corr_1(val_2.data(), 4, 1.0, scores, 0.9028289727756884, 1, 0));
+        EXPECT_TRUE(comp_feats::valid_feature_against_selected_max_corr_1_feat_list(val_2.data(), 4, 1.0, selected, scores, 0.9028289727756884));
+        EXPECT_TRUE(comp_feats::valid_feature_against_selected(val_2.data(), 4, 1.0, scores, 0.9028289727756884, 1, 0));
+        EXPECT_TRUE(comp_feats::valid_feature_against_selected_feat_list(val_2.data(), 4, 1.0, selected, scores, 0.9028289727756884));
+    }
+}
diff --git a/tests/googletest/utils/test_math_utils.cc b/tests/googletest/utils/test_math_utils.cc
new file mode 100644
index 0000000000000000000000000000000000000000..139114bf9fc871e45a37b5bce6bf8142b60c2ab6
--- /dev/null
+++ b/tests/googletest/utils/test_math_utils.cc
@@ -0,0 +1,193 @@
+#include "gtest/gtest.h"
+#include <utils/math_funcs.hpp>
+
+namespace {
+    // Setup initial test vectors
+    std::vector<double> dVec1(16, 1.0);
+    std::vector<double> dVec2 = {1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0};
+    std::vector<double> dVec3 = {2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0};
+
+    std::vector<int> iVec1(16, 1);
+    std::vector<int> iVec2 = {1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    std::vector<int> iVec3 = {2, 4, 4, 4, 5, 5, 7, 9};
+
+    //test mean calculations
+    TEST(MathUtils, MeanTest)
+    {
+        EXPECT_EQ(util_funcs::mean(dVec1), 1.0);
+        EXPECT_EQ(util_funcs::mean(iVec1), 1.0);
+
+        EXPECT_EQ(util_funcs::mean(dVec1.data(), 16), 1.0);
+        EXPECT_EQ(util_funcs::mean(iVec1.data(), 16), 1.0);
+
+        EXPECT_EQ(util_funcs::mean(dVec2), 0.25);
+        EXPECT_EQ(util_funcs::mean(iVec2), 0.25);
+
+        EXPECT_EQ(util_funcs::mean(dVec2.data(), 16), 0.25);
+        EXPECT_EQ(util_funcs::mean(iVec2.data(), 16), 0.25);
+
+        EXPECT_EQ(util_funcs::mean(dVec3), 5.0);
+        EXPECT_EQ(util_funcs::mean(iVec3), 5.0);
+
+        EXPECT_EQ(util_funcs::mean(dVec3.data(), 8), 5.0);
+        EXPECT_EQ(util_funcs::mean(iVec3.data(), 8), 5.0);
+    }
+
+    //test standard deviation calculations
+    TEST(MathUtils, STDTest)
+    {
+        EXPECT_EQ(util_funcs::stand_dev(dVec1), 0.0);
+        EXPECT_EQ(util_funcs::stand_dev(dVec1.data(), 16), 0.0);
+        EXPECT_EQ(util_funcs::stand_dev(dVec1, 1.0), 0.0);
+        EXPECT_EQ(util_funcs::stand_dev(dVec1.data(), 16, 1.0), 0.0);
+
+        EXPECT_EQ(util_funcs::stand_dev(dVec3), 2.0);
+        EXPECT_EQ(util_funcs::stand_dev(dVec3.data(), 8), 2.0);
+        EXPECT_EQ(util_funcs::stand_dev(dVec3, 5.0), 2.0);
+        EXPECT_EQ(util_funcs::stand_dev(dVec3.data(), 8, 5.0), 2.0);
+    }
+
+    //test norm calculations
+    TEST(MathUtils, NormTest)
+    {
+        EXPECT_EQ(util_funcs::norm(dVec1), 4.0);
+        EXPECT_EQ(util_funcs::norm(dVec1.data(), 16), 4.0);
+
+        EXPECT_EQ(util_funcs::norm(dVec2), 2.0);
+        EXPECT_EQ(util_funcs::norm(dVec2.data(), 16), 2.0);
+    }
+
+    //test Pearson correlation
+    TEST(MathUtils, RTest)
+    {
+        std::vector<double> dNeg2(16, 0);
+        std::transform(dVec2.begin(), dVec2.end(), dNeg2.begin(), [](double dd){return -1.0 * dd;});
+
+        std::vector<int> szs = {2, 14};
+        EXPECT_FALSE(std::isfinite(util_funcs::r(dVec1.data(), dVec2.data(), 16)));
+        EXPECT_FALSE(std::isfinite(util_funcs::r(dVec1.data(), dVec2.data(), szs)));
+        EXPECT_FALSE(std::isfinite(util_funcs::r(dVec1.data(), dVec2.data(), szs.data(), 2)));
+
+        EXPECT_TRUE(std::isfinite(util_funcs::r(dVec2.data(), dVec2.data(), 16)));
+        EXPECT_FALSE(std::isfinite(util_funcs::r(dVec2.data(), dVec2.data(), szs)));
+        EXPECT_FALSE(std::isfinite(util_funcs::r(dVec2.data(), dVec2.data(), szs.data(), 2)));
+
+        szs = {8, 8};
+        EXPECT_LT(std::abs(1.0 - util_funcs::r(dVec2.data(), dVec2.data(), 16)), 1.0e-10);
+        EXPECT_LT(std::abs(1.0 - util_funcs::r(dVec2.data(), dVec2.data(), szs)), 1.0e-10);
+        EXPECT_LT(std::abs(1.0 - util_funcs::r(dVec2.data(), dVec2.data(), szs.data(), 2)), 1.0e-10);
+
+        EXPECT_LT(std::abs(1.0 + util_funcs::r(dVec2.data(), dNeg2.data(), 16)), 1e-10);
+        EXPECT_LT(std::abs(1.0 - util_funcs::r(dVec2.data(), dNeg2.data(), szs)), 1e-10);
+        EXPECT_LT(std::abs(1.0 - util_funcs::r(dVec2.data(), dNeg2.data(), szs.data(), 2)), 1e-10);
+    }
+
+    //test r^2
+    TEST(MathUtils, R2Test)
+    {
+        std::vector<double> dNeg2(16, 0);
+        std::transform(dVec2.begin(), dVec2.end(), dNeg2.begin(), [](double dd){return -1.0 * dd;});
+
+        std::vector<int> szs = {2, 14};
+        EXPECT_FALSE(std::isfinite(util_funcs::r2(dVec1.data(), dVec2.data(), 16)));
+        EXPECT_FALSE(std::isfinite(util_funcs::r2(dVec1.data(), dVec2.data(), szs)));
+        EXPECT_FALSE(std::isfinite(util_funcs::r2(dVec1.data(), dVec2.data(), szs.data(), 2)));
+
+        EXPECT_TRUE(std::isfinite(util_funcs::r2(dVec2.data(), dVec2.data(), 16)));
+        EXPECT_FALSE(std::isfinite(util_funcs::r2(dVec2.data(), dVec2.data(), szs)));
+        EXPECT_FALSE(std::isfinite(util_funcs::r2(dVec2.data(), dVec2.data(), szs.data(), 2)));
+
+        szs = {8, 8};
+        EXPECT_LT(std::abs(1.0 - util_funcs::r2(dVec2.data(), dVec2.data(), 16)), 1.0e-10);
+        EXPECT_LT(std::abs(1.0 - util_funcs::r2(dVec2.data(), dVec2.data(), szs)), 1.0e-10);
+        EXPECT_LT(std::abs(1.0 - util_funcs::r2(dVec2.data(), dVec2.data(), szs.data(), 2)), 1.0e-10);
+
+        EXPECT_LT(std::abs(1.0 - util_funcs::r2(dVec2.data(), dNeg2.data(), 16)), 1.0e-10);
+        EXPECT_LT(std::abs(1.0 - util_funcs::r2(dVec2.data(), dNeg2.data(), szs)), 1.0e-10);
+        EXPECT_LT(std::abs(1.0 - util_funcs::r2(dVec2.data(), dNeg2.data(), szs.data(), 2)), 1.0e-10);
+    }
+
+    //test log_r^2
+    TEST(MathUtils, LogR2Test)
+    {
+        std::vector<double> dNeg2(16, 0);
+        std::vector<double> log_x(16, 0);
+        std::transform(dVec2.begin(), dVec2.end(), dNeg2.begin(), [](double dd){return -1.0 * dd;});
+
+        std::vector<int> szs = {2, 14};
+        EXPECT_FALSE(std::isfinite(util_funcs::log_r2(dVec1.data(), dVec2.data(), log_x.data(), 16)));
+        EXPECT_FALSE(std::isfinite(util_funcs::log_r2(dVec1.data(), dVec2.data(), log_x.data(), szs)));
+        EXPECT_FALSE(std::isfinite(util_funcs::log_r2(dVec1.data(), dVec2.data(), log_x.data(), szs.data(), 2)));
+
+        EXPECT_FALSE(std::isfinite(util_funcs::log_r2(dVec2.data(), dVec2.data(), log_x.data(), 16)));
+        EXPECT_FALSE(std::isfinite(util_funcs::log_r2(dVec2.data(), dVec2.data(), log_x.data(), szs)));
+        EXPECT_FALSE(std::isfinite(util_funcs::log_r2(dVec2.data(), dVec2.data(), log_x.data(), szs.data(), 2)));
+
+        szs = {2, 2};
+        std::vector<double> x = {1, 10, 1000, 10000};
+        std::vector<double> y = {0, 1, 3, 4};
+        EXPECT_LT(std::abs(1.0 - util_funcs::log_r2(x.data(), y.data(), log_x.data(), 4)), 1.0);
+        EXPECT_LT(std::abs(1.0 - util_funcs::log_r2(x.data(), y.data(), log_x.data(), szs)), 1.0);
+        EXPECT_LT(std::abs(1.0 - util_funcs::log_r2(x.data(), y.data(), log_x.data(), szs.data(), 2)), 1.0);
+
+    }
+
+    //test argsort
+    TEST(MathUtils, ArgSortTest)
+    {
+        std::vector<double> to_sort = {3.0, 4.0, 2.0};
+        std::vector<int> inds;
+
+        inds = util_funcs::argsort(to_sort);
+        EXPECT_EQ(std::abs(inds[0] - 2) + std::abs(inds[1] - 0) + std::abs(inds[2] - 1), 0.0);
+
+        util_funcs::argsort(inds.data(), inds.data() + inds.size(), to_sort);
+        EXPECT_EQ(std::abs(inds[0] - 2) + std::abs(inds[1] - 0) + std::abs(inds[2] - 1), 0.0);
+
+        util_funcs::argsort(inds.data(), inds.data() + inds.size(), to_sort.data());
+        EXPECT_EQ(std::abs(inds[0] - 2) + std::abs(inds[1] - 0) + std::abs(inds[2] - 1), 0.0);
+
+        std::vector<double> to_sort_int = {3, 4, 2};
+        inds = util_funcs::argsort(to_sort_int);
+        EXPECT_EQ(std::abs(inds[0] - 2) + std::abs(inds[1] - 0) + std::abs(inds[2] - 1), 0.0);
+
+        util_funcs::argsort(inds.data(), inds.data() + inds.size(), to_sort_int);
+        EXPECT_EQ(std::abs(inds[0] - 2) + std::abs(inds[1] - 0) + std::abs(inds[2] - 1), 0.0);
+
+        util_funcs::argsort(inds.data(), inds.data() + inds.size(), to_sort_int.data());
+        EXPECT_EQ(std::abs(inds[0] - 2) + std::abs(inds[1] - 0) + std::abs(inds[2] - 1), 0.0);
+    }
+
+    // test max_abs_val
+    TEST(MathUtils, MaxAbsValTest)
+    {
+        std::vector<double> dNeg3(16, 0);
+        std::transform(dVec3.begin(), dVec3.end(), dNeg3.begin(), [](double dd){return -1.0 * dd;});
+
+        EXPECT_EQ(util_funcs::max_abs_val<double>(dVec3.data(), dVec3.data() + dVec3.size()), 9.0);
+        EXPECT_EQ(util_funcs::max_abs_val<double>(dVec3.data(), dVec3.size()), 9.0);
+
+        EXPECT_EQ(util_funcs::max_abs_val<double>(dNeg3.data(), dNeg3.data() + dNeg3.size()), 9.0);
+        EXPECT_EQ(util_funcs::max_abs_val<double>(dNeg3.data(), dNeg3.size()), 9.0);
+    }
+
+    // test iterate
+    TEST(MathUtils, IterateTest)
+    {
+        std::vector<int> inds = {5, 4, 3};
+
+        util_funcs::iterate(inds, 3, 1);
+        EXPECT_EQ(std::abs(inds[0] - 5) + std::abs(inds[1] - 4) + std::abs(inds[2] - 2), 0.0);
+
+        util_funcs::iterate(inds, 3, 2);
+        EXPECT_EQ(std::abs(inds[0] - 5) + std::abs(inds[1] - 4) + std::abs(inds[2] - 0), 0.0);
+
+        util_funcs::iterate(inds, 3, 7);
+        EXPECT_EQ(std::abs(inds[0] - 4) + std::abs(inds[1] - 3) + std::abs(inds[2] - 2), 0.0);
+
+        util_funcs::iterate(inds, 3, 9);
+        EXPECT_EQ(std::abs(inds[0] - 2) + std::abs(inds[1] - 1) + std::abs(inds[2] - 0), 0.0);
+
+        EXPECT_FALSE(util_funcs::iterate(inds, 3, 1));
+    }
+}
\ No newline at end of file
diff --git a/tests/googletest/utils/test_project.cc b/tests/googletest/utils/test_project.cc
new file mode 100644
index 0000000000000000000000000000000000000000..af5932cf59c72a0dbb521836f14aa306506c2e27
--- /dev/null
+++ b/tests/googletest/utils/test_project.cc
@@ -0,0 +1,51 @@
+#include "gtest/gtest.h"
+#include <utils/project.hpp>
+#include <feature_creation/node/ModelNode.hpp>
+
+namespace {
+
+    //test mean calculations
+    TEST(Project, ProjectTest)
+    {
+        std::vector<double> prop = {1.0, 3.0, 5.0, 6.0};
+        std::vector<double> prop_class = {0.0, 0.0, 0.0, 1.0};
+        std::vector<double> val = {2.0, 2.0, 3.0, 4.0};
+        std::vector<double> scores(1, 0.0);
+        std::vector<int> sizes(1, 4);
+
+        std::vector<node_ptr> phi = {std::make_shared<ModelNode>(0, 0, "A", "$A$", "0", val, std::vector<double>(), Unit())};
+
+        project_funcs::project_r(prop.data(), scores.data(), phi, sizes, 1);
+        EXPECT_LT(std::abs(-0.9028289727756884 - scores[0]), 1e-10);
+        scores[0] = 0.0;
+
+        project_funcs::project_r2(prop.data(), scores.data(), phi, sizes, 1);
+        EXPECT_LT(std::abs(-0.8151001540832047 - scores[0]), 1e-10);
+        scores[0] = 0.0;
+
+        project_funcs::project_log_r2(prop.data(), scores.data(), phi, sizes, 1);
+        EXPECT_LT(std::abs(-0.8437210425744424 - scores[0]), 1e-10);
+        scores[0] = 0.0;
+
+        project_funcs::project_classify(prop_class.data(), scores.data(), phi, sizes, 1);
+        EXPECT_LT(std::abs(-0.4999850001499985 - scores[0]), 1e-10);
+        scores[0] = 0.0;
+
+        project_funcs::project_r_no_omp(prop.data(), scores.data(), phi, sizes, 1);
+        EXPECT_LT(std::abs(-0.9028289727756884 - scores[0]), 1e-10);
+        scores[0] = 0.0;
+
+        project_funcs::project_r2_no_omp(prop.data(), scores.data(), phi, sizes, 1);
+        EXPECT_LT(std::abs(-0.8151001540832047 - scores[0]), 1e-10);
+        scores[0] = 0.0;
+
+        project_funcs::project_log_r2_no_omp(prop.data(), scores.data(), phi, sizes, 1);
+        EXPECT_LT(std::abs(-0.8437210425744424 - scores[0]), 1e-10);
+        scores[0] = 0.0;
+
+        project_funcs::project_classify_no_omp(prop_class.data(), scores.data(), phi, sizes, 1);
+        EXPECT_LT(std::abs(-0.4999850001499985 - scores[0]), 1e-10);
+        scores[0] = 0.0;
+
+    }
+}
diff --git a/tests/googletest/utils/test_str_utils.cc b/tests/googletest/utils/test_str_utils.cc
new file mode 100644
index 0000000000000000000000000000000000000000..1e15717b7d81e39586af932811ad90c2b2134e4e
--- /dev/null
+++ b/tests/googletest/utils/test_str_utils.cc
@@ -0,0 +1,24 @@
+#include "gtest/gtest.h"
+#include <utils/string_utils.hpp>
+
+namespace {
+
+    //test mean calculations
+    TEST(StrUtils, SplitTrimTest)
+    {
+        std::vector<std::string> str_split = str_utils::split_string_trim("A ; B ; C , D :     5");
+        EXPECT_EQ(str_split.size(), 5);
+        EXPECT_STREQ(str_split[0].c_str(), "A");
+        EXPECT_STREQ(str_split[1].c_str(), "B");
+        EXPECT_STREQ(str_split[2].c_str(), "C");
+        EXPECT_STREQ(str_split[3].c_str(), "D");
+        EXPECT_STREQ(str_split[4].c_str(), "5");
+
+        str_split = str_utils::split_string_trim("A ; B ; C , D :     5", ";,");
+        EXPECT_EQ(str_split.size(), 4);
+        EXPECT_STREQ(str_split[0].c_str(), "A");
+        EXPECT_STREQ(str_split[1].c_str(), "B");
+        EXPECT_STREQ(str_split[2].c_str(), "C");
+        EXPECT_STREQ(str_split[3].c_str(), "D :     5");
+    }
+}
\ No newline at end of file