diff --git a/CMakeLists.txt b/CMakeLists.txt
index ec88e1f49fd6aca560fa7e0d1e1f95a47ba853f4..df9e571c293da4a57c44a37243e25c48000987ee 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -305,8 +305,6 @@ list(GET MPI_CXX_LIBRARIES 0 MPI_LIBRARY)
 get_filename_component(MPI_DIR ${MPI_LIBRARY} DIRECTORY)
 
 # Build libnlopt
-
-message(STATUS "CMAKE_C_COMPILER: ${CMAKE_C_COMPILER}")
 set(NLOPT_BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/external/nlopt/build/")
 set(NLOPT_INSTALL_DIR "${CMAKE_CURRENT_BINARY_DIR}/external/nlopt/bin/")
 set(NLOPT_INCLUDE_DIRS "${NLOPT_INSTALL_DIR}/include/")
@@ -377,9 +375,26 @@ if(NOT GTEST_FOUND)
     )
     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})
 
+set(FMT_BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/external/fmt/build/")
+set(FMT_INSTALL_DIR "${CMAKE_CURRENT_BINARY_DIR}/external/fmt/bin/")
+set(FMT_INCLUDE_DIRS "${FMT_INSTALL_DIR}/include/")
+set(FMT_LIBRARY_DIRS "${FMT_INSTALL_DIR}/lib/")
+
+ExternalProject_Add(
+    external_fmt
+    PREFIX "external/fmt"
+    GIT_REPOSITORY "https://github.com/fmtlib/fmt.git"
+    GIT_TAG "7.1.2"
+    CMAKE_ARGS "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER};-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER};-DINSTALL_LIB_DIR=${FMT_LIBRARY_DIRS};-DCMAKE_INSTALL_PREFIX=${FMT_INSTALL_DIR};-DFMT_DOC=OFF;-DFMT_TEST=OFF;-DFMT_OS=OFF;-DBUILD_SHARED_LIBS=ON"
+    BINARY_DIR "${FMT_BUILD_DIR}"
+    INSTALL_DIR "${FMT_INSTALL_DIR}"
+)
+include_directories(${FMT_INCLUDE_DIRS})
+set(FMT_LIBRARIES "${FMT_LIBRARY_DIRS}/libfmt.so")
+message(STATUS "FMT_LIBRARIES: ${FMT_LIBRARIES}")
+
 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/CMakeLists.txt b/src/CMakeLists.txt
index 01942753eb68f4a50f9f3331a43ac86a52596a80..28ba57bfeb4c5534e6b291b27b07e2dbcaf5e696 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -2,7 +2,7 @@ include_directories(${CMAKE_CURRENT_LIST_DIR}/feature_creation/domain/)
 include_directories(${CMAKE_CURRENT_LIST_DIR})
 
 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};${NLOPT_LIBRARY_DIRS};${COIN_UTILS_LIBRARY_DIRS};${CMAKE_CURRENT_LIST_DIR}/../lib/)
+set(CMAKE_INSTALL_RPATH ${Boost_LIBRARY_DIRS};${LAPACK_DIR};${MPI_DIR};${COIN_CLP_LIBRARY_DIRS};${NLOPT_LIBRARY_DIRS};${COIN_UTILS_LIBRARY_DIRS};${CMAKE_CURRENT_LIST_DIR}/../lib/;${FMT_LIBRARY_DIRS})
 message(STATUS "CMAKE_CXX_FLAGS: ${CMAKE_CXX_FLAGS}")
 
 # set(INSTALL_RPATH ${Boost_LIB_DIR})
@@ -30,7 +30,7 @@ set_target_properties(libsisso
     SUFFIX ".so"
 )
 
-target_link_libraries(libsisso ${LAPACK_LIBRARIES} ${MPI_LIBRARIES} -Wl,--rpath=${Boost_LIB_DIR} -Wl,--rpath=${LAPACK_DIR} ${Boost_LIBRARIES} ${COIN_CLP_LIBRARIES} ${OPENMP_LIBRARIES})
+target_link_libraries(libsisso ${LAPACK_LIBRARIES} ${MPI_LIBRARIES} -Wl,--rpath=${Boost_LIB_DIR} -Wl,--rpath=${LAPACK_DIR} ${Boost_LIBRARIES} ${COIN_CLP_LIBRARIES} ${OPENMP_LIBRARIES} ${FMT_LIBRARIES})
 message(STATUS "Testing without coverage.")
 
 install(TARGETS libsisso DESTINATION ${CMAKE_CURRENT_LIST_DIR}/../lib/)
@@ -44,12 +44,12 @@ set_target_properties(sisso++
     RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
 )
 
-target_link_libraries(sisso++ libsisso ${LAPACK_LIBRARIES} ${MPI_LIBRARIES} -Wl,--rpath=${Boost_LIB_DIR} -Wl,--rpath=${LAPACK_DIR} ${Boost_LIBRARIES} ${COIN_CLP_LIBRARIES} ${NLOPT_LIBRARIES} ${OPENMP_LIBRARIES})
+target_link_libraries(sisso++ libsisso ${LAPACK_LIBRARIES} ${MPI_LIBRARIES} -Wl,--rpath=${Boost_LIB_DIR} -Wl,--rpath=${LAPACK_DIR} ${Boost_LIBRARIES} ${COIN_CLP_LIBRARIES} ${NLOPT_LIBRARIES} ${OPENMP_LIBRARIES} ${FMT_LIBRARIES})
 install(TARGETS sisso++ DESTINATION ${CMAKE_CURRENT_LIST_DIR}/../bin/)
 
 if(USE_PYTHON)
     include(${CMAKE_CURRENT_LIST_DIR}/../cmake/TransferDocStrings.cmake)
-    set(CMAKE_INSTALL_RPATH ${PYTHON_PREFIX}/lib/;${Boost_LIBRARY_DIRS};${LAPACK_DIR};${MPI_DIR};${COIN_CLP_LIBRARY_DIRS};${NLOPT_LIBRARY_DIRS};${COIN_UTILS_LIBRARY_DIRS};${CMAKE_CURRENT_LIST_DIR}/../lib/;${PYTHON_INSTDIR}/cpp_sisso/)
+    set(CMAKE_INSTALL_RPATH ${PYTHON_PREFIX}/lib/;${Boost_LIBRARY_DIRS};${LAPACK_DIR};${MPI_DIR};${COIN_CLP_LIBRARY_DIRS};${NLOPT_LIBRARY_DIRS};${COIN_UTILS_LIBRARY_DIRS};${CMAKE_CURRENT_LIST_DIR}/../lib/;${PYTHON_INSTDIR}/cpp_sisso/;${FMT_LIBRARY_DIRS})
 
     transfer_doc_string(${CMAKE_CURRENT_LIST_DIR}/python/bindings_docstring_keyed.cpp ${CMAKE_CURRENT_LIST_DIR}/python/bindings.cpp)
     transfer_doc_string(${CMAKE_CURRENT_LIST_DIR}/python/bindings_docstring_keyed.hpp ${CMAKE_CURRENT_LIST_DIR}/python/bindings.hpp)
diff --git a/src/feature_creation/feature_space/FeatureSpace.cpp b/src/feature_creation/feature_space/FeatureSpace.cpp
index 4cee1bf875c69904308feffa101adb3ca12673f5..e7b5fbbfcceedd132d1553fdb91e8cd0959dcd53 100644
--- a/src/feature_creation/feature_space/FeatureSpace.cpp
+++ b/src/feature_creation/feature_space/FeatureSpace.cpp
@@ -38,23 +38,23 @@ BOOST_CLASS_EXPORT_GUID(InvParamNode, "InvParamNode")
 BOOST_CLASS_EXPORT_GUID(SinParamNode, "SinParamNode")
 BOOST_CLASS_EXPORT_GUID(CosParamNode, "CosParamNode")
 
-BOOST_CLASS_EXPORT_GUID(AddParamTopLevNode, "AddParamTopLevNode")
-BOOST_CLASS_EXPORT_GUID(SubParamTopLevNode, "SubParamTopLevNode")
-BOOST_CLASS_EXPORT_GUID(AbsDiffParamTopLevNode, "AbsDiffParamTopLevNode")
-BOOST_CLASS_EXPORT_GUID(MultParamTopLevNode, "MultParamTopLevNode")
-BOOST_CLASS_EXPORT_GUID(DivParamTopLevNode, "DivParamTopLevNode")
-BOOST_CLASS_EXPORT_GUID(SqParamTopLevNode, "SqParamTopLevNode")
-BOOST_CLASS_EXPORT_GUID(SqrtParamTopLevNode, "SqrtParamTopLevNode")
-BOOST_CLASS_EXPORT_GUID(CbParamTopLevNode, "CbParamTopLevNode")
-BOOST_CLASS_EXPORT_GUID(CbrtParamTopLevNode, "CbrtParamTopLevNode")
-BOOST_CLASS_EXPORT_GUID(SixPowParamTopLevNode, "SixPowParamTopLevNode")
-BOOST_CLASS_EXPORT_GUID(ExpParamTopLevNode, "ExpParamTopLevNode")
-BOOST_CLASS_EXPORT_GUID(NegExpParamTopLevNode, "NegExpParamTopLevNode")
-BOOST_CLASS_EXPORT_GUID(LogParamTopLevNode, "LogParamTopLevNode")
-BOOST_CLASS_EXPORT_GUID(AbsParamTopLevNode, "AbsParamTopLevNode")
-BOOST_CLASS_EXPORT_GUID(InvParamTopLevNode, "InvParamTopLevNode")
-BOOST_CLASS_EXPORT_GUID(SinParamTopLevNode, "SinParamTopLevNode")
-BOOST_CLASS_EXPORT_GUID(CosParamTopLevNode, "CosParamTopLevNode")
+// BOOST_CLASS_EXPORT_GUID(AddParamTopLevNode, "AddParamTopLevNode")
+// BOOST_CLASS_EXPORT_GUID(SubParamTopLevNode, "SubParamTopLevNode")
+// BOOST_CLASS_EXPORT_GUID(AbsDiffParamTopLevNode, "AbsDiffParamTopLevNode")
+// BOOST_CLASS_EXPORT_GUID(MultParamTopLevNode, "MultParamTopLevNode")
+// BOOST_CLASS_EXPORT_GUID(DivParamTopLevNode, "DivParamTopLevNode")
+// BOOST_CLASS_EXPORT_GUID(SqParamTopLevNode, "SqParamTopLevNode")
+// BOOST_CLASS_EXPORT_GUID(SqrtParamTopLevNode, "SqrtParamTopLevNode")
+// BOOST_CLASS_EXPORT_GUID(CbParamTopLevNode, "CbParamTopLevNode")
+// BOOST_CLASS_EXPORT_GUID(CbrtParamTopLevNode, "CbrtParamTopLevNode")
+// BOOST_CLASS_EXPORT_GUID(SixPowParamTopLevNode, "SixPowParamTopLevNode")
+// BOOST_CLASS_EXPORT_GUID(ExpParamTopLevNode, "ExpParamTopLevNode")
+// BOOST_CLASS_EXPORT_GUID(NegExpParamTopLevNode, "NegExpParamTopLevNode")
+// BOOST_CLASS_EXPORT_GUID(LogParamTopLevNode, "LogParamTopLevNode")
+// BOOST_CLASS_EXPORT_GUID(AbsParamTopLevNode, "AbsParamTopLevNode")
+// BOOST_CLASS_EXPORT_GUID(InvParamTopLevNode, "InvParamTopLevNode")
+// BOOST_CLASS_EXPORT_GUID(SinParamTopLevNode, "SinParamTopLevNode")
+// BOOST_CLASS_EXPORT_GUID(CosParamTopLevNode, "CosParamTopLevNode")
 
 FeatureSpace::FeatureSpace(
     std::shared_ptr<MPI_Interface> mpi_comm,
@@ -71,7 +71,7 @@ FeatureSpace::FeatureSpace(
     double cross_corr_max,
     double min_abs_feat_val,
     double max_abs_feat_val,
-    bool param_internal
+    int max_param_depth
 ):
     _phi(phi_0),
     _phi_0(phi_0),
@@ -93,7 +93,7 @@ FeatureSpace::FeatureSpace(
     _n_feat(phi_0.size()),
     _n_rung_store(max_store_rung),
     _n_rung_generate(n_rung_generate),
-    _param_internal(param_internal)
+    _max_param_depth(max_param_depth)
 {
     initialize_fs(project_type);
 }
@@ -104,7 +104,7 @@ void FeatureSpace::initialize_fs(std::string project_type)
         if(_allowed_param_ops.size() != 0)
             throw std::logic_error("Parameterization is not possible recompile with -DPARAMETERIZE");
     #else
-        nlopt_wrapper::set_objective(project_type, _prop.data(), _task_sizes, _max_phi, _param_internal);
+        nlopt_wrapper::set_objective(project_type, _prop.data(), _task_sizes, _max_phi, _max_param_depth);
     #endif
 
     if(_n_rung_store == -1)
diff --git a/src/feature_creation/feature_space/FeatureSpace.hpp b/src/feature_creation/feature_space/FeatureSpace.hpp
index c828581a9037843cc7626afd0a5ce31fc8f83293..92a86ad458a9200b41b652a3e50d92c16e8f97b4 100644
--- a/src/feature_creation/feature_space/FeatureSpace.hpp
+++ b/src/feature_creation/feature_space/FeatureSpace.hpp
@@ -83,7 +83,8 @@ class FeatureSpace
     const int _n_samp; //!< Number of samples (training data)
     const int _n_rung_generate; //!< Total number of rungs to generate on the fly
 
-    bool _param_internal; //!< True if parameterize all scale and shift parameters in a feature
+    int _max_param_depth; //!< Max depth to parameterize a feature (default=_max_rung)
+
 public:
 
     /**
@@ -120,7 +121,7 @@ public:
         double cross_corr_max=1.0,
         double min_abs_feat_val=1e-50,
         double max_abs_feat_val=1e50,
-        bool param_internal=true
+        int max_param_depth = 100
     );
 
     /**
@@ -303,7 +304,7 @@ public:
             double cross_corr_max=1.0,
             double min_abs_feat_val=1e-50,
             double max_abs_feat_val=1e50,
-            bool param_internal=true
+            int max_param_depth = 100
         );
 
         /**
@@ -338,7 +339,7 @@ public:
             double cross_corr_max=1.0,
             double min_abs_feat_val=1e-50,
             double max_abs_feat_val=1e50,
-            bool param_internal=true
+            int max_param_depth = 100
         );
 
         /**
diff --git a/src/feature_creation/node/FeatureNode.hpp b/src/feature_creation/node/FeatureNode.hpp
index c082cdd510ee29bfcabfea507d796739fb35a7e1..940699216eeae1fd0b913b7ff7ffd5956cef49ec 100644
--- a/src/feature_creation/node/FeatureNode.hpp
+++ b/src/feature_creation/node/FeatureNode.hpp
@@ -306,7 +306,7 @@ public:
          * @brief returns the number of parameters for this feature
          * @return the number of parameters (_params.size())
          */
-        inline int n_params(int n_cur=0){return n_cur;};
+        inline int n_params(int n_cur=0, int depth = 1){return n_cur;};
 
         /**
          * @brief Set the values of the training data for the feature inside of the value storage arrays
@@ -314,7 +314,7 @@ public:
          * @param offset(int) Key to determine which part of the temporary storage array to look into
          * @param params pointer to the parameter values
          */
-        inline void set_value(const double* params, int offset = -1){set_value(offset);};
+        inline void set_value(const double* params, int offset = -1, int depth = 1){set_value(offset);};
 
         /**
          * @brief The pointer to where the feature's training data is stored
@@ -323,7 +323,7 @@ public:
          * @param params pointer to the parameter values
          * @returns the pointer to the feature's data
          */
-        inline double* value_ptr(const double* params, int offset = -1){return value_ptr(offset);};
+        inline double* value_ptr(const double* params, int offset = -1, int depth = 1){return value_ptr(offset);};
 
         /**
          * @brief Set the values of the test data for the feature inside of the value storage arrays
@@ -331,7 +331,7 @@ public:
          * @param offset(int) Key to determine which part of the temporary storage array to look into
          * @param params pointer to the parameter values
          */
-        inline void set_test_value(const double* params, int offset = -1){set_test_value(offset);};
+        inline void set_test_value(const double* params, int offset = -1, int depth = 1){set_test_value(offset);};
 
         /**
          * @brief The pointer to where the feature's test data is stored
@@ -340,7 +340,7 @@ public:
          * @param params pointer to the parameter values
          * @returns the pointer to the feature's data
          */
-        inline double* test_value_ptr(const double* params, int offset = -1){return test_value_ptr(offset);};
+        inline double* test_value_ptr(const double* params, int offset = -1, int depth = 1){return test_value_ptr(offset);};
 
         /**
          * @brief The domain for the feature (min/max values)
@@ -348,7 +348,7 @@ public:
          * @param params parameter values for non-linear operations
          * @return the domain
          */
-        Domain domain(double* params){return _domain;};
+        Domain domain(double* params, int depth = 1){return _domain;};
 
         /**
          * @brief The expression of the feature
@@ -356,7 +356,7 @@ public:
          * @param params parameter values for non-linear operations
          * @return feature expression
          */
-        std::string expr(double* params){return _expr;};
+        std::string expr(double* params, int depth = 1){return _expr;};
 
         /**
          * @brief Set the bounds for the nl parameterization
@@ -364,7 +364,7 @@ public:
          * @param lb pointer to the lower bounds data
          * @param ub pointer to the upper bounds data
          */
-        inline void set_bounds(double* lb, double* ub, int from_parent=2){};
+        inline void set_bounds(double* lb, double* ub, int from_parent=2, int depth = 1){};
 
         /**
          * @brief Calculates the derivative of an operation with respect to the parameters for a given sample
diff --git a/src/feature_creation/node/Node.hpp b/src/feature_creation/node/Node.hpp
index d46a0c9eaf0f7c3549fa2f21f4ee44779c0a0d7d..d15a54b5d4684a293e1eb4425d7d773c489493d8 100644
--- a/src/feature_creation/node/Node.hpp
+++ b/src/feature_creation/node/Node.hpp
@@ -346,7 +346,7 @@ public:
          * @brief returns the number of parameters for this feature
          * @return the number of parameters (_params.size())
          */
-        virtual int n_params(int n_cur = 0) = 0;
+        virtual int n_params(int n_cur = 0, int depth = 1) = 0;
 
         /**
          * @brief Set the values of the training data for the feature inside of the value storage arrays
@@ -354,7 +354,7 @@ public:
          * @param offset(int) Key to determine which part of the temporary storage array to look into
          * @param params pointer to the parameter values
          */
-        virtual void set_value(const double* params, int offset = -1) = 0;
+        virtual void set_value(const double* params, int offset = -1, int depth = 1) = 0;
 
         /**
          * @brief The pointer to where the feature's training data is stored
@@ -363,7 +363,7 @@ public:
          * @param params pointer to the parameter values
          * @returns the pointer to the feature's data
          */
-        virtual double* value_ptr(const double* params, int offset = -1) = 0;
+        virtual double* value_ptr(const double* params, int offset = -1, int depth = 1) = 0;
 
         /**
          * @brief Set the values of the test data for the feature inside of the value storage arrays
@@ -371,7 +371,7 @@ public:
          * @param offset(int) Key to determine which part of the temporary storage array to look into
          * @param params pointer to the parameter values
          */
-        virtual void set_test_value(const double* params, int offset = -1) = 0;
+        virtual void set_test_value(const double* params, int offset = -1, int depth = 1) = 0;
 
         /**
          * @brief The pointer to where the feature's test data is stored
@@ -380,7 +380,7 @@ public:
          * @param params pointer to the parameter values
          * @returns the pointer to the feature's data
          */
-        virtual double* test_value_ptr(const double* params, int offset = -1) = 0;
+        virtual double* test_value_ptr(const double* params, int offset = -1, int depth = 1) = 0;
 
         /**
          * @brief The domain for the feature (min/max values)
@@ -388,7 +388,7 @@ public:
          * @param params parameter values for non-linear operations
          * @return the domain
          */
-        virtual Domain domain(double* params) = 0;
+        virtual Domain domain(double* params, int depth = 1) = 0;
 
         /**
          * @brief The expression of the feature
@@ -396,7 +396,7 @@ public:
          * @param params parameter values for non-linear operations
          * @return feature expression
          */
-        virtual std::string expr(double* params) = 0;
+        virtual std::string expr(double* params, int depth = 1) = 0;
 
         /**
          * @brief Set the bounds for the nl parameterization
@@ -404,7 +404,7 @@ public:
          * @param lb pointer to the lower bounds data
          * @param ub pointer to the upper bounds data
          */
-        virtual void set_bounds(double* lb, double* ub, int from_parent=2) = 0;
+        virtual void set_bounds(double* lb, double* ub, int from_parent=2, int depth = 1) = 0;
 
         /**
          * @brief Calculates the derivative of an operation with respect to the parameters for a given sample
diff --git a/src/feature_creation/node/operator_nodes/OperatorNode.hpp b/src/feature_creation/node/operator_nodes/OperatorNode.hpp
index 6faee746389e52c4ae36119ba3a9c605d7d19a1c..68089c809e866e4ee6da01ebd54490bfd0da6ec2 100644
--- a/src/feature_creation/node/operator_nodes/OperatorNode.hpp
+++ b/src/feature_creation/node/operator_nodes/OperatorNode.hpp
@@ -22,7 +22,9 @@
 
 #include<iomanip>
 
-// #ifdef PARAMETERIZE
+#ifdef PARAMETERIZE
+#include<nl_opt/NLOptWrapper.hpp>
+
 //     #include "ceres/ceres.h"
 //     #include "glog/logging.h"
 
@@ -32,7 +34,7 @@
 //     using ceres::Problem;
 //     using ceres::Solver;
 //     using ceres::Solve;
-// #endif
+#endif
 
 #ifdef PY_BINDINGS
     #include <python/conversion_utils.hpp>
@@ -346,7 +348,10 @@ public:
          * @brief returns the number of theoretical parameters for this feature
          * @return the number of theoretical parameters
          */
-        virtual inline int n_params(int n_cur = 0){return std::accumulate(_feats.begin(), _feats.end(), 2, [](double tot, node_ptr feat){return tot + feat->n_params();});}
+        virtual inline int n_params(int n_cur = 0, int depth = 1)
+        {
+            return (depth > nlopt_wrapper::_max_param_depth) ? 0 : std::accumulate(_feats.begin(), _feats.end(), 2, [&](double tot, node_ptr feat){return tot + feat->n_params(0, depth + 1);});
+        }
 
         /**
          * @brief Set the values of the training data for the feature inside of the value storage arrays
@@ -354,7 +359,7 @@ public:
          * @param offset(int) Key to determine which part of the temporary storage array to look into
          * @param params pointer to the parameter values
          */
-        virtual void set_value(const double* params, int offset = -1) = 0;
+        virtual void set_value(const double* params, int offset = -1, int depth = 1) = 0;
 
         /**
          * @brief The pointer to where the feature's training data is stored
@@ -363,13 +368,13 @@ public:
          * @param params pointer to the parameter values
          * @returns the pointer to the feature's data
          */
-        double* value_ptr(const double* params, int offset = -1)
+        double* value_ptr(const double* params, int offset = -1, int depth = 1)
         {
             if(_selected)
                 return node_value_arrs::get_d_matrix_ptr(_d_mat_ind);
 
             offset = (offset == -1) ? rung() : offset;
-            set_value(params, offset);
+            set_value(params, offset, depth);
             return node_value_arrs::get_value_ptr(_arr_ind, _feat_ind, offset, false);
         }
 
@@ -379,7 +384,7 @@ public:
          * @param offset(int) Key to determine which part of the temporary storage array to look into
          * @param params pointer to the parameter values
          */
-        virtual void set_test_value(const double* params, int offset = -1) = 0;
+        virtual void set_test_value(const double* params, int offset = -1, int depth = 1) = 0;
 
         /**
          * @brief The pointer to where the feature's test data is stored
@@ -388,10 +393,10 @@ public:
          * @param params pointer to the parameter values
          * @returns the pointer to the feature's data
          */
-        double* test_value_ptr(const double* params, int offset = -1)
+        double* test_value_ptr(const double* params, int offset = -1, int depth = 1)
         {
             offset = (offset == -1) ? rung() : offset;
-            set_test_value(params, offset);
+            set_test_value(params, offset, depth);
 
             return node_value_arrs::get_test_value_ptr(_arr_ind, _feat_ind, offset, false);
         }
@@ -425,7 +430,7 @@ public:
          * @param lb pointer to the lower bounds data
          * @param ub pointer to the upper bounds data
          */
-        virtual void set_bounds(double* lb, double* ub, int from_parent=2) = 0;
+        virtual void set_bounds(double* lb, double* ub, int from_parent=2, int depth = 1) = 0;
 
         /**
          * @brief Calculates the derivative of an operation with respect to the parameters for a given sample
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/abs/absolute_value.hpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/abs/absolute_value.hpp
index 9b29aa415e09e6d32fdd40cc8fecc7be4a3a4bfd..6e5b4161cc2ab4645467c6064807966e398e879c 100644
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/abs/absolute_value.hpp
+++ b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/abs/absolute_value.hpp
@@ -10,6 +10,7 @@
 #define ABS_VAL_NODE
 
 #include <feature_creation/node/operator_nodes/OperatorNode.hpp>
+#include <fmt/core.h>
 
 // DocString: cls_abs_node
 /**
@@ -69,7 +70,14 @@ public:
     /**
      * @brief Get the expression for the overall feature (From root node down)
      */
-    virtual inline std::string expr(){return "|" + _feats[0]->expr() + "|";}
+    inline std::string expr()
+    {
+        return fmt::format(
+            "|{}|",
+            _feats[0]->expr()
+        );
+    }
+    // virtual inline std::string expr(){return "|" + _feats[0]->expr() + "|";}
 
     // DocString: abs_node_set_value
     /**
@@ -156,7 +164,7 @@ public:
          * @param offset(int) Key to determine which part of the temporary storage array to look into
          * @param params pointer to the parameter values
          */
-        void set_value(const double* params, int offset = -1);
+        void set_value(const double* params, int offset=-1, int depth=1);
 
         /**
          * @brief Set the values of the test data for the feature inside of the value storage arrays
@@ -164,7 +172,7 @@ public:
          * @param offset(int) Key to determine which part of the temporary storage array to look into
          * @param params pointer to the parameter values
          */
-        void set_test_value(const double* params, int offset = -1);
+        void set_test_value(const double* params, int offset=-1, int depth=1);
 
         /**
          * @brief The domain for the feature (min/max values)
@@ -172,7 +180,13 @@ public:
          * @param params parameter values for non-linear operations
          * @return the domain
          */
-        inline Domain domain(double* params){return _feats[0]->domain(params + 2).abs(params[0], params[1]);}
+        inline Domain domain(double* params, int depth=1)
+        {
+            if(depth < nlopt_wrapper::_max_param_depth)
+                return _feats[0]->domain(params + 2, depth + 1).abs(params[0], params[1]);
+            else
+                return _feats[0]->domain().abs(params[0], params[1]);
+        }
 
         /**
          * @brief The expression of the feature
@@ -180,7 +194,25 @@ public:
          * @param params parameter values for non-linear operations
          * @return feature expression
          */
-        inline std::string expr(double* params){return "|" + std::to_string(params[0]) + "*" + _feats[0]->expr(params + 2) + " + " + std::to_string(params[1]) + "|";}
+        inline std::string expr(double* params, int depth=1)
+        {
+            // if(depth < nlopt_wrapper::_max_param_depth)
+            //     return "|" + std::to_string(params[0]) + "*" + _feats[0]->expr(params + 2) + " + " + fmt::format(params[1]) + "|";
+            // else
+            //     return "|" + std::to_string(params[0]) + "*" + _feats[0]->expr() + " + " + fmt::format(params[1]) + "|";
+
+            // if(depth < nlopt_wrapper::_max_param_depth)
+            //     return fmt::format("|{:.10e} * {} {:+15.10e}|", params[0], _feats[0]->expr(params + 2), params[1]);
+            // else
+            //     return fmt::format("|{:.10e} * {} {:+15.10e}|", params[0], _feats[0]->expr(), params[1]);
+
+            return fmt::format(
+                "|{:.10e} * {} {:+15.10e}|",
+                params[0],
+                (depth < nlopt_wrapper::_max_param_depth ? _feats[0]->expr(params + 2, depth + 1) : _feats[0]->expr()),
+                params[1]
+            );
+        }
 
         /**
          * @brief Set the bounds for the nl parameterization
@@ -188,7 +220,7 @@ public:
          * @param lb pointer to the lower bounds data
          * @param ub pointer to the upper bounds data
          */
-        virtual void set_bounds(double* lb, double* ub, int from_parent=2);
+        virtual void set_bounds(double* lb, double* ub, int from_parent=2, int depth = 1);
 
         /**
          * @brief Calculates the derivative of an operation with respect to the parameters for a given sample
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/abs/parameterize_top_lev_absolute_value.cpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/abs/parameterize_top_lev_absolute_value.cpp
deleted file mode 100644
index 252cf5a21d7f012f7c3cff03cdd2c5310260daff..0000000000000000000000000000000000000000
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/abs/parameterize_top_lev_absolute_value.cpp
+++ /dev/null
@@ -1,59 +0,0 @@
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/abs/parameterize_top_lev_absolute_value.hpp>
-
-void generateAbsParamTopLevNode(std::vector<node_ptr>& feat_list, node_ptr feat, int& feat_ind, double l_bound, double u_bound, std::vector<double>& prop)
-{
-    ++feat_ind;
-    node_ptr new_feat = std::make_shared<AbsParamTopLevNode>(feat, feat_ind, prop);
-
-    new_feat->set_value();
-    if(new_feat->is_nan() || new_feat->is_const() || (util_funcs::max_abs_val<double>(new_feat->value_ptr(), new_feat->n_samp()) > u_bound) || (util_funcs::max_abs_val<double>(new_feat->value_ptr(), new_feat->n_samp()) < l_bound))
-        return;
-
-    feat_list.push_back(new_feat);
-}
-
-AbsParamTopLevNode::AbsParamTopLevNode()
-{}
-
-AbsParamTopLevNode::AbsParamTopLevNode(node_ptr feat, int feat_ind, double l_bound, double u_bound, std::vector<double>& prop) :
-    AbsParamNode(feat, feat_ind, l_bound, u_bound)
-{
-    _params.resize(n_params(), 0.0);
-    get_parameters(prop);
-
-    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();
-}
-
-AbsParamTopLevNode::AbsParamTopLevNode(node_ptr feat, int feat_ind, std::vector<double>& prop) :
-    AbsParamNode(feat, feat_ind)
-{
-    _params.resize(n_params(), 0.0);
-    get_parameters(prop);
-}
-
-AbsParamTopLevNode::AbsParamTopLevNode(node_ptr feat, int feat_ind, double l_bound, double u_bound) :
-    AbsParamNode(feat, feat_ind, l_bound, u_bound)
-{
-    _params.resize(n_params(), 0.0);
-}
-
-void AbsParamTopLevNode::set_value(int offset)
-{
-    offset = (offset == -1) ? rung() : offset;
-    if(_selected)
-        allowed_op_funcs::abs(_n_samp, _feats[0]->value_ptr(offset + 2), _params[0], _params[1], node_value_arrs::get_d_matrix_ptr(_d_mat_ind));
-
-    allowed_op_funcs::abs(_n_samp, _feats[0]->value_ptr(offset + 2), _params[0], _params[1], node_value_arrs::get_value_ptr(_arr_ind, _feat_ind, offset, false));
-}
-void AbsParamTopLevNode::set_test_value(int offset)
-{
-    offset = (offset == -1) ? rung() : offset;
-    allowed_op_funcs::abs(_n_test_samp, _feats[0]->test_value_ptr(offset + 2), _params[0], _params[1], node_value_arrs::get_test_value_ptr(_arr_ind, _feat_ind, offset, false));
-}
-
-void AbsParamTopLevNode::set_bounds(double* lb, double* ub, int from_parent)
-{
-    lb[0] = _sign_alpha;
-    ub[0] = _sign_alpha;
-}
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/abs/parameterize_top_lev_absolute_value.hpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/abs/parameterize_top_lev_absolute_value.hpp
deleted file mode 100644
index 19fb2e0140da1debc256f4ef527d597d5cc4a53a..0000000000000000000000000000000000000000
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/abs/parameterize_top_lev_absolute_value.hpp
+++ /dev/null
@@ -1,131 +0,0 @@
-/** @file feature_creation/node/operator_nodes/allowed_operator_nodes/abs/parameterize_top_levabsolute_value.hpp
- *  @brief Class describing the parameterized absolute value operator
- *
- *  This class represents the parameterized unary operator -> |alpha * A + a|
- *
- *  @author Thomas A. R. Purcell (tpurcell)
- *  @bug No known bugs.
- */
-#ifndef PARAM_TOP_LEV_ABS_VAL_NODE
-#define PARAM_TOP_LEV_ABS_VAL_NODE
-
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/abs/parameterized_absolute_value.hpp>
-#include <nl_opt/NLOptWrapper.hpp>
-
-// DocString: cls_abs_node
-/**
- * @brief Node for the absolute value operator
- *
- */
-class AbsParamTopLevNode: public AbsParamNode
-{
-    using AbsParamNode::set_value;
-    using AbsParamNode::set_test_value;
-    using AbsParamNode::value_ptr;
-    using AbsParamNode::test_value_ptr;
-    using AbsParamNode::domain;
-    using AbsParamNode::expr;
-
-    friend class boost::serialization::access;
-
-    /**
-     * @brief Serialization function to send over MPI
-     *
-     * @param ar Archive representation of node
-     */
-    template <typename Archive>
-    void serialize(Archive& ar, const unsigned int version)
-    {
-        ar & boost::serialization::base_object<AbsParamNode>(*this);
-    }
-
-public:
-    /**
-     * @brief Base Constructor
-     * @details This is only used for serialization
-     */
-    AbsParamTopLevNode();
-
-    /**
-     * @brief Constructor
-     * @details Constructs the Node from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_ind Index of the new feature
-     * @param l_bound Minimum absolute value allowed for the feature.
-     * @param u_bound Maximum absolute value allowed for the feature.
-     * @param param_list The list of parameters to optimize using non-linear least squares
-     * @param prop The property to fit to
-     */
-    AbsParamTopLevNode(node_ptr feat, int feat_ind, double l_bound, double u_bound, std::vector<double>& prop);
-
-    /**
-     * @brief Constructor
-     * @details Constructs the Node from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_ind Index of the new feature
-     * @param prop The property to fit to
-     */
-    AbsParamTopLevNode(node_ptr feat, int feat_ind, std::vector<double>& prop);
-
-    /**
-     * @brief Constructor
-     * @details Constructs the Node from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_ind Index of the new feature
-     * @param l_bound Minimum absolute value allowed for the feature.
-     * @param u_bound Maximum absolute value allowed for the feature.
-     * @param param_list The list of parameters to optimize using non-linear least squares
-     */
-    AbsParamTopLevNode(node_ptr feat, int feat_ind, double l_bound=1e-50, double u_bound=1e50);
-
-    // DocString: abs_param_top_lev_node_n_params
-    /**
-     * @brief returns the number of theoretical parameters for this feature
-     * @return the number of theoretical parameters
-     */
-    inline int n_params(int n_cur = 0){return 2;}
-
-    // DocString: abs_param_top_lev_node_set_value
-    /**
-     * @brief Set the values of the training data for the feature inside of the value storage arrays
-     *
-     * @param offset(int) Key to determine which part of the temporary storage array to look into
-     */
-    void set_value(int offset = -1);
-
-    // DocString: abs_param_top_lev_node_set_test_value
-    /**
-     * @brief Set the values of the test data for the feature inside of the value storage arrays
-     *
-     * @param offset(int) Key to determine which part of the temporary storage array to look into
-     */
-    void set_test_value(int offset = -1);
-
-    // DocString: abs_param_top_lev_node_expr
-    /**
-     * @brief Get the expression for the overall feature (From root node down)
-     */
-    inline std::string expr(){return "|" + std::to_string(_params[0]) + "*" + _feats[0]->expr() + " + " + std::to_string(_params[1]) + "|";}
-
-    // DocString: abs_param_top_lev_node_domain
-    /**
-     * @brief The domain for the feature (min/max values)
-     */
-    inline Domain domain(){return _feats[0]->domain().abs(_params[0], _params[1]);}
-
-    // DocString: abs_param_top_lev_node_set_bounds
-    /**
-     * @brief Set the bounds for the nl parameterization
-     *
-     * @param lb pointer to the lower bounds data
-     * @param ub pointer to the upper bounds data
-     */
-    void set_bounds(double* lb, double* ub, int from_parent=2);
-};
-
-void generateAbsParamTopLevNode(std::vector<node_ptr>& feat_list, node_ptr feat, int& feat_ind, double l_bound, double u_bound, std::vector<double>& prop);
-
-#endif
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/abs/parameterized_absolute_value.cpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/abs/parameterized_absolute_value.cpp
index 08df4ae9c4a7dbbe999357f688b7fdde6f97c8a6..8d0f9c9c04bb0bdaebabd249b3e7c500734948bb 100644
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/abs/parameterized_absolute_value.cpp
+++ b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/abs/parameterized_absolute_value.cpp
@@ -57,30 +57,42 @@ void AbsParamNode::get_parameters(std::vector<double>& prop)
     }
 }
 
-void AbsNode::set_value(const double* params, int offset)
+void AbsNode::set_value(const double* params, int offset, int depth)
 {
     offset = (offset == -1) ? rung() : offset;
+    double* vp_0 = (depth < nlopt_wrapper::_max_param_depth) ? _feats[0]->value_ptr(params + 2, offset + 2, depth + 1) : _feats[0]->value_ptr(offset + 2);
+
     if(_selected)
-        allowed_op_funcs::abs(_n_samp, _feats[0]->value_ptr(params + 2, offset + 2), params[0], params[1], node_value_arrs::get_d_matrix_ptr(_d_mat_ind));
+        allowed_op_funcs::abs(_n_samp, vp_0, params[0], params[1], node_value_arrs::get_d_matrix_ptr(_d_mat_ind));
 
-    allowed_op_funcs::abs(_n_samp, _feats[0]->value_ptr(params + 2, offset + 2), params[0], params[1], node_value_arrs::get_value_ptr(_arr_ind, _feat_ind, offset, false));
+    allowed_op_funcs::abs(_n_samp, vp_0, params[0], params[1], node_value_arrs::get_value_ptr(_arr_ind, _feat_ind, offset, false));
 }
-void AbsNode::set_test_value(const double* params, int offset)
+void AbsNode::set_test_value(const double* params, int offset, int depth)
 {
     offset = (offset == -1) ? rung() : offset;
-    allowed_op_funcs::abs(_n_test_samp, _feats[0]->test_value_ptr(params + 2, offset + 2), params[0], params[1], node_value_arrs::get_test_value_ptr(_arr_ind, _feat_ind, offset, false));
+    double* vp_0 = (depth < nlopt_wrapper::_max_param_depth) ? _feats[0]->test_value_ptr(params + 2, offset + 2, depth + 1) : _feats[0]->test_value_ptr(offset + 2);
+
+    allowed_op_funcs::abs(_n_test_samp, vp_0, params[0], params[1], node_value_arrs::get_test_value_ptr(_arr_ind, _feat_ind, offset, false));
 }
 
-void AbsNode::set_bounds(double* lb, double* ub, int from_parent)
+void AbsNode::set_bounds(double* lb, double* ub, int from_parent, int depth)
 {
     lb[0] = 1.0;
     ub[0] = 1.0;
+
+    if(depth >= nlopt_wrapper::_max_param_depth)
+        return;
+
     _feats[0]->set_bounds(lb + 2, ub + 2);
 }
 
-void AbsParamNode::set_bounds(double* lb, double* ub, int from_parent)
+void AbsParamNode::set_bounds(double* lb, double* ub, int from_parent, int depth)
 {
     lb[0] = _sign_alpha;
     ub[0] = _sign_alpha;
-    _feats[0]->set_bounds(lb + 2, ub + 2);
+
+    if(depth >= nlopt_wrapper::_max_param_depth)
+        return;
+
+    _feats[0]->set_bounds(lb + 2, ub + 2, depth + 1);
 }
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/abs/parameterized_absolute_value.hpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/abs/parameterized_absolute_value.hpp
index 6f7674ae23598fa05208fabd570144b26e197bc3..1e3b33cf2ce3c841e291e61c4ca1d8ff5f1e2617 100644
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/abs/parameterized_absolute_value.hpp
+++ b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/abs/parameterized_absolute_value.hpp
@@ -163,7 +163,7 @@ public:
      * @param lb pointer to the lower bounds data
      * @param ub pointer to the upper bounds data
      */
-    void set_bounds(double* lb, double* ub, int from_parent=2);
+    void set_bounds(double* lb, double* ub, int from_parent=2, int depth=1);
 
     /**
      * @brief Converts a feature into a postfix expression (reverse polish notation)
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/abs_diff/absolute_difference.hpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/abs_diff/absolute_difference.hpp
index 1e8b3592f2f9f4ef153f0cf8d787a61d4eedc6db..48633e948423bf27fdfe46e7478ee5987d4fd4ea 100644
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/abs_diff/absolute_difference.hpp
+++ b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/abs_diff/absolute_difference.hpp
@@ -10,6 +10,7 @@
 #define ABS_DIFF_NODE
 
 #include <feature_creation/node/operator_nodes/OperatorNode.hpp>
+#include <fmt/core.h>
 
 // DocString: cls_abs_diff_node
 /**
@@ -72,7 +73,15 @@ public:
     /**
      * @brief Get the expression for the overall feature (From root node down)
      */
-    inline std::string expr(){return "|" + _feats[0]->expr() + " - (" + _feats[1]->expr() + ")|";}
+    inline std::string expr()
+    {
+        return fmt::format(
+            "|({}) - ({})|",
+            _feats[0]->expr(),
+            _feats[1]->expr()
+        );
+    }
+    // inline std::string expr(){return "|" + _feats[0]->expr() + " - (" + _feats[1]->expr() + ")|";}
 
     // DocString: abs_diff_node_set_value
     /**
@@ -165,7 +174,7 @@ public:
          * @param offset(int) Key to determine which part of the temporary storage array to look into
          * @param params pointer to the parameter values
          */
-        void set_value(const double* params, int offset = -1);
+        void set_value(const double* params, int offset=-1, int depth=1);
 
         /**
          * @brief Set the values of the test data for the feature inside of the value storage arrays
@@ -173,7 +182,7 @@ public:
          * @param offset(int) Key to determine which part of the temporary storage array to look into
          * @param params pointer to the parameter values
          */
-        void set_test_value(const double* params, int offset = -1);
+        void set_test_value(const double* params, int offset=-1, int depth=1);
 
         /**
          * @brief The domain for the feature (min/max values)
@@ -181,7 +190,14 @@ public:
          * @param params parameter values for non-linear operations
          * @return the domain
          */
-        inline Domain domain(double* params){return _feats[0]->domain(params + _feats[1]->n_params() + 2).sub(_feats[1]->domain(params + 2), params[0], params[1]).abs();}
+        inline Domain domain(double* params, int depth=1)
+        {
+            if(depth < nlopt_wrapper::_max_param_depth)
+                return _feats[0]->domain(params + _feats[1]->n_params() + 2, depth + 1).sub(_feats[1]->domain(params + 2, depth + 1), params[0], params[1]).abs();
+            else
+                return _feats[0]->domain().sub(_feats[1]->domain(), params[0], params[1]).abs();
+        }
+        // inline Domain domain(double* params){return _feats[0]->domain(params + _feats[1]->n_params() + 2).sub(_feats[1]->domain(params + 2), params[0], params[1]).abs();}
 
         /**
          * @brief The expression of the feature
@@ -189,7 +205,17 @@ public:
          * @param params parameter values for non-linear operations
          * @return feature expression
          */
-        inline std::string expr(double* params){return "|(" + _feats[0]->expr(params + _feats[1]->n_params() + 2) + ") - (" + std::to_string(params[0]) + "*" + _feats[1]->expr(params + 2) + " + " + std::to_string(params[1]) + ")|";}
+        inline std::string expr(double* params, int depth=1)
+        {
+            return fmt::format(
+                "|({}) - ({:.10e} * {} {:+15.10e})|",
+                (depth < nlopt_wrapper::_max_param_depth ? _feats[0]->expr(params + _feats[1]->n_params() + 2, depth + 1) : _feats[0]->expr()),
+                params[0],
+                (depth < nlopt_wrapper::_max_param_depth ? _feats[1]->expr(params + 2, depth + 1) : _feats[1]->expr()),
+                params[1]
+            );
+        }
+        // inline std::string expr(double* params){return "|(" + _feats[0]->expr(params + _feats[1]->n_params() + 2) + ") - (" + std::to_string(params[0]) + "*" + _feats[1]->expr(params + 2) + " + " + std::to_string(params[1]) + ")|";}
 
         /**
          * @brief Set the bounds for the nl parameterization
@@ -197,7 +223,7 @@ public:
          * @param lb pointer to the lower bounds data
          * @param ub pointer to the upper bounds data
          */
-        void set_bounds(double* lb, double* ub, int from_parent=2);
+        void set_bounds(double* lb, double* ub, int from_parent=2, int depth = 1);
 
         /**
          * @brief Calculates the derivative of an operation with respect to the parameters for a given sample
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/abs_diff/parameterize_top_lev_absolute_difference.cpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/abs_diff/parameterize_top_lev_absolute_difference.cpp
deleted file mode 100644
index 9b76933d0f5157fc1732f012e349fa603457eb2f..0000000000000000000000000000000000000000
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/abs_diff/parameterize_top_lev_absolute_difference.cpp
+++ /dev/null
@@ -1,88 +0,0 @@
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/abs_diff/parameterize_top_lev_absolute_difference.hpp>
-
-void generateAbsDiffParamTopLevNode(std::vector<node_ptr>& feat_list, node_ptr feat_1, node_ptr feat_2, int& feat_ind, double l_bound, double u_bound, std::vector<double>& prop)
-{
-    ++feat_ind;
-    node_ptr new_feat = std::make_shared<AbsDiffParamTopLevNode>(feat_1, feat_2, feat_ind, prop);
-
-    std::map<std::string, int> add_sub_leaves;
-    int expected_abs_tot = 0;
-    feat_1->update_add_sub_leaves(add_sub_leaves, 1, expected_abs_tot);
-    feat_2->update_add_sub_leaves(add_sub_leaves,-1, expected_abs_tot);
-
-    if((add_sub_leaves.size() < 2))
-        return;
-
-    double* params = new_feat->parameters().data();
-    double* val_ptr = new_feat->value_ptr();
-    const int offset = new_feat->rung();
-    allowed_op_funcs::sub(feat_1->n_samp(), feat_1->value_ptr(offset + 2), feat_2->value_ptr(offset + 1), params[0], params[1], val_ptr);
-    if(*std::min_element(val_ptr, val_ptr + new_feat->n_samp()) > l_bound)
-        return;
-
-    new_feat->set_value();
-    if(new_feat->is_nan() || new_feat->is_const() || (util_funcs::max_abs_val<double>(new_feat->value_ptr(), new_feat->n_samp()) > u_bound) || (util_funcs::max_abs_val<double>(new_feat->value_ptr(), new_feat->n_samp()) < l_bound) || (util_funcs::max_abs_val<double>(val_ptr, feat_1->n_samp()) < l_bound))
-        return;
-
-    feat_list.push_back(new_feat);
-}
-
-AbsDiffParamTopLevNode::AbsDiffParamTopLevNode()
-{}
-
-AbsDiffParamTopLevNode::AbsDiffParamTopLevNode(node_ptr feat_1, node_ptr feat_2, int feat_ind, double l_bound, double u_bound, std::vector<double>& prop):
-    AbsDiffParamNode(feat_1, feat_2, feat_ind, l_bound, u_bound)
-{
-    std::map<std::string, int> add_sub_leaves;
-    int expected_abs_tot = 0;
-    feat_1->update_add_sub_leaves(add_sub_leaves, 1, expected_abs_tot);
-    feat_2->update_add_sub_leaves(add_sub_leaves,-1, expected_abs_tot);
-
-    if((add_sub_leaves.size() < 2))
-        throw InvalidFeatureException();
-
-    _params.resize(n_params(), 0.0);
-    get_parameters(prop);
-
-    double* params = _params.data();
-    double* val_ptr = value_ptr();
-    const int offset = rung();
-
-    allowed_op_funcs::sub(feat_1->n_samp(), feat_1->value_ptr(offset + 2), feat_2->value_ptr(offset + 1), params[0], params[1], val_ptr);
-    if(*std::min_element(val_ptr, val_ptr + _n_samp) > l_bound)
-        throw InvalidFeatureException();
-
-    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();
-}
-
-AbsDiffParamTopLevNode::AbsDiffParamTopLevNode(node_ptr feat_1, node_ptr feat_2, int feat_ind, std::vector<double>& prop):
-    AbsDiffParamNode(feat_1, feat_2, feat_ind)
-{
-    _params.resize(n_params(), 0.0);
-    get_parameters(prop);
-}
-
-AbsDiffParamTopLevNode::AbsDiffParamTopLevNode(node_ptr feat_1, node_ptr feat_2, int feat_ind, double l_bound, double u_bound):
-    AbsDiffParamNode(feat_1, feat_2, feat_ind, l_bound, u_bound)
-{
-    _params.resize(n_params(), 0.0);
-}
-
-void AbsDiffParamTopLevNode::set_value(int offset)
-{
-    offset = (offset == -1) ? rung() : offset;
-
-    if(_selected)
-        allowed_op_funcs::abs_diff(_n_samp, _feats[0]->value_ptr(offset + 2), _feats[1]->value_ptr(offset + 1), _params[0], _params[1], node_value_arrs::get_d_matrix_ptr(_d_mat_ind));
-    allowed_op_funcs::abs_diff(_n_samp, _feats[0]->value_ptr(offset + 2), _feats[1]->value_ptr(offset + 1), _params[0], _params[1], node_value_arrs::get_value_ptr(_arr_ind, _feat_ind, offset, false));
-}
-
-void AbsDiffParamTopLevNode::set_test_value(int offset)
-{
-    offset = (offset == -1) ? rung() : offset;
-    allowed_op_funcs::abs_diff(_n_test_samp, _feats[0]->test_value_ptr(offset + 2), _feats[1]->test_value_ptr(offset + 1), _params[0], _params[1], node_value_arrs::get_test_value_ptr(_arr_ind, _feat_ind, offset, false));
-}
-
-void AbsDiffParamTopLevNode::set_bounds(double* lb, double* ub, int from_parent)
-{}
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/abs_diff/parameterize_top_lev_absolute_difference.hpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/abs_diff/parameterize_top_lev_absolute_difference.hpp
deleted file mode 100644
index 6f4a1d11c3dc32deb7ab8b1bb4da60413683d8ce..0000000000000000000000000000000000000000
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/abs_diff/parameterize_top_lev_absolute_difference.hpp
+++ /dev/null
@@ -1,133 +0,0 @@
-/** @file feature_creation/node/operator_nodes/allowed_operator_nodes/abs_diff/parameterize_top_levabsolute_difference.hpp
- *  @brief Class describing the parameterized absolute difference operator
- *
- *  This class represents the parameterized unary operator -> |A - alpha * B + a|
- *
- *  @author Thomas A. R. Purcell (tpurcell)
- *  @bug No known bugs.
- */
-#ifndef PARAM_TOP_LEV_ABS_DIFF_NODE
-#define PARAM_TOP_LEV_ABS_DIFF_NODE
-
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/abs_diff/parameterized_absolute_difference.hpp>
-#include <nl_opt/NLOptWrapper.hpp>
-
-// DocString: cls_abs_node
-/**
- * @brief ParamNode for the absolute value operator
- *
- */
-class AbsDiffParamTopLevNode: public AbsDiffParamNode
-{
-    using AbsDiffParamNode::set_value;
-    using AbsDiffParamNode::set_test_value;
-    using AbsDiffParamNode::value_ptr;
-    using AbsDiffParamNode::test_value_ptr;
-    using AbsDiffParamNode::domain;
-    using AbsDiffParamNode::expr;
-
-    friend class boost::serialization::access;
-
-    /**
-     * @brief Serialization function to send over MPI
-     *
-     * @param ar Archive representation of node
-     */
-    template <typename Archive>
-    void serialize(Archive& ar, const unsigned int version)
-    {
-        ar & boost::serialization::base_object<AbsDiffParamNode>(*this);
-    }
-
-public:
-    /**
-     * @brief Base Constructor
-     * @details This is only used for serialization
-     */
-    AbsDiffParamTopLevNode();
-
-    /**
-     * @brief Constructor
-     * @details Constructs the ParamNode from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_2 shared_ptr of the feature to operate on (B)
-     * @param feat_ind Index of the new feature
-     * @param l_bound Minimum absolute value allowed for the feature.
-     * @param u_bound Maximum absolute value allowed for the feature.
-     * @param prop The property to fit to
-     */
-    AbsDiffParamTopLevNode(node_ptr feat_1, node_ptr feat_2, int feat_ind, double l_bound, double u_bound, std::vector<double>& prop);
-
-    /**
-     * @brief Constructor
-     * @details Constructs the ParamNode from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_2 shared_ptr of the feature to operate on (B)
-     * @param feat_ind Index of the new feature
-     * @param prop The property to fit to
-     */
-    AbsDiffParamTopLevNode(node_ptr feat_1, node_ptr feat_2, int feat_ind, std::vector<double>& prop);
-
-    /**
-     * @brief Constructor
-     * @details Constructs the ParamNode from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_ind Index of the new feature
-     * @param l_bound Minimum absolute value allowed for the feature.
-     * @param u_bound Maximum absolute value allowed for the feature.
-     * @param param_list The list of parameters to optimize using non-linear least squares
-     * @param prop The property to fit to
-     */
-    AbsDiffParamTopLevNode(node_ptr feat_1, node_ptr feat_2, int feat_ind, double l_bound=1e-50, double u_bound=1e50);
-
-    // DocString: abs_diff_param_top_lev_node_n_params
-    /**
-     * @brief returns the number of theoretical parameters for this feature
-     * @return the number of theoretical parameters
-     */
-    inline int n_params(int n_cur = 0){return 2;}
-
-    // DocString: abs_diff_param_top_lev_node_set_value
-    /**
-     * @brief Set the values of the training data for the feature inside of the value storage arrays
-     *
-     * @param offset(int) Key to determine which part of the temporary storage array to look into
-     */
-    void set_value(int offset = -1);
-
-    // DocString: abs_diff_param_top_lev_node_set_test_value
-    /**
-     * @brief Set the values of the test data for the feature inside of the value storage arrays
-     *
-     * @param offset(int) Key to determine which part of the temporary storage array to look into
-     */
-    void set_test_value(int offset = -1);
-
-    // DocString: abs_diff_param_top_lev_node_expr
-    /**
-     * @brief Get the expression for the overall feature (From root node down)
-     */
-    inline std::string expr(){return "|(" + _feats[0]->expr() + ") - (" + std::to_string(_params[0]) + "*" + _feats[1]->expr() + " + " + std::to_string(_params[1]) + ")|";}
-
-    // DocString: abs_diff_param_top_lev_node_domain
-    /**
-     * @brief The domain for the feature (min/max values)
-     */
-    inline Domain domain(){return _feats[0]->domain().sub(_feats[1]->domain(), _params[0], _params[1]).abs();}
-
-    // DocString: abs_diff_param_top_lev_node_set_bounds
-    /**
-     * @brief Set the bounds for the nl parameterization
-     *
-     * @param lb pointer to the lower bounds data
-     * @param ub pointer to the upper bounds data
-     */
-    void set_bounds(double* lb, double* ub, int from_parent=2);
-};
-
-void generateAbsDiffParamTopLevNode(std::vector<node_ptr>& feat_list, node_ptr feat_1, node_ptr feat_2, int& feat_ind, double l_bound, double u_bound, std::vector<double>& prop);
-
-#endif
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/abs_diff/parameterized_absolute_difference.cpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/abs_diff/parameterized_absolute_difference.cpp
index eab182e7f4329e3e359cf369aa9b9fa2a13ae644..70c0141b6df5ac40ee76aaff2a3bca88c4eb2d4e 100644
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/abs_diff/parameterized_absolute_difference.cpp
+++ b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/abs_diff/parameterized_absolute_difference.cpp
@@ -80,23 +80,34 @@ void AbsDiffParamNode::get_parameters(std::vector<double>& prop)
     double min_res = nlopt_wrapper::optimize_feature_params(d);
 }
 
-void AbsDiffNode::set_value(const double* params, int offset)
+void AbsDiffNode::set_value(const double* params, int offset, int depth)
 {
     offset = (offset == -1) ? rung() : offset;
 
+    double* vp_0 = (depth < nlopt_wrapper::_max_param_depth) ? _feats[0]->value_ptr(params + _feats[1]->n_params() + 2, offset + 2, depth + 1) : _feats[0]->value_ptr(offset + 2);
+    double* vp_1 = (depth < nlopt_wrapper::_max_param_depth) ? _feats[1]->value_ptr(params + 2, offset + 1, depth + 1) : _feats[1]->value_ptr(offset + 1);
+
     if(_selected)
-        allowed_op_funcs::abs_diff(_n_samp, _feats[0]->value_ptr(params + 2 + _feats[1]->n_params(), offset + 2), _feats[1]->value_ptr(params + 2, offset + 1), params[0], params[1], node_value_arrs::get_d_matrix_ptr(_d_mat_ind));
-    allowed_op_funcs::abs_diff(_n_samp, _feats[0]->value_ptr(params + 2 + _feats[1]->n_params(), offset + 2), _feats[1]->value_ptr(params + 2, offset + 1), params[0], params[1], node_value_arrs::get_value_ptr(_arr_ind, _feat_ind, offset, false));
+        allowed_op_funcs::abs_diff(_n_samp, vp_0, vp_1, params[0], params[1], node_value_arrs::get_d_matrix_ptr(_d_mat_ind));
+
+    allowed_op_funcs::abs_diff(_n_samp, vp_0, vp_1, params[0], params[1], node_value_arrs::get_value_ptr(_arr_ind, _feat_ind, offset, false));
 }
 
-void AbsDiffNode::set_test_value(const double* params, int offset)
+void AbsDiffNode::set_test_value(const double* params, int offset, int depth)
 {
     offset = (offset == -1) ? rung() : offset;
-    allowed_op_funcs::abs_diff(_n_test_samp, _feats[0]->test_value_ptr(params + 2 + _feats[1]->n_params(), offset + 2), _feats[1]->test_value_ptr(params + 2, offset + 1), params[0], params[1], node_value_arrs::get_test_value_ptr(_arr_ind, _feat_ind, offset, false));
+
+    double* vp_0 = (depth < nlopt_wrapper::_max_param_depth) ? _feats[0]->test_value_ptr(params + _feats[1]->n_params() + 2, offset + 2, depth + 1) : _feats[0]->test_value_ptr(offset + 2);
+    double* vp_1 = (depth < nlopt_wrapper::_max_param_depth) ? _feats[1]->test_value_ptr(params + 2, offset + 1, depth + 1) : _feats[1]->test_value_ptr(offset + 1);
+
+    allowed_op_funcs::abs_diff(_n_test_samp, vp_0, vp_1, params[0], params[1], node_value_arrs::get_test_value_ptr(_arr_ind, _feat_ind, offset, false));
 }
 
-void AbsDiffNode::set_bounds(double* lb, double* ub, int from_parent)
+void AbsDiffNode::set_bounds(double* lb, double* ub, int from_parent, int depth)
 {
-    _feats[0]->set_bounds(lb + 2 + _feats[1]->n_params(), ub + 2 + _feats[1]->n_params(), 2 + _feats[1]->n_params());
-    _feats[1]->set_bounds(lb + 2, ub + 2);
+    if(depth >= nlopt_wrapper::_max_param_depth)
+        return;
+
+    _feats[0]->set_bounds(lb + 2 + _feats[1]->n_params(), ub + 2 + _feats[1]->n_params(), 2 + _feats[1]->n_params(), depth + 1);
+    _feats[1]->set_bounds(lb + 2, ub + 2, depth + 1);
 }
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/add/add.hpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/add/add.hpp
index 6ff806832b89038103d3d8a96e22a2e74e25f472..d0e587982e5555fcc2adf6f40d5ad9039dd91027 100644
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/add/add.hpp
+++ b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/add/add.hpp
@@ -10,6 +10,7 @@
 #define ADD_NODE
 
 #include <feature_creation/node/operator_nodes/OperatorNode.hpp>
+#include <fmt/core.h>
 
 // DocString: cls_add_node
 /**
@@ -69,7 +70,15 @@ public:
     /**
      * @brief Get the expression for the overall feature (From root node down)
      */
-    inline std::string expr(){return "[" + _feats[0]->expr() + " + " + _feats[1]->expr() + "]";}
+    inline std::string expr()
+    {
+        return fmt::format(
+            "[({}) + ({})]",
+            _feats[0]->expr(),
+            _feats[1]->expr()
+        );
+    }
+    // inline std::string expr(){return "[" + _feats[0]->expr() + " + " + _feats[1]->expr() + "]";}
 
     // DocString: add_node_set_value
     /**
@@ -162,7 +171,7 @@ public:
          * @param offset(int) Key to determine which part of the temporary storage array to look into
          * @param params pointer to the parameter values
          */
-        void set_value(const double* params, int offset = -1);
+        void set_value(const double* params, int offset=-1, int depth=1);
 
         /**
          * @brief Set the values of the test data for the feature inside of the value storage arrays
@@ -170,7 +179,7 @@ public:
          * @param offset(int) Key to determine which part of the temporary storage array to look into
          * @param params pointer to the parameter values
          */
-        void set_test_value(const double* params, int offset = -1);
+        void set_test_value(const double* params, int offset=-1, int depth=1);
 
         /**
          * @brief The domain for the feature (min/max values)
@@ -178,7 +187,14 @@ public:
          * @param params parameter values for non-linear operations
          * @return the domain
          */
-        inline Domain domain(double* params){return _feats[0]->domain(params + 2 + _feats[1]->n_params()).add(_feats[1]->domain(params + 2), params[0], params[1]);}
+        inline Domain domain(double* params, int depth=1)
+        {
+            if(depth < nlopt_wrapper::_max_param_depth)
+                return _feats[0]->domain(params + _feats[1]->n_params() + 2, depth + 1).add(_feats[1]->domain(params + 2, depth + 1), params[0], params[1]);
+            else
+                return _feats[0]->domain().add(_feats[1]->domain(), params[0], params[1]);
+        }
+        // inline Domain domain(double* params){return _feats[0]->domain(params + 2 + _feats[1]->n_params()).add(_feats[1]->domain(params + 2), params[0], params[1]);}
 
         /**
          * @brief The expression of the feature
@@ -186,7 +202,17 @@ public:
          * @param params parameter values for non-linear operations
          * @return feature expression
          */
-        inline std::string expr(double* params){return "[" + _feats[0]->expr(params + _feats[1]->n_params() + 2) + " + (" + std::to_string(params[0]) + "*" + _feats[1]->expr(params + 2) + " + " + std::to_string(params[1]) + ")]";}
+        inline std::string expr(double* params, int depth=1)
+        {
+            return fmt::format(
+                "[({}) + ({:.10e} * {} {:+15.10e})]",
+                (depth < nlopt_wrapper::_max_param_depth ? _feats[0]->expr(params + _feats[1]->n_params() + 2, depth + 1) : _feats[0]->expr()),
+                params[0],
+                (depth < nlopt_wrapper::_max_param_depth ? _feats[1]->expr(params + 2, depth + 1) : _feats[1]->expr()),
+                params[1]
+            );
+        }
+        // inline std::string expr(double* params){return "[" + _feats[0]->expr(params + _feats[1]->n_params() + 2) + " + (" + std::to_string(params[0]) + "*" + _feats[1]->expr(params + 2) + " + " + std::to_string(params[1]) + ")]";}
 
         /**
          * @brief Set the bounds for the nl parameterization
@@ -194,7 +220,7 @@ public:
          * @param lb pointer to the lower bounds data
          * @param ub pointer to the upper bounds data
          */
-        void set_bounds(double* lb, double* ub, int from_parent=2);
+        void set_bounds(double* lb, double* ub, int from_parent=2, int depth = 1);
 
         /**
          * @brief Calculates the derivative of an operation with respect to the parameters for a given sample
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/add/parameterize_top_lev_add.cpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/add/parameterize_top_lev_add.cpp
deleted file mode 100644
index 42c5e98302d5de30784a8ab5b774acb11e046e70..0000000000000000000000000000000000000000
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/add/parameterize_top_lev_add.cpp
+++ /dev/null
@@ -1,62 +0,0 @@
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/add/parameterize_top_lev_add.hpp>
-
-void generateAddParamTopLevNode(std::vector<node_ptr>& feat_list, node_ptr feat_1, node_ptr feat_2, int& feat_ind, double l_bound, double u_bound, std::vector<double>& prop)
-{
-    ++feat_ind;
-    node_ptr new_feat = std::make_shared<AddParamTopLevNode>(feat_1, feat_2, feat_ind, prop);
-
-    new_feat->set_value();
-
-    if(new_feat->is_nan() || new_feat->is_const() || (util_funcs::max_abs_val<double>(new_feat->value_ptr(), new_feat->n_samp()) > u_bound) || (util_funcs::max_abs_val<double>(new_feat->value_ptr(), new_feat->n_samp()) < l_bound))
-        return;
-
-    feat_list.push_back(new_feat);
-}
-
-AddParamTopLevNode::AddParamTopLevNode()
-{}
-
-AddParamTopLevNode::AddParamTopLevNode(node_ptr feat_1, node_ptr feat_2, int feat_ind, double l_bound, double u_bound, std::vector<double>& prop) :
-    AddParamNode(feat_1, feat_2, feat_ind, l_bound, u_bound)
-{
-    _params.resize(n_params(), 0.0);
-    get_parameters(prop);
-
-    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();
-}
-
-AddParamTopLevNode::AddParamTopLevNode(node_ptr feat_1, node_ptr feat_2, int feat_ind, std::vector<double>& prop) :
-    AddParamNode(feat_1, feat_2, feat_ind)
-{
-    _params.resize(n_params(), 0.0);
-    get_parameters(prop);
-}
-
-AddParamTopLevNode::AddParamTopLevNode(node_ptr feat_1, node_ptr feat_2, int feat_ind, double l_bound, double u_bound) :
-    AddParamNode(feat_1, feat_2, feat_ind, l_bound, u_bound)
-{
-    _params.resize(n_params(), 0.0);
-}
-
-void AddParamTopLevNode::set_value(int offset)
-{
-    offset = (offset == -1) ? rung() : offset;
-
-    if(_selected)
-        allowed_op_funcs::add(_n_samp, _feats[0]->value_ptr(offset + 2), _feats[1]->value_ptr(offset + 1), _params[0], _params[1], node_value_arrs::get_d_matrix_ptr(_d_mat_ind));
-
-    allowed_op_funcs::add(_n_samp, _feats[0]->value_ptr(offset + 2), _feats[1]->value_ptr(offset + 1), _params[0], _params[1], node_value_arrs::get_value_ptr(_arr_ind, _feat_ind, offset, false));
-}
-
-void AddParamTopLevNode::set_test_value(int offset)
-{
-    offset = (offset == -1) ? rung() : offset;
-    allowed_op_funcs::add(_n_test_samp, _feats[0]->test_value_ptr(offset + 2), _feats[1]->test_value_ptr(offset + 1), _params[0], _params[1], node_value_arrs::get_test_value_ptr(_arr_ind, _feat_ind, offset, false));
-}
-
-void AddParamTopLevNode::set_bounds(double* lb, double* ub, int from_parent)
-{
-    lb[1] = 0.0;
-    ub[1] = 0.0;
-}
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/add/parameterize_top_lev_add.hpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/add/parameterize_top_lev_add.hpp
deleted file mode 100644
index 2516ba165e5996a8f3e68ed051625d5b308e24db..0000000000000000000000000000000000000000
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/add/parameterize_top_lev_add.hpp
+++ /dev/null
@@ -1,131 +0,0 @@
-/** @file feature_creation/node/operator_nodes/allowed_operator_nodes/add/parameterize_top_levadd.hpp
- *  @brief Class describing the parameterized addition operator
- *
- *  This class represents the parameterized unary operator -> A + alpha * B + a
- *
- *  @author Thomas A. R. Purcell (tpurcell)
- *  @bug No known bugs.
- */
-#ifndef PARAM_TOP_LEV_ADD_NODE
-#define PARAM_TOP_LEV_ADD_NODE
-
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/add/parameterized_add.hpp>
-#include <nl_opt/NLOptWrapper.hpp>
-
-// DocString: cls_param_add_node
-/**
- * @brief ParamNode for the absolute value operator
- *
- */
-class AddParamTopLevNode: public AddParamNode
-{
-    using AddParamNode::set_value;
-    using AddParamNode::set_test_value;
-    using AddParamNode::value_ptr;
-    using AddParamNode::test_value_ptr;
-    using AddParamNode::domain;
-    using AddParamNode::expr;
-
-    friend class boost::serialization::access;
-
-    /**
-     * @brief Serialization function to send over MPI
-     *
-     * @param ar Archive representation of node
-     */
-    template <typename Archive>
-    void serialize(Archive& ar, const unsigned int version)
-    {
-        ar & boost::serialization::base_object<AddParamNode>(*this);
-    }
-public:
-    /**
-     * @brief Base Constructor
-     * @details This is only used for serialization
-     */
-    AddParamTopLevNode();
-
-    /**
-     * @brief Constructor
-     * @details Constructs the ParamNode from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_2 shared_ptr of the feature to operate on (B)
-     * @param feat_ind Index of the new feature
-     * @param l_bound Minimum absolute value allowed for the feature.
-     * @param u_bound Maximum absolute value allowed for the feature.
-     * @param prop The property to fit to
-     */
-    AddParamTopLevNode(node_ptr feat_1, node_ptr feat_2, int feat_ind, double l_bound, double u_bound, std::vector<double>& prop);
-
-    /**
-     * @brief Constructor
-     * @details Constructs the ParamNode from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_2 shared_ptr of the feature to operate on (B)
-     * @param feat_ind Index of the new feature
-     * @param prop The property to fit to
-     */
-    AddParamTopLevNode(node_ptr feat_1, node_ptr feat_2, int feat_ind, std::vector<double>& prop);
-
-    /**
-     * @brief Constructor
-     * @details Constructs the ParamNode from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_ind Index of the new feature
-     * @param l_bound Minimum absolute value allowed for the feature.
-     * @param u_bound Maximum absolute value allowed for the feature.
-     * @param param_list The list of parameters to optimize using non-linear least squares
-     */
-    AddParamTopLevNode(node_ptr feat_1, node_ptr feat_2, int feat_ind, double l_bound=1e-50, double u_bound=1e50);
-
-    // DocString: add_param_top_lev_node_n_params
-    /**
-     * @brief returns the number of theoretical parameters for this feature
-     * @return the number of theoretical parameters
-     */
-    inline int n_params(int n_cur = 0){return 2;}
-
-    // DocString: add_param_top_lev_node_set_value
-    /**
-     * @brief Set the values of the training data for the feature inside of the value storage arrays
-     *
-     * @param offset(int) Key to determine which part of the temporary storage array to look into
-     */
-    void set_value(int offset = -1);
-
-    // DocString: add_param_top_lev_node_set_test_value
-    /**
-     * @brief Set the values of the test data for the feature inside of the value storage arrays
-     *
-     * @param offset(int) Key to determine which part of the temporary storage array to look into
-     */
-    void set_test_value(int offset = -1);
-
-    // DocString: add_param_top_lev_node_expr
-    /**
-     * @brief Get the expression for the overall feature (From root node down)
-     */
-    inline std::string expr(){return "[" + _feats[0]->expr() + " + (" + std::to_string(_params[0]) + "*" + _feats[1]->expr() + " + " + std::to_string(_params[1]) + ")]";}
-
-    // DocString: add_param_top_lev_node_domain
-    /**
-     * @brief The domain for the feature (min/max values)
-     */
-    inline Domain domain(){return _feats[0]->domain().add(_feats[1]->domain(), _params[0], _params[1]);}
-
-    // DocString: add_param_top_lev_node_set_bounds
-    /**
-     * @brief Set the bounds for the nl parameterization
-     *
-     * @param lb pointer to the lower bounds data
-     * @param ub pointer to the upper bounds data
-     */
-    void set_bounds(double* lb, double* ub, int from_parent=2);
-};
-
-void generateAddParamTopLevNode(std::vector<node_ptr>& feat_list, node_ptr feat_1, node_ptr feat_2, int& feat_ind, double l_bound, double u_bound, std::vector<double>& prop);
-
-#endif
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/add/parameterized_add.cpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/add/parameterized_add.cpp
index a7161dbf58cd12f900a1eb976fb2aa7be1d0360e..8c8ff4eac2e5fd7db551c20cd9eb10f1f8bdd82c 100644
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/add/parameterized_add.cpp
+++ b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/add/parameterized_add.cpp
@@ -50,27 +50,37 @@ void AddParamNode::get_parameters(std::vector<double>& prop)
     double min_res = nlopt_wrapper::optimize_feature_params(d);
 }
 
-void AddNode::set_value(const double* params, int offset)
+void AddNode::set_value(const double* params, int offset, int depth)
 {
     offset = (offset == -1) ? rung() : offset;
 
+    double* vp_0 = (depth < nlopt_wrapper::_max_param_depth) ? _feats[0]->value_ptr(params + _feats[1]->n_params() + 2, offset + 2, depth + 1) : _feats[0]->value_ptr(offset + 2);
+    double* vp_1 = (depth < nlopt_wrapper::_max_param_depth) ? _feats[1]->value_ptr(params + 2, offset + 1, depth + 1) : _feats[1]->value_ptr(offset + 1);
+
     if(_selected)
-        allowed_op_funcs::add(_n_samp, _feats[0]->value_ptr(params + 2 + _feats[1]->n_params(), offset + 2), _feats[1]->value_ptr(params + 2, offset + 1), params[0], params[1], node_value_arrs::get_d_matrix_ptr(_d_mat_ind));
+        allowed_op_funcs::add(_n_samp, vp_0, vp_1, params[0], params[1], node_value_arrs::get_d_matrix_ptr(_d_mat_ind));
 
-    allowed_op_funcs::add(_n_samp, _feats[0]->value_ptr(params + 2 + _feats[1]->n_params(), offset + 2), _feats[1]->value_ptr(params + 2, offset + 1), params[0], params[1], node_value_arrs::get_value_ptr(_arr_ind, _feat_ind, offset, false));
+    allowed_op_funcs::add(_n_samp, vp_0, vp_1, params[0], params[1], node_value_arrs::get_value_ptr(_arr_ind, _feat_ind, offset, false));
 }
 
-void AddNode::set_test_value(const double* params, int offset)
+void AddNode::set_test_value(const double* params, int offset, int depth)
 {
     offset = (offset == -1) ? rung() : offset;
-    allowed_op_funcs::add(_n_test_samp, _feats[0]->test_value_ptr(params + 2 + _feats[1]->n_params(), offset + 2), _feats[1]->test_value_ptr(params + 2, offset + 1), params[0], params[1], node_value_arrs::get_test_value_ptr(_arr_ind, _feat_ind, offset, false));
+
+    double* vp_0 = (depth < nlopt_wrapper::_max_param_depth) ? _feats[0]->test_value_ptr(params + _feats[1]->n_params() + 2, offset + 2, depth + 1) : _feats[0]->test_value_ptr(offset + 2);
+    double* vp_1 = (depth < nlopt_wrapper::_max_param_depth) ? _feats[1]->test_value_ptr(params + 2, offset + 1, depth + 1) : _feats[1]->test_value_ptr(offset + 1);
+
+    allowed_op_funcs::add(_n_test_samp, vp_0, vp_1, params[0], params[1], node_value_arrs::get_test_value_ptr(_arr_ind, _feat_ind, offset, false));
 }
 
-void AddNode::set_bounds(double* lb, double* ub, int from_parent)
+void AddNode::set_bounds(double* lb, double* ub, int from_parent, int depth)
 {
     lb[1] = 0.0;
     ub[1] = 0.0;
 
-    _feats[0]->set_bounds(lb + 2 + _feats[1]->n_params(), ub + 2 + _feats[1]->n_params());
-    _feats[1]->set_bounds(lb + 2, ub + 2);
+    if(depth >= nlopt_wrapper::_max_param_depth)
+        return;
+
+    _feats[0]->set_bounds(lb + 2 + _feats[1]->n_params(), ub + 2 + _feats[1]->n_params(), depth + 1);
+    _feats[1]->set_bounds(lb + 2, ub + 2, depth + 1);
 }
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/cb/cube.hpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/cb/cube.hpp
index 1f2c70d743f5df27bfa66980b625c44abc0b0592..c5a09d912d6913a405a774eb85383d94ec7eb45b 100644
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/cb/cube.hpp
+++ b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/cb/cube.hpp
@@ -10,6 +10,7 @@
 #define CB_NODE
 
 #include <feature_creation/node/operator_nodes/OperatorNode.hpp>
+#include <fmt/core.h>
 
 // DocString: cls_cb_node
 /**
@@ -67,7 +68,13 @@ public:
     /**
      * @brief Get the expression for the overall feature (From root node down)
      */
-    inline std::string expr(){return "(" + _feats[0]->expr() + ")^3";}
+    inline std::string expr()
+    {
+        return fmt::format(
+            "[{}]^3",
+            _feats[0]->expr()
+        );
+    }
 
     // DocString: cb_node_set_value
     /**
@@ -154,7 +161,7 @@ public:
          * @param offset(int) Key to determine which part of the temporary storage array to look into
          * @param params pointer to the parameter values
          */
-        void set_value(const double* params, int offset = -1);
+        void set_value(const double* params, int offset=-1, int depth=1);
 
         /**
          * @brief Set the values of the test data for the feature inside of the value storage arrays
@@ -162,7 +169,7 @@ public:
          * @param offset(int) Key to determine which part of the temporary storage array to look into
          * @param params pointer to the parameter values
          */
-        void set_test_value(const double* params, int offset = -1);
+        void set_test_value(const double* params, int offset=-1, int depth=1);
 
         /**
          * @brief The domain for the feature (min/max values)
@@ -170,7 +177,14 @@ public:
          * @param params parameter values for non-linear operations
          * @return the domain
          */
-        inline Domain domain(double* params){return _feats[0]->domain(params + 2).pow(3.0, params[0], params[1]);}
+        inline Domain domain(double* params, int depth=1)
+        {
+            if(depth < nlopt_wrapper::_max_param_depth)
+                return _feats[0]->domain(params + 2, depth + 1).pow(3.0, params[0], params[1]);
+            else
+                return _feats[0]->domain().pow(3.0, params[0], params[1]);
+        }
+        // inline Domain domain(double* params){return _feats[0]->domain(params + 2).pow(3.0, params[0], params[1]);}
 
         /**
          * @brief The expression of the feature
@@ -178,7 +192,16 @@ public:
          * @param params parameter values for non-linear operations
          * @return feature expression
          */
-        inline std::string expr(double* params){return "(" + std::to_string(params[0]) + "*" + _feats[0]->expr(params + 2) + " + " + std::to_string(params[1]) + ")^3";}
+        inline std::string expr(double* params, int depth=1)
+        {
+            return fmt::format(
+                "[{:.10e} * {} {:+15.10e}]^3",
+                params[0],
+                (depth < nlopt_wrapper::_max_param_depth ? _feats[0]->expr(params + 2, depth + 1) : _feats[0]->expr()),
+                params[1]
+            );
+        }
+        // inline std::string expr(double* params){return "(" + std::to_string(params[0]) + "*" + _feats[0]->expr(params + 2) + " + " + std::to_string(params[1]) + ")^3";}
 
         /**
          * @brief Set the bounds for the nl parameterization
@@ -186,7 +209,7 @@ public:
          * @param lb pointer to the lower bounds data
          * @param ub pointer to the upper bounds data
          */
-        void set_bounds(double* lb, double* ub, int from_parent=2);
+        void set_bounds(double* lb, double* ub, int from_parent=2, int depth = 1);
 
         /**
          * @brief Calculates the derivative of an operation with respect to the parameters for a given sample
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/cb/parameterize_top_lev_cube.cpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/cb/parameterize_top_lev_cube.cpp
deleted file mode 100644
index 95f865b81815d969c9f72cc0e202d540d7d81d6e..0000000000000000000000000000000000000000
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/cb/parameterize_top_lev_cube.cpp
+++ /dev/null
@@ -1,63 +0,0 @@
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/cb/parameterize_top_lev_cube.hpp>
-
-void generateCbParamTopLevNode(std::vector<node_ptr>& feat_list, node_ptr feat, int& feat_ind, double l_bound, double u_bound, std::vector<double>& prop)
-{
-    ++feat_ind;
-    node_ptr new_feat = std::make_shared<CbParamTopLevNode>(feat, feat_ind, prop);
-
-    new_feat->set_value();
-    if(new_feat->is_nan() || new_feat->is_const() || (util_funcs::max_abs_val<double>(new_feat->value_ptr(), new_feat->n_samp()) > u_bound) || (util_funcs::max_abs_val<double>(new_feat->value_ptr(), new_feat->n_samp()) < l_bound))
-        return;
-
-    feat_list.push_back(new_feat);
-}
-
-CbParamTopLevNode::CbParamTopLevNode()
-{}
-
-CbParamTopLevNode::CbParamTopLevNode(node_ptr feat, int feat_ind, double l_bound, double u_bound, std::vector<double>& prop) :
-    CbParamNode(feat, feat_ind, l_bound, u_bound)
-{
-    _params.resize(n_params(), 0.0);
-
-    get_parameters(prop);
-    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();
-}
-
-CbParamTopLevNode::CbParamTopLevNode(node_ptr feat, int feat_ind, std::vector<double>& prop) :
-    CbParamNode(feat, feat_ind)
-{
-    _params.resize(n_params(), 0.0);
-
-    get_parameters(prop);
-    set_value();
-}
-
-CbParamTopLevNode::CbParamTopLevNode(node_ptr feat, int feat_ind, double l_bound, double u_bound) :
-    CbParamNode(feat, feat_ind, l_bound, u_bound)
-{
-    _params.resize(n_params(), 0.0);
-}
-
-void CbParamTopLevNode::set_value(int offset)
-{
-    offset = (offset == -1) ? rung() : offset;
-    if(_selected)
-        allowed_op_funcs::cb(_n_samp, _feats[0]->value_ptr(offset + 2), _params[0], _params[1], node_value_arrs::get_d_matrix_ptr(_d_mat_ind));
-
-    allowed_op_funcs::cb(_n_samp, _feats[0]->value_ptr(offset + 2), _params[0], _params[1], node_value_arrs::get_value_ptr(_arr_ind, _feat_ind, offset, false));
-}
-
-void CbParamTopLevNode::set_test_value(int offset)
-{
-    offset = (offset == -1) ? rung() : offset;
-    allowed_op_funcs::cb(_n_test_samp, _feats[0]->test_value_ptr(offset + 2), _params[0], _params[1], node_value_arrs::get_test_value_ptr(_arr_ind, _feat_ind, offset, false));
-}
-
-void CbParamTopLevNode::set_bounds(double* lb, double* ub, int from_parent)
-{
-    lb[0] = 1.0;
-    ub[0] = 1.0;
-}
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/cb/parameterize_top_lev_cube.hpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/cb/parameterize_top_lev_cube.hpp
deleted file mode 100644
index 2eb13937b732ecce2e3e37a9fdb98b47f68f588f..0000000000000000000000000000000000000000
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/cb/parameterize_top_lev_cube.hpp
+++ /dev/null
@@ -1,131 +0,0 @@
-/** @file feature_creation/node/operator_nodes/allowed_operator_nodes/cb/parameterize_top_levcube.hpp
- *  @brief Class describing the parameterized cube operator
- *
- *  This class represents the parameterized unary operator -> (alpha * A + a)^3
- *
- *  @author Thomas A. R. Purcell (tpurcell)
- *  @bug No known bugs.
- */
-#ifndef PARAM_TOP_LEV_CUBE_NODE
-#define PARAM_TOP_LEV_CUBE_NODE
-
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/cb/parameterized_cube.hpp>
-#include <nl_opt/NLOptWrapper.hpp>
-
-// DocString: cls_abs_node
-/**
- * @brief ParamNode for the absolute value operator
- *
- */
-class CbParamTopLevNode: public CbParamNode
-{
-    using CbParamNode::set_value;
-    using CbParamNode::set_test_value;
-    using CbParamNode::value_ptr;
-    using CbParamNode::test_value_ptr;
-    using CbParamNode::domain;
-    using CbParamNode::expr;
-
-    friend class boost::serialization::access;
-
-    /**
-     * @brief Serialization function to send over MPI
-     *
-     * @param ar Archive representation of node
-     */
-    template <typename Archive>
-    void serialize(Archive& ar, const unsigned int version)
-    {
-        ar & boost::serialization::base_object<CbParamNode>(*this);
-    }
-
-
-public:
-    /**
-     * @brief Base Constructor
-     * @details This is only used for serialization
-     */
-    CbParamTopLevNode();
-
-    /**
-     * @brief Constructor
-     * @details Constructs the ParamNode from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_ind Index of the new feature
-     * @param l_bound Minimum absolute value allowed for the feature.
-     * @param u_bound Maximum absolute value allowed for the feature.
-     * @param prop The property to fit to
-     */
-    CbParamTopLevNode(node_ptr feat, int feat_ind, double l_bound, double u_bound, std::vector<double>& prop);
-
-    /**
-     * @brief Constructor
-     * @details Constructs the ParamNode from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_ind Index of the new feature
-     * @param prop The property to fit to
-     */
-    CbParamTopLevNode(node_ptr feat, int feat_ind, std::vector<double>& prop);
-
-    /**
-     * @brief Constructor
-     * @details Constructs the ParamNode from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_ind Index of the new feature
-     * @param l_bound Minimum absolute value allowed for the feature.
-     * @param u_bound Maximum absolute value allowed for the feature.
-     * @param param_list The list of parameters to optimize using non-linear least squares
-     */
-    CbParamTopLevNode(node_ptr feat, int feat_ind, double l_bound=1e-50, double u_bound=1e50);
-
-    // DocString: cb_param_top_lev_node_n_params
-    /**
-     * @brief returns the number of theoretical parameters for this feature
-     * @return the number of theoretical parameters
-     */
-    inline int n_params(int n_cur = 0){return 2;}
-
-    // DocString: cb_param_top_lev_node_set_value
-    /**
-     * @brief Set the values of the training data for the feature inside of the value storage arrays
-     *
-     * @param offset(int) Key to determine which part of the temporary storage array to look into
-     */
-    void set_value(int offset = -1);
-
-    // DocString: cb_param_top_lev_node_set_test_value
-    /**
-     * @brief Set the values of the test data for the feature inside of the value storage arrays
-     *
-     * @param offset(int) Key to determine which part of the temporary storage array to look into
-     */
-    void set_test_value(int offset = -1);
-
-    // DocString: cb_param_top_lev_node_expr
-    /**
-     * @brief Get the expression for the overall feature (From root node down)
-     */
-    inline std::string expr(){return "(" + std::to_string(_params[0]) + "*" + _feats[0]->expr() + " + " + std::to_string(_params[1]) + ")^3";}
-
-    // DocString: cb_param_top_lev_node_domain
-    /**
-     * @brief The domain for the feature (min/max values)
-     */
-    inline Domain domain(){return _feats[0]->domain().pow(3.0, _params[0], _params[1]);}
-
-    // DocString: cb_param_top_lev_node_set_bounds
-    /**
-     * @brief Set the bounds for the nl parameterization
-     *
-     * @param lb pointer to the lower bounds data
-     * @param ub pointer to the upper bounds data
-     */
-    void set_bounds(double* lb, double* ub, int from_parent=2);
-};
-
-void generateCbParamTopLevNode(std::vector<node_ptr>& feat_list, node_ptr feat, int& feat_ind, double l_bound, double u_bound, std::vector<double>& prop);
-
-#endif
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/cb/parameterized_cube.cpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/cb/parameterized_cube.cpp
index 5eb08d7d7b205f3e4b7a700d6298a555bc845ede..ca8c2f3236d1cf76fce1af753814dabb894c00f2 100644
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/cb/parameterized_cube.cpp
+++ b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/cb/parameterized_cube.cpp
@@ -52,25 +52,32 @@ void CbParamNode::get_parameters(std::vector<double>& prop)
     double min_res = nlopt_wrapper::optimize_feature_params(d);
 }
 
-void CbNode::set_value(const double* params, int offset)
+void CbNode::set_value(const double* params, int offset, int depth)
 {
     offset = (offset == -1) ? rung() : offset;
+    double* vp_0 = (depth < nlopt_wrapper::_max_param_depth) ? _feats[0]->value_ptr(params + 2, offset + 2, depth + 1) : _feats[0]->value_ptr(offset + 2);
+
     if(_selected)
         allowed_op_funcs::cb(_n_samp, _feats[0]->value_ptr(params + 2, offset + 2), params[0], params[1], node_value_arrs::get_d_matrix_ptr(_d_mat_ind));
 
     allowed_op_funcs::cb(_n_samp, _feats[0]->value_ptr(params + 2, offset + 2), params[0], params[1], node_value_arrs::get_value_ptr(_arr_ind, _feat_ind, offset, false));
 }
 
-void CbNode::set_test_value(const double* params, int offset)
+void CbNode::set_test_value(const double* params, int offset, int depth)
 {
     offset = (offset == -1) ? rung() : offset;
+    double* vp_0 = (depth < nlopt_wrapper::_max_param_depth) ? _feats[0]->test_value_ptr(params + 2, offset + 2, depth + 1) : _feats[0]->test_value_ptr(offset + 2);
+
     allowed_op_funcs::cb(_n_test_samp, _feats[0]->test_value_ptr(params + 2, offset + 2), params[0], params[1], node_value_arrs::get_test_value_ptr(_arr_ind, _feat_ind, offset, false));
 }
 
-void CbNode::set_bounds(double* lb, double* ub, int from_parent)
+void CbNode::set_bounds(double* lb, double* ub, int from_parent, int depth)
 {
     lb[0] = 1.0;
     ub[0] = 1.0;
 
-    _feats[0]->set_bounds(lb + 2, ub + 2);
+    if(depth >= nlopt_wrapper::_max_param_depth)
+        return;
+
+    _feats[0]->set_bounds(lb + 2, ub + 2, depth + 1);
 }
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/cbrt/cube_root.hpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/cbrt/cube_root.hpp
index b63e94dec10c3fb4e67fe11a473e9eb32a57e60e..705de1631de40751e13e19c6354d8a596e4bf145 100644
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/cbrt/cube_root.hpp
+++ b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/cbrt/cube_root.hpp
@@ -10,6 +10,7 @@
 #define CBRT_NODE
 
 #include <feature_creation/node/operator_nodes/OperatorNode.hpp>
+#include <fmt/core.h>
 
 // DocString: cls_cbrt_node
 /**
@@ -67,7 +68,13 @@ public:
     /**
      * @brief Get the expression for the overall feature (From root node down)
      */
-    inline std::string expr(){return "cbrt(" + _feats[0]->expr() + ")";}
+    inline std::string expr()
+    {
+        return fmt::format(
+            "cbrt[{}]",
+            _feats[0]->expr()
+        );
+    }
 
     // DocString: cbrt_node_set_value
     /**
@@ -154,7 +161,7 @@ public:
          * @param offset(int) Key to determine which part of the temporary storage array to look into
          * @param params pointer to the parameter values
          */
-        void set_value(const double* params, int offset = -1);
+        void set_value(const double* params, int offset=-1, int depth=1);
 
         /**
          * @brief Set the values of the test data for the feature inside of the value storage arrays
@@ -162,7 +169,7 @@ public:
          * @param offset(int) Key to determine which part of the temporary storage array to look into
          * @param params pointer to the parameter values
          */
-        void set_test_value(const double* params, int offset = -1);
+        void set_test_value(const double* params, int offset=-1, int depth=1);
 
         /**
          * @brief The domain for the feature (min/max values)
@@ -170,7 +177,14 @@ public:
          * @param params parameter values for non-linear operations
          * @return the domain
          */
-        inline Domain domain(double* params){return _feats[0]->domain(params + 2).pow(1.0 / 3.0, params[0], params[1]);}
+        inline Domain domain(double* params, int depth=1)
+        {
+            if(depth < nlopt_wrapper::_max_param_depth)
+                return _feats[0]->domain(params + 2, depth + 1).pow(1.0 / 3.0, params[0], params[1]);
+            else
+                return _feats[0]->domain().pow(1.0 / 3.0, params[0], params[1]);
+        }
+        // inline Domain domain(double* params){return _feats[0]->domain(params + 2).pow(1.0 / 3.0, params[0], params[1]);}
 
         /**
          * @brief The expression of the feature
@@ -178,7 +192,16 @@ public:
          * @param params parameter values for non-linear operations
          * @return feature expression
          */
-        inline std::string expr(double* params){return "cbrt(" + std::to_string(params[0]) + "*" + _feats[0]->expr(params + 2) + " + " + std::to_string(params[1]) + ")";}
+        inline std::string expr(double* params, int depth=1)
+        {
+            return fmt::format(
+                "cbrt[{:.10e} * {} {:+15.10e}]",
+                params[0],
+                (depth < nlopt_wrapper::_max_param_depth ? _feats[0]->expr(params + 2, depth + 1) : _feats[0]->expr()),
+                params[1]
+            );
+        }
+        // inline std::string expr(double* params){return "cbrt(" + std::to_string(params[0]) + "*" + _feats[0]->expr(params + 2) + " + " + std::to_string(params[1]) + ")";}
 
         /**
          * @brief Set the bounds for the nl parameterization
@@ -186,7 +209,7 @@ public:
          * @param lb pointer to the lower bounds data
          * @param ub pointer to the upper bounds data
          */
-        virtual void set_bounds(double* lb, double* ub, int from_parent=2);
+        virtual void set_bounds(double* lb, double* ub, int from_parent=2, int depth = 1);
 
         /**
          * @brief Calculates the derivative of an operation with respect to the parameters for a given sample
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/cbrt/parameterize_top_lev_cube_root.cpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/cbrt/parameterize_top_lev_cube_root.cpp
deleted file mode 100644
index ae37c16004aa143b4370e13702c2edd4ea151f6f..0000000000000000000000000000000000000000
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/cbrt/parameterize_top_lev_cube_root.cpp
+++ /dev/null
@@ -1,65 +0,0 @@
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/cbrt/parameterize_top_lev_cube_root.hpp>
-
-void generateCbrtParamTopLevNode(std::vector<node_ptr>& feat_list, node_ptr feat, int& feat_ind, double l_bound, double u_bound, std::vector<double>& prop)
-{
-    ++feat_ind;
-    node_ptr new_feat = std::make_shared<CbrtParamTopLevNode>(feat, feat_ind, prop);
-
-    if(new_feat->parameters()[0] * feat->domain() + new_feat->parameters()[1] <= 0.0)
-        return;
-
-    new_feat->set_value();
-    if(new_feat->is_nan() || new_feat->is_const() || (util_funcs::max_abs_val<double>(new_feat->value_ptr(), new_feat->n_samp()) > u_bound) || (util_funcs::max_abs_val<double>(new_feat->value_ptr(), new_feat->n_samp()) < l_bound))
-        return;
-
-    feat_list.push_back(new_feat);
-}
-
-CbrtParamTopLevNode::CbrtParamTopLevNode()
-{}
-
-CbrtParamTopLevNode::CbrtParamTopLevNode(node_ptr feat, int feat_ind, double l_bound, double u_bound, std::vector<double>& prop) :
-    CbrtParamNode(feat, feat_ind, l_bound, u_bound)
-{
-    _params.resize(n_params(), 0.0);
-    get_parameters(prop);
-    if((_params[0] * _feats[0]->domain() + _params[1]) <= 0.0)
-        throw InvalidFeatureException();
-
-    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();
-}
-
-CbrtParamTopLevNode::CbrtParamTopLevNode(node_ptr feat, int feat_ind, std::vector<double>& prop) :
-    CbrtParamNode(feat, feat_ind)
-{
-    _params.resize(n_params(), 0.0);
-    get_parameters(prop);
-}
-
-CbrtParamTopLevNode::CbrtParamTopLevNode(node_ptr feat, int feat_ind, double l_bound, double u_bound) :
-    CbrtParamNode(feat, feat_ind, l_bound, u_bound)
-{
-    _params.resize(n_params(), 0.0);
-}
-
-void CbrtParamTopLevNode::set_value(int offset)
-{
-    offset = (offset == -1) ? rung() : offset;
-    if(_selected)
-        allowed_op_funcs::cbrt(_n_samp, _feats[0]->value_ptr(offset + 2), _params[0], _params[1], node_value_arrs::get_d_matrix_ptr(_d_mat_ind));
-
-    allowed_op_funcs::cbrt(_n_samp, _feats[0]->value_ptr(offset + 2), _params[0], _params[1], node_value_arrs::get_value_ptr(_arr_ind, _feat_ind, offset, false));
-}
-
-void CbrtParamTopLevNode::set_test_value(int offset)
-{
-    offset = (offset == -1) ? rung() : offset;
-    allowed_op_funcs::cbrt(_n_test_samp, _feats[0]->test_value_ptr(offset + 2), _params[0], _params[1], node_value_arrs::get_test_value_ptr(_arr_ind, _feat_ind, offset, false));
-}
-
-void CbrtParamTopLevNode::set_bounds(double* lb, double* ub, int from_parent)
-{
-    lb[0] = _sign_alpha;
-    ub[0] = _sign_alpha;
-}
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/cbrt/parameterize_top_lev_cube_root.hpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/cbrt/parameterize_top_lev_cube_root.hpp
deleted file mode 100644
index b9e74a19e229cf5f9d8a03d75a8aa87621593ec8..0000000000000000000000000000000000000000
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/cbrt/parameterize_top_lev_cube_root.hpp
+++ /dev/null
@@ -1,130 +0,0 @@
-/** @file feature_creation/node/operator_nodes/allowed_operator_nodes/cbrt/parameterize_top_levcube_root.hpp
- *  @brief Class describing the parameterized cube root operator
- *
- *  This class represents the parameterized unary operator -> cbrt(alpha * A + a)
- *
- *  @author Thomas A. R. Purcell (tpurcell)
- *  @bug No known bugs.
- */
-#ifndef PARAM_TOP_LEV_CBRT_NODE
-#define PARAM_TOP_LEV_CBRT_NODE
-
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/cbrt/parameterized_cube_root.hpp>
-#include <nl_opt/NLOptWrapper.hpp>
-
-// DocString: cls_abs_node
-/**
- * @brief ParamNode for the absolute value operator
- *
- */
-class CbrtParamTopLevNode: public CbrtParamNode
-{
-    using CbrtParamNode::set_value;
-    using CbrtParamNode::set_test_value;
-    using CbrtParamNode::value_ptr;
-    using CbrtParamNode::test_value_ptr;
-    using CbrtParamNode::domain;
-    using CbrtParamNode::expr;
-
-    friend class boost::serialization::access;
-
-    /**
-     * @brief Serialization function to send over MPI
-     *
-     * @param ar Archive representation of node
-     */
-    template <typename Archive>
-    void serialize(Archive& ar, const unsigned int version)
-    {
-        ar & boost::serialization::base_object<CbrtParamNode>(*this);
-    }
-
-public:
-    /**
-     * @brief Base Constructor
-     * @details This is only used for serialization
-     */
-    CbrtParamTopLevNode();
-
-    /**
-     * @brief Constructor
-     * @details Constructs the ParamNode from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_ind Index of the new feature
-     * @param l_bound Minimum absolute value allowed for the feature.
-     * @param u_bound Maximum absolute value allowed for the feature.
-     * @param prop The property to fit to
-     */
-    CbrtParamTopLevNode(node_ptr feat, int feat_ind, double l_bound, double u_bound, std::vector<double>& prop);
-
-    /**
-     * @brief Constructor
-     * @details Constructs the ParamNode from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_ind Index of the new feature
-     * @param prop The property to fit to
-     */
-    CbrtParamTopLevNode(node_ptr feat, int feat_ind, std::vector<double>& prop);
-
-    /**
-     * @brief Constructor
-     * @details Constructs the ParamNode from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_ind Index of the new feature
-     * @param l_bound Minimum absolute value allowed for the feature.
-     * @param u_bound Maximum absolute value allowed for the feature.
-     * @param param_list The list of parameters to optimize using non-linear least squares
-     */
-    CbrtParamTopLevNode(node_ptr feat, int feat_ind, double l_bound=1e-50, double u_bound=1e50d);
-
-    // DocString: cbrt_param_top_lev_node_n_params
-    /**
-     * @brief returns the number of theoretical parameters for this feature
-     * @return the number of theoretical parameters
-     */
-    inline int n_params(int n_cur = 0){return 2;}
-
-    // DocString: cbrt_param_top_lev_node_set_value
-    /**
-     * @brief Set the values of the training data for the feature inside of the value storage arrays
-     *
-     * @param offset(int) Key to determine which part of the temporary storage array to look into
-     */
-    void set_value(int offset = -1);
-
-    // DocString: cbrt_param_top_lev_node_set_test_value
-    /**
-     * @brief Set the values of the test data for the feature inside of the value storage arrays
-     *
-     * @param offset(int) Key to determine which part of the temporary storage array to look into
-     */
-    void set_test_value(int offset = -1);
-
-    // DocString: cbrt_param_top_lev_node_expr
-    /**
-     * @brief Get the expression for the overall feature (From root node down)
-     */
-    inline std::string expr(){return "cbrt(" + std::to_string(_params[0]) + "*" + _feats[0]->expr() + " + " + std::to_string(_params[1]) + ")";}
-
-    // DocString: cbrt_param_top_lev_node_domain
-    /**
-     * @brief The domain for the feature (min/max values)
-     */
-    inline Domain domain(){return _feats[0]->domain().pow(1.0 / 3.0, _params[0], _params[1]);}
-
-    // DocString: cbrt_param_top_lev_node_set_bounds
-    /**
-     * @brief Set the bounds for the nl parameterization
-     *
-     * @param lb pointer to the lower bounds data
-     * @param ub pointer to the upper bounds data
-     */
-    void set_bounds(double* lb, double* ub, int from_parent=2);
-};
-
-void generateCbrtParamTopLevNode(std::vector<node_ptr>& feat_list, node_ptr feat, int& feat_ind, double l_bound, double u_bound, std::vector<double>& prop);
-
-#endif
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/cbrt/parameterized_cube_root.cpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/cbrt/parameterized_cube_root.cpp
index de18674e3b7f22bf4b20831cc79343341e880d5b..79a75f1c20e25725168fd66a6a93806e3577a8cd 100644
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/cbrt/parameterized_cube_root.cpp
+++ b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/cbrt/parameterized_cube_root.cpp
@@ -65,31 +65,43 @@ void CbrtParamNode::get_parameters(std::vector<double>& prop)
     }
 }
 
-void CbrtNode::set_value(const double* params, int offset)
+void CbrtNode::set_value(const double* params, int offset, int depth)
 {
     offset = (offset == -1) ? rung() : offset;
+    double* vp_0 = (depth < nlopt_wrapper::_max_param_depth) ? _feats[0]->value_ptr(params + 2, offset + 2, depth + 1) : _feats[0]->value_ptr(offset + 2);
+
     if(_selected)
         allowed_op_funcs::cbrt(_n_samp, _feats[0]->value_ptr(params + 2, offset + 2), params[0], params[1], node_value_arrs::get_d_matrix_ptr(_d_mat_ind));
 
     allowed_op_funcs::cbrt(_n_samp, _feats[0]->value_ptr(params + 2, offset + 2), params[0], params[1], node_value_arrs::get_value_ptr(_arr_ind, _feat_ind, offset, false));
 }
 
-void CbrtNode::set_test_value(const double* params, int offset)
+void CbrtNode::set_test_value(const double* params, int offset, int depth)
 {
     offset = (offset == -1) ? rung() : offset;
+    double* vp_0 = (depth < nlopt_wrapper::_max_param_depth) ? _feats[0]->test_value_ptr(params + 2, offset + 2, depth + 1) : _feats[0]->test_value_ptr(offset + 2);
+
     allowed_op_funcs::cbrt(_n_test_samp, _feats[0]->test_value_ptr(params + 2, offset + 2), params[0], params[1], node_value_arrs::get_test_value_ptr(_arr_ind, _feat_ind, offset, false));
 }
 
-void CbrtNode::set_bounds(double* lb, double* ub, int from_parent)
+void CbrtNode::set_bounds(double* lb, double* ub, int from_parent, int depth)
 {
     lb[0] = 1.0;
     ub[0] = 1.0;
+
+    if(depth >= nlopt_wrapper::_max_param_depth)
+        return;
+
     _feats[0]->set_bounds(lb + 2, ub + 2);
 }
 
-void CbrtParamNode::set_bounds(double* lb, double* ub, int from_parent)
+void CbrtParamNode::set_bounds(double* lb, double* ub, int from_parent, int depth)
 {
     lb[0] = _sign_alpha;
     ub[0] = _sign_alpha;
-    _feats[0]->set_bounds(lb + 2, ub + 2);
+
+    if(depth >= nlopt_wrapper::_max_param_depth)
+        return;
+
+    _feats[0]->set_bounds(lb + 2, ub + 2, depth + 1);
 }
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/cbrt/parameterized_cube_root.hpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/cbrt/parameterized_cube_root.hpp
index f185ba86e5b5c7ef19ad662d0679d1ac1a555ef3..07a356ce861ea05f45381ac1efe18cd3686f666a 100644
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/cbrt/parameterized_cube_root.hpp
+++ b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/cbrt/parameterized_cube_root.hpp
@@ -162,7 +162,7 @@ public:
      * @param lb pointer to the lower bounds data
      * @param ub pointer to the upper bounds data
      */
-    void set_bounds(double* lb, double* ub, int from_parent=2);
+    void set_bounds(double* lb, double* ub, int from_parent=2, int depth=1);
 
     /**
      * @brief Converts a feature into a postfix expression (reverse polish notation)
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/cos/cos.hpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/cos/cos.hpp
index 9a9ae1d375311b212a2b3a5497c73476f224a6c3..51ff78cf256daa1c03985f45ba3d8660608ebd2a 100644
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/cos/cos.hpp
+++ b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/cos/cos.hpp
@@ -10,6 +10,7 @@
 #define COS_NODE
 
 #include <feature_creation/node/operator_nodes/OperatorNode.hpp>
+#include <fmt/core.h>
 
 // DocString: cls_cos_node
 /**
@@ -67,7 +68,13 @@ public:
     /**
      * @brief Get the expression for the overall feature (From root node down)
      */
-    inline std::string expr(){return "cos(" + _feats[0]->expr() + ")";}
+    inline std::string expr()
+    {
+        return fmt::format(
+            "cos[{}]",
+            _feats[0]->expr()
+        );
+    }
 
     // DocString: cos_node_set_value
     /**
@@ -154,7 +161,7 @@ public:
          * @param offset(int) Key to determine which part of the temporary storage array to look into
          * @param params pointer to the parameter values
          */
-        void set_value(const double* params, int offset = -1);
+        void set_value(const double* params, int offset=-1, int depth=1);
 
         /**
          * @brief Set the values of the test data for the feature inside of the value storage arrays
@@ -162,7 +169,7 @@ public:
          * @param offset(int) Key to determine which part of the temporary storage array to look into
          * @param params pointer to the parameter values
          */
-        void set_test_value(const double* params, int offset = -1);
+        void set_test_value(const double* params, int offset=-1, int depth=1);
 
         /**
          * @brief The domain for the feature (min/max values)
@@ -170,7 +177,7 @@ public:
          * @param params parameter values for non-linear operations
          * @return the domain
          */
-        inline Domain domain(double* params){return Domain(-1.0, 1.0);}
+        inline Domain domain(double* params, int depth=1){return Domain(-1.0, 1.0);}
 
         /**
          * @brief The expression of the feature
@@ -178,7 +185,16 @@ public:
          * @param params parameter values for non-linear operations
          * @return feature expression
          */
-        inline std::string expr(double* params){return "cos(" + std::to_string(params[0]) + "*" + _feats[0]->expr( params + 2) + " + " + std::to_string(params[1]) + ")";}
+        inline std::string expr(double* params, int depth=1)
+        {
+            return fmt::format(
+                "cbrt[{:.10e} * {} {:+15.10e}]",
+                params[0],
+                (depth < nlopt_wrapper::_max_param_depth ? _feats[0]->expr(params + 2, depth + 1) : _feats[0]->expr()),
+                params[1]
+            );
+        }
+        // inline std::string expr(double* params){return "cos(" + std::to_string(params[0]) + "*" + _feats[0]->expr( params + 2) + " + " + std::to_string(params[1]) + ")";}
 
         /**
          * @brief Set the bounds for the nl parameterization
@@ -186,7 +202,7 @@ public:
          * @param lb pointer to the lower bounds data
          * @param ub pointer to the upper bounds data
          */
-        void set_bounds(double* lb, double* ub, int from_parent=2);
+        void set_bounds(double* lb, double* ub, int from_parent=2, int depth = 1);
 
         /**
          * @brief Calculates the derivative of an operation with respect to the parameters for a given sample
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/cos/parameterize_top_lev_cos.cpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/cos/parameterize_top_lev_cos.cpp
deleted file mode 100644
index 186e0c325ce1195d124dcbd9cfdabcdfdf763855..0000000000000000000000000000000000000000
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/cos/parameterize_top_lev_cos.cpp
+++ /dev/null
@@ -1,68 +0,0 @@
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/cos/parameterize_top_lev_cos.hpp>
-
-void generateCosParamTopLevNode(std::vector<node_ptr>& feat_list, node_ptr feat, int& feat_ind, double l_bound, double u_bound, std::vector<double>& prop)
-{
-    ++feat_ind;
-
-    if((feat->type() == NODE_TYPE::SIN) || (feat->type() == NODE_TYPE::COS))
-        return;
-
-    node_ptr new_feat = std::make_shared<CosParamTopLevNode>(feat, feat_ind, prop);
-
-    new_feat->set_value();
-    if(new_feat->is_nan() || new_feat->is_const() || (util_funcs::max_abs_val<double>(new_feat->value_ptr(), new_feat->n_samp()) > u_bound) || (util_funcs::max_abs_val<double>(new_feat->value_ptr(), new_feat->n_samp()) < l_bound))
-        return;
-
-    feat_list.push_back(new_feat);
-}
-
-CosParamTopLevNode::CosParamTopLevNode()
-{}
-
-CosParamTopLevNode::CosParamTopLevNode(node_ptr feat, int feat_ind, double l_bound, double u_bound, std::vector<double>& prop) :
-    CosParamNode(feat, feat_ind, l_bound, u_bound)
-{
-    if((feat->type() == NODE_TYPE::SIN) || (feat->type() == NODE_TYPE::COS))
-        throw InvalidFeatureException();
-
-    _params.resize(n_params(), 0.0);
-    get_parameters(prop);
-
-    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();
-}
-
-CosParamTopLevNode::CosParamTopLevNode(node_ptr feat, int feat_ind, std::vector<double>& prop) :
-    CosParamNode(feat, feat_ind)
-{
-    _params.resize(n_params(), 0.0);
-    get_parameters(prop);
-}
-
-CosParamTopLevNode::CosParamTopLevNode(node_ptr feat, int feat_ind, double l_bound, double u_bound) :
-    CosParamNode(feat, feat_ind, l_bound, u_bound)
-{
-    _params.resize(n_params(), 0.0);
-}
-
-void CosParamTopLevNode::set_value(int offset)
-{
-    offset = (offset == -1) ? rung() : offset;
-    if(_selected)
-        allowed_op_funcs::cos(_n_samp, _feats[0]->value_ptr(offset + 2), _params[0], _params[1], node_value_arrs::get_d_matrix_ptr(_d_mat_ind));
-
-    allowed_op_funcs::cos(_n_samp, _feats[0]->value_ptr(offset + 2), _params[0], _params[1], node_value_arrs::get_value_ptr(_arr_ind, _feat_ind, offset, false));
-}
-
-void CosParamTopLevNode::set_test_value(int offset)
-{
-    offset = (offset == -1) ? rung() : offset;
-    allowed_op_funcs::cos(_n_test_samp, _feats[0]->test_value_ptr(offset + 2), _params[0], _params[1], node_value_arrs::get_test_value_ptr(_arr_ind, _feat_ind, offset, false));
-}
-
-
-void CosParamTopLevNode::set_bounds(double* lb, double* ub, int from_parent)
-{
-    lb[1] = -1.0 * M_PI;
-    ub[1] = M_PI;
-}
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/cos/parameterize_top_lev_cos.hpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/cos/parameterize_top_lev_cos.hpp
deleted file mode 100644
index 6805c38879708acaa758253ded198dbc1e2112a3..0000000000000000000000000000000000000000
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/cos/parameterize_top_lev_cos.hpp
+++ /dev/null
@@ -1,131 +0,0 @@
-/** @file feature_creation/node/operator_nodes/allowed_operator_nodes/cos/parameterize_top_levcos.hpp
- *  @brief Class describing the parameterized cos operator
- *
- *  This class represents the parameterized unary operator -> cos(alpha * A + a)
- *
- *  @author Thomas A. R. Purcell (tpurcell)
- *  @bug No known bugs.
- */
-#ifndef PARAM_TOP_LEV_COS_NODE
-#define PARAM_TOP_LEV_COS_NODE
-
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/cos/parameterized_cos.hpp>
-#include <nl_opt/NLOptWrapper.hpp>
-
-// DocString: cls_abs_node
-/**
- * @brief ParamNode for the absolute value operator
- *
- */
-class CosParamTopLevNode: public CosParamNode
-{
-    using CosParamNode::set_value;
-    using CosParamNode::set_test_value;
-    using CosParamNode::value_ptr;
-    using CosParamNode::test_value_ptr;
-    using CosParamNode::domain;
-    using CosParamNode::expr;
-
-    friend class boost::serialization::access;
-
-    /**
-     * @brief Serialization function to send over MPI
-     *
-     * @param ar Archive representation of node
-     */
-    template <typename Archive>
-    void serialize(Archive& ar, const unsigned int version)
-    {
-        ar & boost::serialization::base_object<CosParamNode>(*this);
-    }
-
-public:
-    /**
-     * @brief Base Constructor
-     * @details This is only used for serialization
-     */
-    CosParamTopLevNode();
-
-    /**
-     * @brief Constructor
-     * @details Constructs the ParamNode from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_ind Index of the new feature
-     * @param l_bound Minimum absolute value allowed for the feature.
-     * @param u_bound Maximum absolute value allowed for the feature.
-     * @param param_list The list of parameters to optimize using non-linear least squares
-     * @param prop The property to fit to
-     */
-    CosParamTopLevNode(node_ptr feat, int feat_ind, double l_bound, double u_bound, std::vector<double>& prop);
-
-    /**
-     * @brief Constructor
-     * @details Constructs the ParamNode from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_ind Index of the new feature
-     * @param prop The property to fit to
-     */
-    CosParamTopLevNode(node_ptr feat, int feat_ind, std::vector<double>& prop);
-
-    /**
-     * @brief Constructor
-     * @details Constructs the ParamNode from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_ind Index of the new feature
-     * @param l_bound Minimum absolute value allowed for the feature.
-     * @param u_bound Maximum absolute value allowed for the feature.
-     * @param param_list The list of parameters to optimize using non-linear least squares
-     */
-    CosParamTopLevNode(node_ptr feat, int feat_ind, double l_bound=1e-50, double u_bound=1e50);
-
-    // DocString: cos_param_top_lev_node_n_params
-    /**
-     * @brief returns the number of theoretical parameters for this feature
-     * @return the number of theoretical parameters
-     */
-    inline int n_params(int n_cur = 0){return 2;}
-
-    // DocString: cos_param_top_lev_node_set_value
-    /**
-     * @brief Set the values of the training data for the feature inside of the value storage arrays
-     *
-     * @param offset(int) Key to determine which part of the temporary storage array to look into
-     */
-    void set_value(int offset = -1);
-
-    // DocString: cos_param_top_lev_node_set_test_value
-    /**
-     * @brief Set the values of the test data for the feature inside of the value storage arrays
-     *
-     * @param offset(int) Key to determine which part of the temporary storage array to look into
-     */
-    void set_test_value(int offset = -1);
-
-    // DocString: cos_param_top_lev_node_expr
-    /**
-     * @brief Get the expression for the overall feature (From root node down)
-     */
-    inline std::string expr(){return "cos(" + std::to_string(_params[0]) + "*" + _feats[0]->expr() + " + " + std::to_string(_params[1]) + ")";}
-
-    // DocString: cos_param_top_lev_node_domain
-    /**
-     * @brief The domain for the feature (min/max values)
-     */
-    inline Domain domain(){return Domain(-1.0, 1.0);}
-
-    // DocString: cos_param_top_lev_node_set_bounds
-    /**
-     * @brief Set the bounds for the nl parameterization
-     *
-     * @param lb pointer to the lower bounds data
-     * @param ub pointer to the upper bounds data
-     */
-    void set_bounds(double* lb, double* ub, int from_parent=2);
-};
-
-void generateCosParamTopLevNode(std::vector<node_ptr>& feat_list, node_ptr feat, int& feat_ind, double l_bound, double u_bound, std::vector<double>& prop);
-
-#endif
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/cos/parameterized_cos.cpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/cos/parameterized_cos.cpp
index 8d1c93f04fa8b3cc49b29fa8012e38fec834fbbb..c03df526a0c6ecc496956cb18e934a645ca76891 100644
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/cos/parameterized_cos.cpp
+++ b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/cos/parameterized_cos.cpp
@@ -56,22 +56,26 @@ void CosParamNode::get_parameters(std::vector<double>& prop)
     double min_res = nlopt_wrapper::optimize_feature_params(d);
 }
 
-void CosNode::set_value(const double* params, int offset)
+void CosNode::set_value(const double* params, int offset, int depth)
 {
     offset = (offset == -1) ? rung() : offset;
+    double* vp_0 = (depth < nlopt_wrapper::_max_param_depth) ? _feats[0]->value_ptr(params + 2, offset + 2, depth + 1) : _feats[0]->value_ptr(offset + 2);
+
     if(_selected)
         allowed_op_funcs::cos(_n_samp, _feats[0]->value_ptr(params + 2, offset + 2), params[0], params[1], node_value_arrs::get_d_matrix_ptr(_d_mat_ind));
 
     allowed_op_funcs::cos(_n_samp, _feats[0]->value_ptr(params + 2, offset + 2), params[0], params[1], node_value_arrs::get_value_ptr(_arr_ind, _feat_ind, offset, false));
 }
 
-void CosNode::set_test_value(const double* params, int offset)
+void CosNode::set_test_value(const double* params, int offset, int depth)
 {
     offset = (offset == -1) ? rung() : offset;
+    double* vp_0 = (depth < nlopt_wrapper::_max_param_depth) ? _feats[0]->test_value_ptr(params + 2, offset + 2, depth + 1) : _feats[0]->test_value_ptr(offset + 2);
+
     allowed_op_funcs::cos(_n_test_samp, _feats[0]->test_value_ptr(params + 2, offset + 2), params[0], params[1], node_value_arrs::get_test_value_ptr(_arr_ind, _feat_ind, offset, false));
 }
 
-void CosNode::set_bounds(double* lb, double* ub, int from_parent)
+void CosNode::set_bounds(double* lb, double* ub, int from_parent, int depth)
 {
     lb[1] = -1.0 * M_PI;
     ub[1] = M_PI;
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/div/divide.hpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/div/divide.hpp
index 2a6156c025c76adf73891feb3fb60399ec833153..b8a4adac944fc3af55896b65b1e59159d7ba3ff9 100644
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/div/divide.hpp
+++ b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/div/divide.hpp
@@ -10,6 +10,7 @@
 #define DIV_NODE
 
 #include <feature_creation/node/operator_nodes/OperatorNode.hpp>
+#include <fmt/core.h>
 
 // DocString: cls_div_node
 /**
@@ -69,7 +70,15 @@ public:
     /**
      * @brief Get the expression for the overall feature (From root node down)
      */
-    inline std::string expr(){return "[(" + _feats[0]->expr() + ") / (" + _feats[1]->expr() + ")]";}
+    inline std::string expr()
+    {
+        return fmt::format(
+            "[({}) / ({})]",
+            _feats[0]->expr(),
+            _feats[1]->expr()
+        );
+    }
+    // inline std::string expr(){return "[(" + _feats[0]->expr() + ") / (" + _feats[1]->expr() + ")]";}
 
     // DocString: div_node_set_value
     /**
@@ -162,7 +171,7 @@ public:
          * @param offset(int) Key to determine which part of the temporary storage array to look into
          * @param params pointer to the parameter values
          */
-        void set_value(const double* params, int offset = -1);
+        void set_value(const double* params, int offset=-1, int depth=1);
 
         /**
          * @brief Set the values of the test data for the feature inside of the value storage arrays
@@ -170,7 +179,7 @@ public:
          * @param offset(int) Key to determine which part of the temporary storage array to look into
          * @param params pointer to the parameter values
          */
-        void set_test_value(const double* params, int offset = -1);
+        void set_test_value(const double* params, int offset=-1, int depth=1);
 
         /**
          * @brief The domain for the feature (min/max values)
@@ -178,7 +187,14 @@ public:
          * @param params parameter values for non-linear operations
          * @return the domain
          */
-        inline Domain domain(double* params){return _feats[0]->domain(params + _feats[1]->n_params() + 2).div(_feats[1]->domain(params + 2), params[0], params[1]);}
+        inline Domain domain(double* params, int depth=1)
+        {
+            if(depth < nlopt_wrapper::_max_param_depth)
+                return _feats[0]->domain(params + _feats[1]->n_params() + 2, depth + 1).div(_feats[1]->domain(params + 2, depth + 1), params[0], params[1]);
+            else
+                return _feats[0]->domain().div(_feats[1]->domain(), params[0], params[1]);
+        }
+        // inline Domain domain(double* params){return _feats[0]->domain(params + _feats[1]->n_params() + 2).div(_feats[1]->domain(params + 2), params[0], params[1]);}
 
         /**
          * @brief The expression of the feature
@@ -186,7 +202,17 @@ public:
          * @param params parameter values for non-linear operations
          * @return feature expression
          */
-        inline std::string expr(double* params){return "[(" + _feats[0]->expr(params + _feats[1]->n_params() + 2) + ") / (" + std::to_string(params[0]) + "*" + _feats[1]->expr(params + 2) + " + " + std::to_string(params[1]) + ")]";}
+        inline std::string expr(double* params, int depth=1)
+        {
+            return fmt::format(
+                "[({}) / ({:.10e} * {} {:+15.10e})]",
+                (depth < nlopt_wrapper::_max_param_depth ? _feats[0]->expr(params + _feats[1]->n_params() + 2, depth + 1) : _feats[0]->expr()),
+                params[0],
+                (depth < nlopt_wrapper::_max_param_depth ? _feats[1]->expr(params + 2, depth + 1) : _feats[1]->expr()),
+                params[1]
+            );
+        }
+        // inline std::string expr(double* params){return "[(" + _feats[0]->expr(params + _feats[1]->n_params() + 2) + ") / (" + std::to_string(params[0]) + "*" + _feats[1]->expr(params + 2) + " + " + std::to_string(params[1]) + ")]";}
 
         /**
          * @brief Set the bounds for the nl parameterization
@@ -194,7 +220,7 @@ public:
          * @param lb pointer to the lower bounds data
          * @param ub pointer to the upper bounds data
          */
-        void set_bounds(double* lb, double* ub, int from_parent=2);
+        void set_bounds(double* lb, double* ub, int from_parent=2, int depth = 1);
 
         /**
          * @brief Calculates the derivative of an operation with respect to the parameters for a given sample
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/div/parameterize_top_lev_divide.cpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/div/parameterize_top_lev_divide.cpp
deleted file mode 100644
index 673ff3b69a71be1023ac922b7f7ab43a49a5ef52..0000000000000000000000000000000000000000
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/div/parameterize_top_lev_divide.cpp
+++ /dev/null
@@ -1,73 +0,0 @@
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/div/parameterize_top_lev_divide.hpp>
-
-void generateDivParamTopLevNode(std::vector<node_ptr>& feat_list, node_ptr feat_1, node_ptr feat_2, int& feat_ind, double l_bound, double u_bound, std::vector<double>& prop)
-{
-    ++feat_ind;
-    if((feat_1->type() == NODE_TYPE::INV) || (feat_2->type() == NODE_TYPE::INV) || (feat_2->type() == NODE_TYPE::DIV))
-        return;
-
-    node_ptr new_feat = std::make_shared<DivParamTopLevNode>(feat_1, feat_2, feat_ind, prop);
-
-    if((new_feat->parameters()[0] * feat_2->domain() + new_feat->parameters()[1]).contains(0.0))
-        return;
-
-    new_feat->set_value();
-    if(new_feat->is_nan() || new_feat->is_const() || (util_funcs::max_abs_val<double>(new_feat->value_ptr(), new_feat->n_samp()) > u_bound) || (util_funcs::max_abs_val<double>(new_feat->value_ptr(), new_feat->n_samp()) < l_bound))
-        return;
-
-    feat_list.push_back(new_feat);
-}
-
-DivParamTopLevNode::DivParamTopLevNode()
-{}
-
-DivParamTopLevNode::DivParamTopLevNode(node_ptr feat_1, node_ptr feat_2, int feat_ind, double l_bound, double u_bound, std::vector<double>& prop) :
-    DivParamNode(feat_1, feat_2, feat_ind, l_bound, u_bound)
-{
-     if((feat_1->type() == NODE_TYPE::INV) || (feat_2->type() == NODE_TYPE::INV) || (feat_2->type() == NODE_TYPE::DIV))
-        throw InvalidFeatureException();
-
-    _params.resize(n_params(), 0.0);
-    get_parameters(prop);
-
-    if((_params[0] * _feats[1]->domain() + _params[1]).contains(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();
-}
-
-DivParamTopLevNode::DivParamTopLevNode(node_ptr feat_1, node_ptr feat_2, int feat_ind, std::vector<double>& prop) :
-    DivParamNode(feat_1, feat_2, feat_ind)
-{
-    _params.resize(n_params(), 0.0);
-
-    get_parameters(prop);
-}
-
-DivParamTopLevNode::DivParamTopLevNode(node_ptr feat_1, node_ptr feat_2, int feat_ind, double l_bound, double u_bound) :
-    DivParamNode(feat_1, feat_2, feat_ind, l_bound, u_bound)
-{
-    _params.resize(n_params(), 0.0);
-}
-
-void DivParamTopLevNode::set_value(int offset)
-{
-    offset = (offset == -1) ? rung() : offset;
-
-    if(_selected)
-        allowed_op_funcs::div(_n_samp, _feats[0]->value_ptr(offset + 2), _feats[1]->value_ptr(offset + 1), _params[0], _params[1], node_value_arrs::get_d_matrix_ptr(_d_mat_ind));
-    allowed_op_funcs::div(_n_samp, _feats[0]->value_ptr(offset + 2), _feats[1]->value_ptr(offset + 1), _params[0], _params[1], node_value_arrs::get_value_ptr(_arr_ind, _feat_ind, offset, false));
-}
-void DivParamTopLevNode::set_test_value(int offset)
-{
-    offset = (offset == -1) ? rung() : offset;
-    allowed_op_funcs::div(_n_test_samp, _feats[0]->test_value_ptr(offset + 2), _feats[1]->test_value_ptr(offset + 1), _params[0], _params[1], node_value_arrs::get_test_value_ptr(_arr_ind, _feat_ind, offset, false));
-}
-
-void DivParamTopLevNode::set_bounds(double* lb, double* ub, int from_parent)
-{
-    lb[0] = 1.0;
-    ub[0] = 1.0;
-}
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/div/parameterize_top_lev_divide.hpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/div/parameterize_top_lev_divide.hpp
deleted file mode 100644
index 960815812acaf9a5ac4ef4679f0e32a6fcc372aa..0000000000000000000000000000000000000000
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/div/parameterize_top_lev_divide.hpp
+++ /dev/null
@@ -1,131 +0,0 @@
-/** @file feature_creation/node/operator_nodes/allowed_operator_nodes/div/parameterize_top_levdivide.hpp
- *  @brief Class describing the parameterized addition operator
- *
- *  This class represents the parameterized unary operator -> A / (alpha * B + a)
- *
- *  @author Thomas A. R. Purcell (tpurcell)
- *  @bug No known bugs.
- */
-#ifndef PARAM_TOP_LEV_DIV_NODE
-#define PARAM_TOP_LEV_DIV_NODE
-
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/div/parameterized_divide.hpp>
-#include <nl_opt/NLOptWrapper.hpp>
-
-// DocString: cls_param_add_node
-/**
- * @brief ParamNode for the absolute value operator
- *
- */
-class DivParamTopLevNode: public DivParamNode
-{
-    using DivParamNode::set_value;
-    using DivParamNode::set_test_value;
-    using DivParamNode::value_ptr;
-    using DivParamNode::test_value_ptr;
-    using DivParamNode::domain;
-    using DivParamNode::expr;
-
-    friend class boost::serialization::access;
-
-    /**
-     * @brief Serialization function to send over MPI
-     *
-     * @param ar Archive representation of node
-     */
-    template <typename Archive>
-    void serialize(Archive& ar, const unsigned int version)
-    {
-        ar & boost::serialization::base_object<DivParamNode>(*this);
-    }
-public:
-    /**
-     * @brief Base Constructor
-     * @details This is only used for serialization
-     */
-    DivParamTopLevNode();
-
-    /**
-     * @brief Constructor
-     * @details Constructs the ParamNode from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_2 shared_ptr of the feature to operate on (B)
-     * @param feat_ind Index of the new feature
-     * @param l_bound Minimum absolute value allowed for the feature.
-     * @param u_bound Maximum absolute value allowed for the feature.
-     * @param prop The property to fit to
-     */
-    DivParamTopLevNode(node_ptr feat_1, node_ptr feat_2, int feat_ind, double l_bound, double u_bound, std::vector<double>& prop);
-
-    /**
-     * @brief Constructor
-     * @details Constructs the ParamNode from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_2 shared_ptr of the feature to operate on (B)
-     * @param feat_ind Index of the new feature
-     * @param prop The property to fit to
-     */
-    DivParamTopLevNode(node_ptr feat_1, node_ptr feat_2, int feat_ind, std::vector<double>& prop);
-
-    /**
-     * @brief Constructor
-     * @details Constructs the ParamNode from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_ind Index of the new feature
-     * @param l_bound Minimum absolute value allowed for the feature.
-     * @param u_bound Maximum absolute value allowed for the feature.
-     * @param param_list The list of parameters to optimize using non-linear least squares
-     */
-    DivParamTopLevNode(node_ptr feat_1, node_ptr feat_2, int feat_ind, double l_bound=1e-50, double u_bound=1e50);
-
-    // DocString: div_param_top_lev_node_n_params
-    /**
-     * @brief returns the number of theoretical parameters for this feature
-     * @return the number of theoretical parameters
-     */
-    inline int n_params(int n_cur = 0){return 2;}
-
-    // DocString: div_param_top_lev_node_set_value
-    /**
-     * @brief Set the values of the training data for the feature inside of the value storage arrays
-     *
-     * @param offset(int) Key to determine which part of the temporary storage array to look into
-     */
-    void set_value(int offset = -1);
-
-    // DocString: div_param_top_lev_node_set_test_value
-    /**
-     * @brief Set the values of the test data for the feature inside of the value storage arrays
-     *
-     * @param offset(int) Key to determine which part of the temporary storage array to look into
-     */
-    void set_test_value(int offset = -1);
-
-    // DocString: div_param_top_lev_node_expr
-    /**
-     * @brief Get the expression for the overall feature (From root node down)
-     */
-    inline std::string expr(){return "[(" + _feats[0]->expr() + ") / (" + std::to_string(_params[0]) + "*" + _feats[1]->expr() + " + " + std::to_string(_params[1]) + ")]";}
-
-    // DocString: div_param_top_lev_node_domain
-    /**
-     * @brief The domain for the feature (min/max values)
-     */
-    inline Domain domain(){return _feats[0]->domain().div(_feats[1]->domain(), _params[0], _params[1]);}
-
-    // DocString: div_param_top_lev_node_set_bounds
-    /**
-     * @brief Set the bounds for the nl parameterization
-     *
-     * @param lb pointer to the lower bounds data
-     * @param ub pointer to the upper bounds data
-     */
-    void set_bounds(double* lb, double* ub, int from_parent=2);
-};
-
-void generateDivParamTopLevNode(std::vector<node_ptr>& feat_list, node_ptr feat_1, node_ptr feat_2, int& feat_ind, double l_bound, double u_bound, std::vector<double>& prop);
-
-#endif
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/div/parameterized_divide.cpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/div/parameterized_divide.cpp
index cbdc91142a258e641fdcc0536e29af90ec5698ba..8d8f71fce88ea1838debddc3ad80604d3b3542b8 100644
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/div/parameterized_divide.cpp
+++ b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/div/parameterized_divide.cpp
@@ -63,25 +63,37 @@ void DivParamNode::get_parameters(std::vector<double>& prop)
     double min_res = nlopt_wrapper::optimize_feature_params(d, rung() > 1);
 }
 
-void DivNode::set_value(const double* params, int offset)
+void DivNode::set_value(const double* params, int offset, int depth)
 {
     offset = (offset == -1) ? rung() : offset;
 
+    double* vp_0 = (depth < nlopt_wrapper::_max_param_depth) ? _feats[0]->value_ptr(params + _feats[1]->n_params() + 2, offset + 2, depth + 1) : _feats[0]->value_ptr(offset + 2);
+    double* vp_1 = (depth < nlopt_wrapper::_max_param_depth) ? _feats[1]->value_ptr(params + 2, offset + 1, depth + 1) : _feats[1]->value_ptr(offset + 1);
+
     if(_selected)
-        allowed_op_funcs::div(_n_samp, _feats[0]->value_ptr(params + 2 + _feats[1]->n_params(), offset + 2), _feats[1]->value_ptr(params + 2, offset + 1), params[0], params[1], node_value_arrs::get_d_matrix_ptr(_d_mat_ind));
-    allowed_op_funcs::div(_n_samp, _feats[0]->value_ptr(params + 2 + _feats[1]->n_params(), offset + 2), _feats[1]->value_ptr(params + 2, offset + 1), params[0], params[1], node_value_arrs::get_value_ptr(_arr_ind, _feat_ind, offset, false));
+        allowed_op_funcs::div(_n_samp, vp_0, vp_1, params[0], params[1], node_value_arrs::get_d_matrix_ptr(_d_mat_ind));
+
+    allowed_op_funcs::div(_n_samp, vp_0, vp_1, params[0], params[1], node_value_arrs::get_value_ptr(_arr_ind, _feat_ind, offset, false));
 }
-void DivNode::set_test_value(const double* params, int offset)
+
+void DivNode::set_test_value(const double* params, int offset, int depth)
 {
     offset = (offset == -1) ? rung() : offset;
-    allowed_op_funcs::div(_n_test_samp, _feats[0]->test_value_ptr(params + 2 + _feats[1]->n_params(), offset + 2), _feats[1]->test_value_ptr(params + 2, offset + 1), params[0], params[1], node_value_arrs::get_test_value_ptr(_arr_ind, _feat_ind, offset, false));
+
+    double* vp_0 = (depth < nlopt_wrapper::_max_param_depth) ? _feats[0]->test_value_ptr(params + _feats[1]->n_params() + 2, offset + 2, depth + 1) : _feats[0]->test_value_ptr(offset + 2);
+    double* vp_1 = (depth < nlopt_wrapper::_max_param_depth) ? _feats[1]->test_value_ptr(params + 2, offset + 1, depth + 1) : _feats[1]->test_value_ptr(offset + 1);
+
+    allowed_op_funcs::div(_n_test_samp, vp_0, vp_1, params[0], params[1], node_value_arrs::get_test_value_ptr(_arr_ind, _feat_ind, offset, false));
 }
 
-void DivNode::set_bounds(double* lb, double* ub, int from_parent)
+void DivNode::set_bounds(double* lb, double* ub, int from_parent, int depth)
 {
     lb[0] = 1.0;
     ub[0] = 1.0;
 
-    _feats[0]->set_bounds(lb + 2 + _feats[1]->n_params(), ub + 2 + _feats[1]->n_params(), 2 + _feats[1]->n_params());
-    _feats[1]->set_bounds(lb + 2, ub + 2);
+    if(depth >= nlopt_wrapper::_max_param_depth)
+        return;
+
+    _feats[0]->set_bounds(lb + 2 + _feats[1]->n_params(), ub + 2 + _feats[1]->n_params(), 2 + _feats[1]->n_params(), depth + 1);
+    _feats[1]->set_bounds(lb + 2, ub + 2, depth + 1);
 }
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/exp/exponential.hpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/exp/exponential.hpp
index 0026a696cd2120913fe3fa860e62a0d42274be61..0a8dec85bd933d3fdc737853949124cbeae48b70 100644
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/exp/exponential.hpp
+++ b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/exp/exponential.hpp
@@ -10,6 +10,7 @@
 #define EXP_NODE
 
 #include <feature_creation/node/operator_nodes/OperatorNode.hpp>
+#include <fmt/core.h>
 
 // DocString: cls_exp_node
 /**
@@ -67,7 +68,13 @@ public:
     /**
      * @brief Get the expression for the overall feature (From root node down)
      */
-    inline std::string expr(){return "exp(" + _feats[0]->expr() + ")";}
+    inline std::string expr()
+    {
+        return fmt::format(
+            "exp[{}]",
+            _feats[0]->expr()
+        );
+    }
 
     // DocString: exp_node_set_value
     /**
@@ -154,7 +161,7 @@ public:
          * @param offset(int) Key to determine which part of the temporary storage array to look into
          * @param params pointer to the parameter values
          */
-        void set_value(const double* params, int offset = -1);
+        void set_value(const double* params, int offset=-1, int depth=1);
 
         /**
          * @brief Set the values of the test data for the feature inside of the value storage arrays
@@ -162,7 +169,7 @@ public:
          * @param offset(int) Key to determine which part of the temporary storage array to look into
          * @param params pointer to the parameter values
          */
-        void set_test_value(const double* params, int offset = -1);
+        void set_test_value(const double* params, int offset=-1, int depth=1);
 
         /**
          * @brief The domain for the feature (min/max values)
@@ -170,7 +177,14 @@ public:
          * @param params parameter values for non-linear operations
          * @return the domain
          */
-        inline Domain domain(double* params){return _feats[0]->domain(params + 2).exp(params[0], params[1]);}
+        inline Domain domain(double* params, int depth=1)
+        {
+            if(depth < nlopt_wrapper::_max_param_depth)
+                return _feats[0]->domain(params + 2, depth + 1).exp(params[0], params[1]);
+            else
+                return _feats[0]->domain().exp(params[0], params[1]);
+        }
+        // inline Domain domain(double* params){return _feats[0]->domain(params + 2).exp(params[0], params[1]);}
 
         /**
          * @brief The expression of the feature
@@ -178,7 +192,16 @@ public:
          * @param params parameter values for non-linear operations
          * @return feature expression
          */
-        inline std::string expr(double* params){return "exp(" + std::to_string(params[0]) + "*" + _feats[0]->expr(params + 2) + " + " + std::to_string(params[1]) + ")";}
+        inline std::string expr(double* params, int depth=1)
+        {
+            return fmt::format(
+                "exp[{:.10e} * {} {:+15.10e}]",
+                params[0],
+                (depth < nlopt_wrapper::_max_param_depth ? _feats[0]->expr(params + 2, depth + 1) : _feats[0]->expr()),
+                params[1]
+            );
+        }
+        // inline std::string expr(double* params){return "exp(" + std::to_string(params[0]) + "*" + _feats[0]->expr(params + 2) + " + " + std::to_string(params[1]) + ")";}
 
         /**
          * @brief Set the bounds for the nl parameterization
@@ -186,7 +209,7 @@ public:
          * @param lb pointer to the lower bounds data
          * @param ub pointer to the upper bounds data
          */
-        void set_bounds(double* lb, double* ub, int from_parent=2);
+        void set_bounds(double* lb, double* ub, int from_parent=2, int depth = 1);
 
         /**
          * @brief Calculates the derivative of an operation with respect to the parameters for a given sample
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/exp/parameterize_top_lev_exponential.cpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/exp/parameterize_top_lev_exponential.cpp
deleted file mode 100644
index db984723e75c684a56ab8d7677d51ca7b0f967fa..0000000000000000000000000000000000000000
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/exp/parameterize_top_lev_exponential.cpp
+++ /dev/null
@@ -1,68 +0,0 @@
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/exp/parameterize_top_lev_exponential.hpp>
-
-void generateExpParamTopLevNode(std::vector<node_ptr>& feat_list, node_ptr feat, int& feat_ind, double l_bound, double u_bound, std::vector<double>& prop)
-{
-    ++feat_ind;
-
-    if((feat->type() == NODE_TYPE::NEG_EXP) || (feat->type() == NODE_TYPE::EXP) || (feat->type() == NODE_TYPE::ADD) || (feat->type() == NODE_TYPE::SUB) || (feat->type() == NODE_TYPE::LOG))
-        return;
-
-    node_ptr new_feat = std::make_shared<ExpParamTopLevNode>(feat, feat_ind, prop);
-
-    new_feat->set_value();
-    if(new_feat->is_nan() || new_feat->is_const() || (util_funcs::max_abs_val<double>(new_feat->value_ptr(), new_feat->n_samp()) > u_bound) || (util_funcs::max_abs_val<double>(new_feat->value_ptr(), new_feat->n_samp()) < l_bound))
-        return;
-
-    feat_list.push_back(new_feat);
-}
-
-ExpParamTopLevNode::ExpParamTopLevNode()
-{}
-
-ExpParamTopLevNode::ExpParamTopLevNode(node_ptr feat, int feat_ind, double l_bound, double u_bound, std::vector<double>& prop) :
-    ExpParamNode(feat, feat_ind, l_bound, u_bound)
-{
-    if((feat->type() == NODE_TYPE::NEG_EXP) || (feat->type() == NODE_TYPE::EXP) || (feat->type() == NODE_TYPE::ADD) || (feat->type() == NODE_TYPE::SUB) || (feat->type() == NODE_TYPE::LOG))
-        throw InvalidFeatureException();
-
-    _params.resize(n_params(), 0.0);
-    get_parameters(prop);
-
-    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();
-}
-
-ExpParamTopLevNode::ExpParamTopLevNode(node_ptr feat, int feat_ind, std::vector<double>& prop) :
-    ExpParamNode(feat, feat_ind)
-{
-    _params.resize(n_params(), 0.0);
-    get_parameters(prop);
-}
-
-ExpParamTopLevNode::ExpParamTopLevNode(node_ptr feat, int feat_ind, double l_bound, double u_bound) :
-    ExpParamNode(feat, feat_ind, l_bound, u_bound)
-{
-    _params.resize(n_params(), 0.0);
-}
-
-void ExpParamTopLevNode::set_value(int offset)
-{
-    offset = (offset == -1) ? rung() : offset;
-    if(_selected)
-        allowed_op_funcs::exp(_n_samp, _feats[0]->value_ptr(offset + 2), _params[0], _params[1], node_value_arrs::get_d_matrix_ptr(_d_mat_ind));
-
-    allowed_op_funcs::exp(_n_samp, _feats[0]->value_ptr(offset + 2), _params[0], _params[1], node_value_arrs::get_value_ptr(_arr_ind, _feat_ind, offset, false));
-}
-void ExpParamTopLevNode::set_test_value(int offset)
-{
-    offset = (offset == -1) ? rung() : offset;
-    allowed_op_funcs::exp(_n_test_samp, _feats[0]->test_value_ptr(offset + 2), _params[0], _params[1], node_value_arrs::get_test_value_ptr(_arr_ind, _feat_ind, offset, false));
-}
-
-void ExpParamTopLevNode::set_bounds(double* lb, double* ub, int from_parent)
-{
-    lb[0] = 0.0;
-
-    *(lb - from_parent) = 1.0;
-    *(ub - from_parent) = 1.0;
-}
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/exp/parameterize_top_lev_exponential.hpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/exp/parameterize_top_lev_exponential.hpp
deleted file mode 100644
index 4bfc5ca8c7e66cef1350aab61aaa8a3f500a39b8..0000000000000000000000000000000000000000
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/exp/parameterize_top_lev_exponential.hpp
+++ /dev/null
@@ -1,130 +0,0 @@
-/** @file feature_creation/node/operator_nodes/allowed_operator_nodes/exp/parameterize_top_levexponetial.hpp
- *  @brief Class describing the parameterized exp operator
- *
- *  This class represents the parameterized unary operator -> exp(alpha * A + a)
- *
- *  @author Thomas A. R. Purcell (tpurcell)
- *  @bug No known bugs.
- */
-#ifndef PARAM_TOP_LEV_EXP_NODE
-#define PARAM_TOP_LEV_EXP_NODE
-
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/exp/parameterized_exponential.hpp>
-#include <nl_opt/NLOptWrapper.hpp>
-
-// DocString: cls_abs_node
-/**
- * @brief ParamNode for the absolute value operator
- *
- */
-class ExpParamTopLevNode: public ExpParamNode
-{
-    using ExpParamNode::set_value;
-    using ExpParamNode::set_test_value;
-    using ExpParamNode::value_ptr;
-    using ExpParamNode::test_value_ptr;
-    using ExpParamNode::domain;
-    using ExpParamNode::expr;
-
-    friend class boost::serialization::access;
-
-    /**
-     * @brief Serialization function to send over MPI
-     *
-     * @param ar Archive representation of node
-     */
-    template <typename Archive>
-    void serialize(Archive& ar, const unsigned int version)
-    {
-        ar & boost::serialization::base_object<ExpParamNode>(*this);
-    }
-
-public:
-    /**
-     * @brief Base Constructor
-     * @details This is only used for serialization
-     */
-    ExpParamTopLevNode();
-
-    /**
-     * @brief Constructor
-     * @details Constructs the ParamNode from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_ind Index of the new feature
-     * @param l_bound Minimum absolute value allowed for the feature.
-     * @param u_bound Maximum absolute value allowed for the feature.
-     * @param prop The property to fit to
-     */
-    ExpParamTopLevNode(node_ptr feat, int feat_ind, double l_bound, double u_bound, std::vector<double>& prop);
-
-    /**
-     * @brief Constructor
-     * @details Constructs the ParamNode from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_ind Index of the new feature
-     * @param prop The property to fit to
-     */
-    ExpParamTopLevNode(node_ptr feat, int feat_ind, std::vector<double>& prop);
-
-    /**
-     * @brief Constructor
-     * @details Constructs the ParamNode from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_ind Index of the new feature
-     * @param l_bound Minimum absolute value allowed for the feature.
-     * @param u_bound Maximum absolute value allowed for the feature.
-     * @param param_list The list of parameters to optimize using non-linear least squares
-     */
-    ExpParamTopLevNode(node_ptr feat, int feat_ind, double l_bound=1e-50, double u_bound=1e50);
-
-    // DocString: exp_param_top_lev_node_set_value
-    /**
-     * @brief Set the values of the training data for the feature inside of the value storage arrays
-     *
-     * @param offset(int) Key to determine which part of the temporary storage array to look into
-     */
-    void set_value(int offset = -1);
-
-    // DocString: exp_param_top_lev_node_set_test_value
-    /**
-     * @brief Set the values of the test data for the feature inside of the value storage arrays
-     *
-     * @param offset(int) Key to determine which part of the temporary storage array to look into
-     */
-    void set_test_value(int offset = -1);
-
-    // DocString: exp_param_top_lev_node_n_params
-    /**
-     * @brief returns the number of theoretical parameters for this feature
-     * @return the number of theoretical parameters
-     */
-    inline int n_params(int n_cur = 0){return 2;}
-
-    // DocString: exp_param_top_lev_node_expr
-    /**
-     * @brief Get the expression for the overall feature (From root node down)
-     */
-    inline std::string expr(){return "exp(" + std::to_string(_params[0]) + "*" + _feats[0]->expr() + " + " + std::to_string(_params[1]) + ")";}
-
-    // DocString: exp_param_top_lev_node_domain
-    /**
-     * @brief The domain for the feature (min/max values)
-     */
-    inline Domain domain(){return _feats[0]->domain().exp(_params[0], _params[1]);}
-
-    // DocString: exp_param_top_lev_node_set_bounds
-    /**
-     * @brief Set the bounds for the nl parameterization
-     *
-     * @param lb pointer to the lower bounds data
-     * @param ub pointer to the upper bounds data
-     */
-    void set_bounds(double* lb, double* ub, int from_parent=2);
-};
-
-void generateExpParamTopLevNode(std::vector<node_ptr>& feat_list, node_ptr feat, int& feat_ind, double l_bound, double u_bound, std::vector<double>& prop);
-
-#endif
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/exp/parameterized_exponential.cpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/exp/parameterized_exponential.cpp
index 7158781048b65bb06c5515589e4d8d4fa601c57c..0b1acc5b5f2e7548762bd9b4f6e88cf54aa6c741 100644
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/exp/parameterized_exponential.cpp
+++ b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/exp/parameterized_exponential.cpp
@@ -56,26 +56,33 @@ void ExpParamNode::get_parameters(std::vector<double>& prop)
     double min_res = nlopt_wrapper::optimize_feature_params(d);
 }
 
-void ExpNode::set_value(const double* params, int offset)
+void ExpNode::set_value(const double* params, int offset, int depth)
 {
     offset = (offset == -1) ? rung() : offset;
+    double* vp_0 = (depth < nlopt_wrapper::_max_param_depth) ? _feats[0]->value_ptr(params + 2, offset + 2, depth + 1) : _feats[0]->value_ptr(offset + 2);
+
     if(_selected)
         allowed_op_funcs::exp(_n_samp, _feats[0]->value_ptr(params + 2, offset + 2), params[0], params[1], node_value_arrs::get_d_matrix_ptr(_d_mat_ind));
 
     allowed_op_funcs::exp(_n_samp, _feats[0]->value_ptr(params + 2, offset + 2), params[0], params[1], node_value_arrs::get_value_ptr(_arr_ind, _feat_ind, offset, false));
 }
-void ExpNode::set_test_value(const double* params, int offset)
+void ExpNode::set_test_value(const double* params, int offset, int depth)
 {
     offset = (offset == -1) ? rung() : offset;
+    double* vp_0 = (depth < nlopt_wrapper::_max_param_depth) ? _feats[0]->test_value_ptr(params + 2, offset + 2, depth + 1) : _feats[0]->test_value_ptr(offset + 2);
+
     allowed_op_funcs::exp(_n_test_samp, _feats[0]->test_value_ptr(params + 2, offset + 2), params[0], params[1], node_value_arrs::get_test_value_ptr(_arr_ind, _feat_ind, offset, false));
 }
 
-void ExpNode::set_bounds(double* lb, double* ub, int from_parent)
+void ExpNode::set_bounds(double* lb, double* ub, int from_parent, int depth)
 {
     lb[0] = 0.0;
 
     *(lb - from_parent) = 1.0;
     *(ub - from_parent) = 1.0;
 
-    _feats[0]->set_bounds(lb + 2, ub + 2);
+    if(depth >= nlopt_wrapper::_max_param_depth)
+        return;
+
+    _feats[0]->set_bounds(lb + 2, ub + 2, depth + 1);
 }
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/inv/inverse.hpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/inv/inverse.hpp
index 50ad2620b79e43fb698e0e5b42c3a60d2ab46066..d61a1811b59d2661590109a76a7935a29e239d52 100644
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/inv/inverse.hpp
+++ b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/inv/inverse.hpp
@@ -1,7 +1,16 @@
+/** @file feature_creation/node/operator_nodes/allowed_operator_nodes/inverse.hpp
+ *  @brief Class describing the logarithm operator
+ *
+ *  This class represents the unary operator -> 1 / A
+ *
+ *  @author Thomas A. R. Purcell (tpurcell)
+ *  @bug No known bugs.
+ */
 #ifndef INV_NODE
 #define INV_NODE
 
 #include <feature_creation/node/operator_nodes/OperatorNode.hpp>
+#include <fmt/core.h>
 
 // DocString: cls_inv_node
 /**
@@ -55,7 +64,13 @@ public:
     /**
      * @brief Get the expression for the overall feature (From root node down)
      */
-    inline std::string expr(){return "1.0 / (" + _feats[0]->expr() + ")";}
+    inline std::string expr()
+    {
+        return fmt::format(
+            "1.0 / [{}]",
+            _feats[0]->expr()
+        );
+    }
 
     // DocString: inv_node_set_value
     /**
@@ -142,7 +157,7 @@ public:
          * @param offset(int) Key to determine which part of the temporary storage array to look into
          * @param params pointer to the parameter values
          */
-        void set_value(const double* params, int offset = -1);
+        void set_value(const double* params, int offset=-1, int depth=1);
 
         /**
          * @brief Set the values of the test data for the feature inside of the value storage arrays
@@ -150,7 +165,7 @@ public:
          * @param offset(int) Key to determine which part of the temporary storage array to look into
          * @param params pointer to the parameter values
          */
-        void set_test_value(const double* params, int offset = -1);
+        void set_test_value(const double* params, int offset=-1, int depth=1);
 
         /**
          * @brief The domain for the feature (min/max values)
@@ -158,7 +173,14 @@ public:
          * @param params parameter values for non-linear operations
          * @return the domain
          */
-        inline Domain domain(double* params){return _feats[0]->domain(params + 2).inv(params[0], params[1]);}
+        inline Domain domain(double* params, int depth=1)
+        {
+            if(depth < nlopt_wrapper::_max_param_depth)
+                return _feats[0]->domain(params + 2, depth + 1).inv(params[0], params[1]);
+            else
+                return _feats[0]->domain().inv(params[0], params[1]);
+        }
+        // inline Domain domain(double* params){return _feats[0]->domain(params + 2).inv(params[0], params[1]);}
 
         /**
          * @brief The expression of the feature
@@ -166,7 +188,16 @@ public:
          * @param params parameter values for non-linear operations
          * @return feature expression
          */
-        inline std::string expr(double* params){return "1.0 / (" + std::to_string(params[0]) + "*" + _feats[0]->expr(params + 2) + " + " + std::to_string(params[1]) + ")";}
+        inline std::string expr(double* params, int depth=1)
+        {
+            return fmt::format(
+                "1.0 / [{:.10e} * {} {:+15.10e}]",
+                params[0],
+                (depth < nlopt_wrapper::_max_param_depth ? _feats[0]->expr(params + 2, depth + 1) : _feats[0]->expr()),
+                params[1]
+            );
+        }
+        // inline std::string expr(double* params){return "1.0 / (" + std::to_string(params[0]) + "*" + _feats[0]->expr(params + 2) + " + " + std::to_string(params[1]) + ")";}
 
         /**
          * @brief Set the bounds for the nl parameterization
@@ -174,7 +205,7 @@ public:
          * @param lb pointer to the lower bounds data
          * @param ub pointer to the upper bounds data
          */
-        void set_bounds(double* lb, double* ub, int from_parent=2);
+        void set_bounds(double* lb, double* ub, int from_parent=2, int depth = 1);
 
         /**
          * @brief Calculates the derivative of an operation with respect to the parameters for a given sample
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/inv/parameterize_top_lev_inverse.cpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/inv/parameterize_top_lev_inverse.cpp
deleted file mode 100644
index 4e7f32c7eb0c226ae00083a186b80dabe67e28b1..0000000000000000000000000000000000000000
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/inv/parameterize_top_lev_inverse.cpp
+++ /dev/null
@@ -1,73 +0,0 @@
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/inv/parameterize_top_lev_inverse.hpp>
-
-void generateInvParamTopLevNode(std::vector<node_ptr>& feat_list, node_ptr feat, int& feat_ind, double l_bound, double u_bound, std::vector<double>& prop)
-{
-    ++feat_ind;
-
-    if((feat->type() == NODE_TYPE::DIV) || (feat->type() == NODE_TYPE::INV))
-        return;
-
-    node_ptr new_feat = std::make_shared<InvParamTopLevNode>(feat, feat_ind, prop);
-
-    if(new_feat->parameters()[0] * feat->domain() + new_feat->parameters()[1] <= 0.0)
-        return;
-
-    new_feat->set_value();
-    if(new_feat->is_nan() || new_feat->is_const() || (util_funcs::max_abs_val<double>(new_feat->value_ptr(), new_feat->n_samp()) > u_bound) || (util_funcs::max_abs_val<double>(new_feat->value_ptr(), new_feat->n_samp()) < l_bound))
-        return;
-
-    feat_list.push_back(new_feat);
-}
-
-InvParamTopLevNode::InvParamTopLevNode()
-{}
-
-InvParamTopLevNode::InvParamTopLevNode(node_ptr feat, int feat_ind, double l_bound, double u_bound, std::vector<double>& prop) :
-    InvParamNode(feat, feat_ind, l_bound, u_bound)
-{
-    if((feat->type() == NODE_TYPE::DIV) || (feat->type() == NODE_TYPE::INV))
-        throw InvalidFeatureException();
-
-    _params.resize(n_params(), 0.0);
-    get_parameters(prop);
-
-    if((_params[0] * _feats[0]->domain() + _params[1]).contains(0.0))
-        throw InvalidFeatureException();
-
-    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();
-}
-
-InvParamTopLevNode::InvParamTopLevNode(node_ptr feat, int feat_ind, std::vector<double>& prop) :
-    InvParamNode(feat, feat_ind)
-{
-    _params.resize(n_params(), 0.0);
-    get_parameters(prop);
-}
-
-InvParamTopLevNode::InvParamTopLevNode(node_ptr feat, int feat_ind, double l_bound, double u_bound) :
-    InvParamNode(feat, feat_ind, l_bound, u_bound)
-{
-    _params.resize(n_params(), 0.0);
-}
-
-void InvParamTopLevNode::set_value(int offset)
-{
-    offset = (offset == -1) ? rung() : offset;
-    if(_selected)
-        allowed_op_funcs::inv(_n_samp, _feats[0]->value_ptr(offset + 2), _params[0], _params[1], node_value_arrs::get_d_matrix_ptr(_d_mat_ind));
-
-    allowed_op_funcs::inv(_n_samp, _feats[0]->value_ptr(offset + 2), _params[0], _params[1], node_value_arrs::get_value_ptr(_arr_ind, _feat_ind, offset, false));
-}
-
-void InvParamTopLevNode::set_test_value(int offset)
-{
-    offset = (offset == -1) ? rung() : offset;
-    allowed_op_funcs::inv(_n_test_samp, _feats[0]->test_value_ptr(offset + 2), _params[0], _params[1], node_value_arrs::get_test_value_ptr(_arr_ind, _feat_ind, offset, false));
-}
-
-void InvParamTopLevNode::set_bounds(double* lb, double* ub, int from_parent)
-{
-    lb[0] = 1.0;
-    ub[0] = 1.0;
-}
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/inv/parameterize_top_lev_inverse.hpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/inv/parameterize_top_lev_inverse.hpp
deleted file mode 100644
index 9916ad7fd0d2792d015643002fb6bf080c33e978..0000000000000000000000000000000000000000
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/inv/parameterize_top_lev_inverse.hpp
+++ /dev/null
@@ -1,130 +0,0 @@
-/** @file feature_creation/node/operator_nodes/allowed_operator_nodes/inv/parameterize_top_levinverse.hpp
- *  @brief Class describing the parameterized inverse operator
- *
- *  This class represents the parameterized unary operator -> 1.0 / (alpha * A + a)
- *
- *  @author Thomas A. R. Purcell (tpurcell)
- *  @bug No known bugs.
- */
-#ifndef PARAM_TOP_LEV_INV_NODE
-#define PARAM_TOP_LEV_INV_NODE
-
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/inv/parameterized_inverse.hpp>
-#include <nl_opt/NLOptWrapper.hpp>
-
-// DocString: cls_abs_node
-/**
- * @brief ParamNode for the absolute value operator
- *
- */
-class InvParamTopLevNode: public InvParamNode
-{
-    using InvParamNode::set_value;
-    using InvParamNode::set_test_value;
-    using InvParamNode::value_ptr;
-    using InvParamNode::test_value_ptr;
-    using InvParamNode::domain;
-    using InvParamNode::expr;
-
-    friend class boost::serialization::access;
-
-    /**
-     * @brief Serialization function to send over MPI
-     *
-     * @param ar Archive representation of node
-     */
-    template <typename Archive>
-    void serialize(Archive& ar, const unsigned int version)
-    {
-        ar & boost::serialization::base_object<InvParamNode>(*this);
-    }
-
-public:
-    /**
-     * @brief Base Constructor
-     * @details This is only used for serialization
-     */
-    InvParamTopLevNode();
-
-    /**
-     * @brief Constructor
-     * @details Constructs the ParamNode from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_ind Index of the new feature
-     * @param l_bound Minimum absolute value allowed for the feature.
-     * @param u_bound Maximum absolute value allowed for the feature.
-     * @param prop The property to fit to
-     */
-    InvParamTopLevNode(node_ptr feat, int feat_ind, double l_bound, double u_bound, std::vector<double>& prop);
-
-    /**
-     * @brief Constructor
-     * @details Constructs the ParamNode from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_ind Index of the new feature
-     * @param prop The property to fit to
-     */
-    InvParamTopLevNode(node_ptr feat, int feat_ind, std::vector<double>& prop);
-
-    /**
-     * @brief Constructor
-     * @details Constructs the ParamNode from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_ind Index of the new feature
-     * @param l_bound Minimum absolute value allowed for the feature.
-     * @param u_bound Maximum absolute value allowed for the feature.
-     * @param param_list The list of parameters to optimize using non-linear least squares
-     */
-    InvParamTopLevNode(node_ptr feat, int feat_ind, double l_bound=1e-50, double u_bound=1e50);
-
-    // DocString: inv_param_top_lev_node_n_params
-    /**
-     * @brief returns the number of theoretical parameters for this feature
-     * @return the number of theoretical parameters
-     */
-    inline int n_params(int n_cur = 0){return 2;}
-
-    // DocString: inv_param_top_lev_node_set_value
-    /**
-     * @brief Set the values of the training data for the feature inside of the value storage arrays
-     *
-     * @param offset(int) Key to determine which part of the temporary storage array to look into
-     */
-    void set_value(int offset = -1);
-
-    // DocString: inv_param_top_lev_node_set_test_value
-    /**
-     * @brief Set the values of the test data for the feature inside of the value storage arrays
-     *
-     * @param offset(int) Key to determine which part of the temporary storage array to look into
-     */
-    void set_test_value(int offset = -1);
-
-    // DocString: inv_param_top_lev_node_expr
-    /**
-     * @brief Get the expression for the overall feature (From root node down)
-     */
-    inline std::string expr(){return "1.0 / (" + std::to_string(_params[0]) + "*" + _feats[0]->expr() + " + " + std::to_string(_params[1]) + ")";}
-
-    // DocString: inv_param_top_lev_node_domain
-    /**
-     * @brief The domain for the feature (min/max values)
-     */
-    inline Domain domain(){return _feats[0]->domain().inv(_params[0], _params[1]);}
-
-    // DocString: inv_param_top_lev_node_set_bounds
-    /**
-     * @brief Set the bounds for the nl parameterization
-     *
-     * @param lb pointer to the lower bounds data
-     * @param ub pointer to the upper bounds data
-     */
-    void set_bounds(double* lb, double* ub, int from_parent=2);
-};
-
-void generateInvParamTopLevNode(std::vector<node_ptr>& feat_list, node_ptr feat, int& feat_ind, double l_bound, double u_bound, std::vector<double>& prop);
-
-#endif
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/inv/parameterized_inverse.cpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/inv/parameterized_inverse.cpp
index a9dc1db93c0517b73862036841a5f78ca6a16808..318e4c7d7a450daea81663753d8c11b9ef6cae11 100644
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/inv/parameterized_inverse.cpp
+++ b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/inv/parameterized_inverse.cpp
@@ -62,25 +62,32 @@ void InvParamNode::get_parameters(std::vector<double>& prop)
     double min_res = nlopt_wrapper::optimize_feature_params(d, rung() > 1);
 }
 
-void InvNode::set_value(const double* params, int offset)
+void InvNode::set_value(const double* params, int offset, int depth)
 {
     offset = (offset == -1) ? rung() : offset;
+    double* vp_0 = (depth < nlopt_wrapper::_max_param_depth) ? _feats[0]->value_ptr(params + 2, offset + 2, depth + 1) : _feats[0]->value_ptr(offset + 2);
+
     if(_selected)
-        allowed_op_funcs::inv(_n_samp, _feats[0]->value_ptr(params + 2, offset + 2), params[0], params[1], node_value_arrs::get_d_matrix_ptr(_d_mat_ind));
+        allowed_op_funcs::inv(_n_samp, vp_0, params[0], params[1], node_value_arrs::get_d_matrix_ptr(_d_mat_ind));
 
-    allowed_op_funcs::inv(_n_samp, _feats[0]->value_ptr(params + 2, offset + 2), params[0], params[1], node_value_arrs::get_value_ptr(_arr_ind, _feat_ind, offset, false));
+    allowed_op_funcs::inv(_n_samp, vp_0, params[0], params[1], node_value_arrs::get_value_ptr(_arr_ind, _feat_ind, offset, false));
 }
 
-void InvNode::set_test_value(const double* params, int offset)
+void InvNode::set_test_value(const double* params, int offset, int depth)
 {
     offset = (offset == -1) ? rung() : offset;
-    allowed_op_funcs::inv(_n_test_samp, _feats[0]->test_value_ptr(params + 2, offset + 2), params[0], params[1], node_value_arrs::get_test_value_ptr(_arr_ind, _feat_ind, offset, false));
+    double* vp_0 = (depth < nlopt_wrapper::_max_param_depth) ? _feats[0]->test_value_ptr(params + 2, offset + 2, depth + 1) : _feats[0]->test_value_ptr(offset + 2);
+
+    allowed_op_funcs::inv(_n_test_samp, vp_0, params[0], params[1], node_value_arrs::get_test_value_ptr(_arr_ind, _feat_ind, offset, false));
 }
 
-void InvNode::set_bounds(double* lb, double* ub, int from_parent)
+void InvNode::set_bounds(double* lb, double* ub, int from_parent, int depth)
 {
     lb[0] = 1.0;
     ub[0] = 1.0;
 
-    _feats[0]->set_bounds(lb + 2, ub + 2);
+    if(depth >= nlopt_wrapper::_max_param_depth)
+        return;
+
+    _feats[0]->set_bounds(lb + 2, ub + 2, depth + 1);
 }
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/log/log.hpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/log/log.hpp
index 299c6fa3e3c049753db0c8d4c8ff4ffd2df92d80..17b7cafb850dbf3f72bdf3a20e19eaf5549de575 100644
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/log/log.hpp
+++ b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/log/log.hpp
@@ -10,6 +10,7 @@
 #define LOG_NODE
 
 #include <feature_creation/node/operator_nodes/OperatorNode.hpp>
+#include <fmt/core.h>
 
 // DocString: cls_log_node
 /**
@@ -67,7 +68,13 @@ public:
     /**
      * @brief Get the expression for the overall feature (From root node down)
      */
-    inline std::string expr(){return "log(" + _feats[0]->expr() + ")";}
+    inline std::string expr()
+    {
+        return fmt::format(
+            "ln[{}]",
+            _feats[0]->expr()
+        );
+    }
 
     // DocString: log_node_set_value
     /**
@@ -154,7 +161,7 @@ public:
          * @param offset(int) Key to determine which part of the temporary storage array to look into
          * @param params pointer to the parameter values
          */
-        void set_value(const double* params, int offset = -1);
+        void set_value(const double* params, int offset=-1, int depth=1);
 
         /**
          * @brief Set the values of the test data for the feature inside of the value storage arrays
@@ -162,7 +169,7 @@ public:
          * @param offset(int) Key to determine which part of the temporary storage array to look into
          * @param params pointer to the parameter values
          */
-        void set_test_value(const double* params, int offset = -1);
+        void set_test_value(const double* params, int offset=-1, int depth=1);
 
         /**
          * @brief The domain for the feature (min/max values)
@@ -170,7 +177,14 @@ public:
          * @param params parameter values for non-linear operations
          * @return the domain
          */
-        inline Domain domain(double* params){return _feats[0]->domain(params + 2).log(params[0], params[1]);}
+        inline Domain domain(double* params, int depth=1)
+        {
+            if(depth < nlopt_wrapper::_max_param_depth)
+                return _feats[0]->domain(params + 2, depth + 1).log(params[0], params[1]);
+            else
+                return _feats[0]->domain().log(params[0], params[1]);
+        }
+        // inline Domain domain(double* params){return _feats[0]->domain(params + 2).log(params[0], params[1]);}
 
         /**
          * @brief The expression of the feature
@@ -178,7 +192,16 @@ public:
          * @param params parameter values for non-linear operations
          * @return feature expression
          */
-        inline std::string expr(double* params){return "log(" + std::to_string(params[0]) + "*" + _feats[0]->expr(params + 2) + " + " + std::to_string(params[1]) + ")";}
+        inline std::string expr(double* params, int depth=1)
+        {
+            return fmt::format(
+                "ln[{:.10e} * {} {:+15.10e}]",
+                params[0],
+                (depth < nlopt_wrapper::_max_param_depth ? _feats[0]->expr(params + 2, depth + 1) : _feats[0]->expr()),
+                params[1]
+            );
+        }
+        // inline std::string expr(double* params){return "log(" + std::to_string(params[0]) + "*" + _feats[0]->expr(params + 2) + " + " + std::to_string(params[1]) + ")";}
 
         /**
          * @brief Set the bounds for the nl parameterization
@@ -186,7 +209,7 @@ public:
          * @param lb pointer to the lower bounds data
          * @param ub pointer to the upper bounds data
          */
-        void set_bounds(double* lb, double* ub, int from_parent=2);
+        void set_bounds(double* lb, double* ub, int from_parent=2, int depth = 1);
 
         /**
          * @brief Calculates the derivative of an operation with respect to the parameters for a given sample
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/log/parameterize_top_lev_log.cpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/log/parameterize_top_lev_log.cpp
deleted file mode 100644
index 95c620951e5d04e1b7438c469e55f2cead6e979e..0000000000000000000000000000000000000000
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/log/parameterize_top_lev_log.cpp
+++ /dev/null
@@ -1,73 +0,0 @@
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/log/parameterize_top_lev_log.hpp>
-
-
-void generateLogParamTopLevNode(std::vector<node_ptr>& feat_list, node_ptr feat, int& feat_ind, double l_bound, double u_bound, std::vector<double>& prop)
-{
-    ++feat_ind;
-
-    if((feat->type() == NODE_TYPE::NEG_EXP) || (feat->type() == NODE_TYPE::EXP) || (feat->type() == NODE_TYPE::DIV) || (feat->type() == NODE_TYPE::INV) || (feat->type() == NODE_TYPE::MULT) || (feat->type() == NODE_TYPE::LOG) || (feat->type() == NODE_TYPE::SIX_POW) || (feat->type() == NODE_TYPE::CB) || (feat->type() == NODE_TYPE::SQ) || (feat->type() == NODE_TYPE::CBRT) || (feat->type() == NODE_TYPE::SQRT))
-        return;
-
-    node_ptr new_feat = std::make_shared<LogParamTopLevNode>(feat, feat_ind, prop);
-    if(new_feat->parameters()[0] * feat->domain() + new_feat->parameters()[1] <= 0.0)
-        return;
-
-    new_feat->set_value();
-    if(new_feat->is_nan() || new_feat->is_const() || (util_funcs::max_abs_val<double>(new_feat->value_ptr(), new_feat->n_samp()) > u_bound) || (util_funcs::max_abs_val<double>(new_feat->value_ptr(), new_feat->n_samp()) < l_bound))
-        return;
-
-    feat_list.push_back(new_feat);
-}
-
-LogParamTopLevNode::LogParamTopLevNode()
-{}
-
-LogParamTopLevNode::LogParamTopLevNode(node_ptr feat, int feat_ind, double l_bound, double u_bound, std::vector<double>& prop) :
-    LogParamNode(feat, feat_ind, l_bound, u_bound)
-{
-    if((feat->type() == NODE_TYPE::NEG_EXP) || (feat->type() == NODE_TYPE::EXP) || (feat->type() == NODE_TYPE::DIV) || (feat->type() == NODE_TYPE::INV) || (feat->type() == NODE_TYPE::MULT) || (feat->type() == NODE_TYPE::LOG) || (feat->type() == NODE_TYPE::SIX_POW) || (feat->type() == NODE_TYPE::CB) || (feat->type() == NODE_TYPE::SQ) || (feat->type() == NODE_TYPE::CBRT) || (feat->type() == NODE_TYPE::SQRT))
-        throw InvalidFeatureException();
-
-    _params.resize(n_params(), 0.0);
-    get_parameters(prop);
-
-    if((_params[0] * _feats[0]->domain() + _params[1]) <= 0.0)
-        throw InvalidFeatureException();
-
-    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();
-}
-
-LogParamTopLevNode::LogParamTopLevNode(node_ptr feat, int feat_ind, std::vector<double>& prop) :
-    LogParamNode(feat, feat_ind)
-{
-    _params.resize(n_params(), 0.0);
-    get_parameters(prop);
-}
-
-LogParamTopLevNode::LogParamTopLevNode(node_ptr feat, int feat_ind, double l_bound, double u_bound) :
-    LogParamNode(feat, feat_ind, l_bound, u_bound)
-{
-    _params.resize(n_params(), 0.0);
-}
-
-void LogParamTopLevNode::set_value(int offset)
-{
-    offset = (offset == -1) ? rung() : offset;
-    if(_selected)
-        allowed_op_funcs::log(_n_samp, _feats[0]->value_ptr(offset + 2), _params[0], _params[1], node_value_arrs::get_d_matrix_ptr(_d_mat_ind));
-
-    allowed_op_funcs::log(_n_samp, _feats[0]->value_ptr(offset + 2), _params[0], _params[1], node_value_arrs::get_value_ptr(_arr_ind, _feat_ind, offset, false));
-}
-
-void LogParamTopLevNode::set_test_value(int offset)
-{
-    offset = (offset == -1) ? rung() : offset;
-    allowed_op_funcs::log(_n_test_samp, _feats[0]->test_value_ptr(offset + 2), _params[0], _params[1], node_value_arrs::get_test_value_ptr(_arr_ind, _feat_ind, offset, false));
-}
-
-void LogParamTopLevNode::set_bounds(double* lb, double* ub, int from_parent)
-{
-    *(lb - from_parent + 1) = 0.0;
-    *(ub - from_parent + 1) = 0.0;
-}
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/log/parameterize_top_lev_log.hpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/log/parameterize_top_lev_log.hpp
deleted file mode 100644
index 368671cdc148240a58418c0a71e619aea696970a..0000000000000000000000000000000000000000
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/log/parameterize_top_lev_log.hpp
+++ /dev/null
@@ -1,130 +0,0 @@
-/** @file feature_creation/node/operator_nodes/allowed_operator_nodes/log/parameterize_top_levlog.hpp
- *  @brief Class describing the parameterized log operator
- *
- *  This class represents the parameterized unary operator -> log(alpha * A + a)
- *
- *  @author Thomas A. R. Purcell (tpurcell)
- *  @bug No known bugs.
- */
-#ifndef PARAM_TOP_LEV_LOG_NODE
-#define PARAM_TOP_LEV_LOG_NODE
-
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/log/parameterized_log.hpp>
-#include <nl_opt/NLOptWrapper.hpp>
-
-// DocString: cls_abs_node
-/**
- * @brief ParamNode for the absolute value operator
- *
- */
-class LogParamTopLevNode: public LogParamNode
-{
-    using LogParamNode::set_value;
-    using LogParamNode::set_test_value;
-    using LogParamNode::value_ptr;
-    using LogParamNode::test_value_ptr;
-    using LogParamNode::domain;
-    using LogParamNode::expr;
-
-    friend class boost::serialization::access;
-
-    /**
-     * @brief Serialization function to send over MPI
-     *
-     * @param ar Archive representation of node
-     */
-    template <typename Archive>
-    void serialize(Archive& ar, const unsigned int version)
-    {
-        ar & boost::serialization::base_object<LogParamNode>(*this);
-    }
-
-public:
-    /**
-     * @brief Base Constructor
-     * @details This is only used for serialization
-     */
-    LogParamTopLevNode();
-
-    /**
-     * @brief Constructor
-     * @details Constructs the ParamNode from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_ind Index of the new feature
-     * @param l_bound Minimum absolute value allowed for the feature.
-     * @param u_bound Maximum absolute value allowed for the feature.
-     * @param prop The property to fit to
-     */
-    LogParamTopLevNode(node_ptr feat, int feat_ind, double l_bound, double u_bound, std::vector<double>& prop);
-
-    /**
-     * @brief Constructor
-     * @details Constructs the ParamNode from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_ind Index of the new feature
-     * @param prop The property to fit to
-     */
-    LogParamTopLevNode(node_ptr feat, int feat_ind, std::vector<double>& prop);
-
-    /**
-     * @brief Constructor
-     * @details Constructs the ParamNode from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_ind Index of the new feature
-     * @param l_bound Minimum absolute value allowed for the feature.
-     * @param u_bound Maximum absolute value allowed for the feature.
-     * @param param_list The list of parameters to optimize using non-linear least squares
-     */
-    LogParamTopLevNode(node_ptr feat, int feat_ind, double l_bound=1e-50, double u_bound=1e50);
-
-    // DocString: log_param_top_lev_node_n_params
-    /**
-     * @brief returns the number of theoretical parameters for this feature
-     * @return the number of theoretical parameters
-     */
-    inline int n_params(int n_cur = 0){return 2;}
-
-    // DocString: log_param_top_lev_node_set_value
-    /**
-     * @brief Set the values of the training data for the feature inside of the value storage arrays
-     *
-     * @param offset(int) Key to determine which part of the temporary storage array to look into
-     */
-    void set_value(int offset = -1);
-
-    // DocString: log_param_top_lev_node_set_test_value
-    /**
-     * @brief Set the values of the test data for the feature inside of the value storage arrays
-     *
-     * @param offset(int) Key to determine which part of the temporary storage array to look into
-     */
-    void set_test_value(int offset = -1);
-
-    // DocString: log_param_top_lev_node_expr
-    /**
-     * @brief Get the expression for the overall feature (From root node down)
-     */
-    inline std::string expr(){return "log(" + std::to_string(_params[0]) + "*" + _feats[0]->expr() + " + " + std::to_string(_params[1]) + ")";}
-
-    // DocString: log_param_top_lev_node_domain
-    /**
-     * @brief The domain for the feature (min/max values)
-     */
-    inline Domain domain(){return _feats[0]->domain().log(_params[0], _params[1]);}
-
-    // DocString: log_param_top_lev_node_set_bounds
-    /**
-     * @brief Set the bounds for the nl parameterization
-     *
-     * @param lb pointer to the lower bounds data
-     * @param ub pointer to the upper bounds data
-     */
-    void set_bounds(double* lb, double* ub, int from_parent=2);
-};
-
-void generateLogParamTopLevNode(std::vector<node_ptr>& feat_list, node_ptr feat, int& feat_ind, double l_bound, double u_bound, std::vector<double>& prop);
-
-#endif
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/log/parameterized_log.cpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/log/parameterized_log.cpp
index 7822ce70ac1d11207a6096fa80fe6ffe4a8c5cb1..cd5ecab35e11d67216a438d1127c121174a594c1 100644
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/log/parameterized_log.cpp
+++ b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/log/parameterized_log.cpp
@@ -61,25 +61,32 @@ void LogParamNode::get_parameters(std::vector<double>& prop)
     double min_res = nlopt_wrapper::optimize_feature_params(d);
 }
 
-void LogNode::set_value(const double* params, int offset)
+void LogNode::set_value(const double* params, int offset, int depth)
 {
     offset = (offset == -1) ? rung() : offset;
+    double* vp_0 = (depth < nlopt_wrapper::_max_param_depth) ? _feats[0]->value_ptr(params + 2, offset + 2, depth + 1) : _feats[0]->value_ptr(offset + 2);
+
     if(_selected)
         allowed_op_funcs::log(_n_samp, _feats[0]->value_ptr(params + 2, offset + 2), params[0], params[1], node_value_arrs::get_d_matrix_ptr(_d_mat_ind));
 
     allowed_op_funcs::log(_n_samp, _feats[0]->value_ptr(params + 2, offset + 2), params[0], params[1], node_value_arrs::get_value_ptr(_arr_ind, _feat_ind, offset, false));
 }
 
-void LogNode::set_test_value(const double* params, int offset)
+void LogNode::set_test_value(const double* params, int offset, int depth)
 {
     offset = (offset == -1) ? rung() : offset;
+    double* vp_0 = (depth < nlopt_wrapper::_max_param_depth) ? _feats[0]->test_value_ptr(params + 2, offset + 2, depth + 1) : _feats[0]->test_value_ptr(offset + 2);
+
     allowed_op_funcs::log(_n_test_samp, _feats[0]->test_value_ptr(params + 2, offset + 2), params[0], params[1], node_value_arrs::get_test_value_ptr(_arr_ind, _feat_ind, offset, false));
 }
 
-void LogNode::set_bounds(double* lb, double* ub, int from_parent)
+void LogNode::set_bounds(double* lb, double* ub, int from_parent, int depth)
 {
     *(lb - from_parent + 1) = 0.0;
     *(ub - from_parent + 1) = 0.0;
 
-    _feats[0]->set_bounds(lb + 2, ub + 2);
+    if(depth >= nlopt_wrapper::_max_param_depth)
+        return;
+
+    _feats[0]->set_bounds(lb + 2, ub + 2, depth + 1);
 }
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/mult/multiply.hpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/mult/multiply.hpp
index 7e1de2d4d493b88d3c0ff5457235c9e965826542..e3ed3b0c4607861c0215b1fff358470806a9a711 100644
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/mult/multiply.hpp
+++ b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/mult/multiply.hpp
@@ -10,6 +10,7 @@
 #define MULT_NODE
 
 #include <feature_creation/node/operator_nodes/OperatorNode.hpp>
+#include <fmt/core.h>
 
 // DocString: cls_multiplication_node
 /**
@@ -70,7 +71,15 @@ public:
     /**
      * @brief Get the expression for the overall feature (From root node down)
      */
-    inline std::string expr(){return "[(" + _feats[0]->expr() + ") * (" + _feats[1]->expr() + ")]";}
+    inline std::string expr()
+    {
+        return fmt::format(
+            "[({}) * ({})]",
+            _feats[0]->expr(),
+            _feats[1]->expr()
+        );
+    }
+    // inline std::string expr(){return "[(" + _feats[0]->expr() + ") * (" + _feats[1]->expr() + ")]";}
 
     // DocString: mult_node_set_value
     /**
@@ -163,7 +172,7 @@ public:
          * @param offset(int) Key to determine which part of the temporary storage array to look into
          * @param params pointer to the parameter values
          */
-        void set_value(const double* params, int offset = -1);
+        void set_value(const double* params, int offset=-1, int depth=1);
 
         /**
          * @brief Set the values of the test data for the feature inside of the value storage arrays
@@ -171,7 +180,7 @@ public:
          * @param offset(int) Key to determine which part of the temporary storage array to look into
          * @param params pointer to the parameter values
          */
-        void set_test_value(const double* params, int offset = -1);
+        void set_test_value(const double* params, int offset=-1, int depth=1);
 
         /**
          * @brief The domain for the feature (min/max values)
@@ -179,7 +188,14 @@ public:
          * @param params parameter values for non-linear operations
          * @return the domain
          */
-        inline Domain domain(double* params){return _feats[0]->domain(params + _feats[1]->n_params() + 2).mult(_feats[1]->domain(params + 2), params[0], params[1]);}
+        inline Domain domain(double* params, int depth=1)
+        {
+            if(depth < nlopt_wrapper::_max_param_depth)
+                return _feats[0]->domain(params + _feats[1]->n_params() + 2, depth + 1).mult(_feats[1]->domain(params + 2, depth + 1), params[0], params[1]);
+            else
+                return _feats[0]->domain().mult(_feats[1]->domain(), params[0], params[1]);
+        }
+        // inline Domain domain(double* params){return _feats[0]->domain(params + _feats[1]->n_params() + 2).mult(_feats[1]->domain(params + 2), params[0], params[1]);}
 
         /**
          * @brief The expression of the feature
@@ -187,7 +203,17 @@ public:
          * @param params parameter values for non-linear operations
          * @return feature expression
          */
-        inline std::string expr(double* params){return "[(" + _feats[0]->expr(params + _feats[1]->n_params() + 2) + ") * (" + std::to_string(params[0]) + "*" + _feats[1]->expr(params + 2) + " + " + std::to_string(params[1]) + ")]";}
+        inline std::string expr(double* params, int depth=1)
+        {
+            return fmt::format(
+                "[({}) * ({:.10e} * {} {:+15.10e})]",
+                (depth < nlopt_wrapper::_max_param_depth ? _feats[0]->expr(params + _feats[1]->n_params() + 2, depth + 1) : _feats[0]->expr()),
+                params[0],
+                (depth < nlopt_wrapper::_max_param_depth ? _feats[1]->expr(params + 2, depth + 1) : _feats[1]->expr()),
+                params[1]
+            );
+        }
+        // inline std::string expr(double* params){return "[(" + _feats[0]->expr(params + _feats[1]->n_params() + 2) + ") * (" + std::to_string(params[0]) + "*" + _feats[1]->expr(params + 2) + " + " + std::to_string(params[1]) + ")]";}
 
         /**
          * @brief Set the bounds for the nl parameterization
@@ -195,7 +221,7 @@ public:
          * @param lb pointer to the lower bounds data
          * @param ub pointer to the upper bounds data
          */
-        void set_bounds(double* lb, double* ub, int from_parent=2);
+        void set_bounds(double* lb, double* ub, int from_parent=2, int depth = 1);
 
         /**
          * @brief Calculates the derivative of an operation with respect to the parameters for a given sample
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/mult/parameterize_top_lev_multiply.cpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/mult/parameterize_top_lev_multiply.cpp
deleted file mode 100644
index fbd219c0ef4e954785eb8e3882247e64a018532d..0000000000000000000000000000000000000000
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/mult/parameterize_top_lev_multiply.cpp
+++ /dev/null
@@ -1,60 +0,0 @@
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/mult/parameterize_top_lev_multiply.hpp>
-
-void generateMultParamTopLevNode(std::vector<node_ptr>& feat_list, node_ptr feat_1, node_ptr feat_2, int& feat_ind, double l_bound, double u_bound, std::vector<double>& prop)
-{
-    ++feat_ind;
-    node_ptr new_feat = std::make_shared<MultParamTopLevNode>(feat_1, feat_2, feat_ind, prop);
-
-    new_feat->set_value();
-    if(new_feat->is_nan() || new_feat->is_const() || (util_funcs::max_abs_val<double>(new_feat->value_ptr(), new_feat->n_samp()) > u_bound) || (util_funcs::max_abs_val<double>(new_feat->value_ptr(), new_feat->n_samp()) < l_bound))
-        return;
-
-    feat_list.push_back(new_feat);
-}
-
-MultParamTopLevNode::MultParamTopLevNode()
-{}
-
-MultParamTopLevNode::MultParamTopLevNode(node_ptr feat_1, node_ptr feat_2, int feat_ind, double l_bound, double u_bound, std::vector<double>& prop):
-    MultParamNode(feat_1, feat_2, feat_ind, l_bound, u_bound)
-{
-    _params.resize(n_params(), 0.0);
-    get_parameters(prop);
-
-    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();
-}
-
-MultParamTopLevNode::MultParamTopLevNode(node_ptr feat_1, node_ptr feat_2, int feat_ind, double l_bound, double u_bound):
-    MultParamNode(feat_1, feat_2, feat_ind, l_bound, u_bound)
-{
-    _params.resize(n_params(), 0.0);
-}
-
-MultParamTopLevNode::MultParamTopLevNode(node_ptr feat_1, node_ptr feat_2, int feat_ind, std::vector<double>& prop):
-    MultParamNode(feat_1, feat_2, feat_ind)
-{
-    _params.resize(n_params(), 0.0);
-    get_parameters(prop);
-}
-
-void MultParamTopLevNode::set_value(int offset)
-{
-    offset = (offset == -1) ? rung() : offset;
-
-    if(_selected)
-        allowed_op_funcs::mult(_n_samp, _feats[0]->value_ptr(offset + 2), _feats[1]->value_ptr(offset + 1), _params[0], _params[1], node_value_arrs::get_d_matrix_ptr(_d_mat_ind));
-    allowed_op_funcs::mult(_n_samp, _feats[0]->value_ptr(offset + 2), _feats[1]->value_ptr(offset + 1), _params[0], _params[1], node_value_arrs::get_value_ptr(_arr_ind, _feat_ind, offset, false));
-}
-
-void MultParamTopLevNode::set_test_value(int offset)
-{
-    offset = (offset == -1) ? rung() : offset;
-    allowed_op_funcs::mult(_n_test_samp, _feats[0]->test_value_ptr(offset + 2), _feats[1]->test_value_ptr(offset + 1), _params[0], _params[1], node_value_arrs::get_test_value_ptr(_arr_ind, _feat_ind, offset, false));
-}
-
-void MultParamTopLevNode::set_bounds(double* lb, double* ub, int from_parent)
-{
-    lb[0] = 1.0;
-    ub[0] = 1.0;
-}
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/mult/parameterize_top_lev_multiply.hpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/mult/parameterize_top_lev_multiply.hpp
deleted file mode 100644
index 7e630725a27ebf76b1f9b02010c8c21c1713213b..0000000000000000000000000000000000000000
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/mult/parameterize_top_lev_multiply.hpp
+++ /dev/null
@@ -1,130 +0,0 @@
-/** @file feature_creation/node/operator_nodes/allowed_operator_nodes/div/parameterize_top_levmultiply.hpp
- *  @brief Class describing the parameterized addition operator
- *
- *  This class represents the parameterized unary operator -> A * (alpha * B + a)
- *
- *  @author Thomas A. R. Purcell (tpurcell)
- *  @bug No known bugs.
- */
-#ifndef PARAM_TOP_LEV_MULT_NODE
-#define PARAM_TOP_LEV_MULT_NODE
-
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/mult/parameterized_multiply.hpp>
-#include <nl_opt/NLOptWrapper.hpp>
-
-// DocString: cls_param_add_node
-/**
- * @brief ParamNode for the absolute value operator
- *
- */
-class MultParamTopLevNode: public MultParamNode
-{
-    using MultParamNode::set_value;
-    using MultParamNode::set_test_value;
-    using MultParamNode::value_ptr;
-    using MultParamNode::test_value_ptr;
-    using MultParamNode::domain;
-    using MultParamNode::expr;
-
-    friend class boost::serialization::access;
-
-    /**
-     * @brief Serialization function to send over MPI
-     *
-     * @param ar Archive representation of node
-     */
-    template <typename Archive>
-    void serialize(Archive& ar, const unsigned int version)
-    {
-        ar & boost::serialization::base_object<MultParamNode>(*this);
-    }
-
-public:
-    /**
-     * @brief Base Constructor
-     * @details This is only used for serialization
-     */
-    MultParamTopLevNode();
-
-    /**
-     * @brief Constructor
-     * @details Constructs the ParamNode from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_ind Index of the new feature
-     * @param l_bound Minimum absolute value allowed for the feature.
-     * @param u_bound Maximum absolute value allowed for the feature.
-     * @param prop The property to fit to
-     */
-    MultParamTopLevNode(node_ptr feat_1, node_ptr feat_2, int feat_ind, double l_bound, double u_bound, std::vector<double>& prop);
-
-    /**
-     * @brief Constructor
-     * @details Constructs the ParamNode from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_ind Index of the new feature
-     * @param prop The property to fit to
-     */
-    MultParamTopLevNode(node_ptr feat_1, node_ptr feat_2, int feat_ind, std::vector<double>& prop);
-
-    /**
-     * @brief Constructor
-     * @details Constructs the ParamNode from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_ind Index of the new feature
-     * @param l_bound Minimum absolute value allowed for the feature.
-     * @param u_bound Maximum absolute value allowed for the feature.
-     * @param param_list The list of parameters to optimize using non-linear least squares
-     */
-    MultParamTopLevNode(node_ptr feat_1, node_ptr feat_2, int feat_ind, double l_bound=1e-50, double u_bound=1e50);
-
-    // DocString: mult_param_top_lev_node_n_params
-    /**
-     * @brief returns the number of theoretical parameters for this feature
-     * @return the number of theoretical parameters
-     */
-    inline int n_params(int n_cur = 0){return 2;}
-
-    // DocString: mult_param_top_lev_node_set_value
-    /**
-     * @brief Set the values of the training data for the feature inside of the value storage arrays
-     *
-     * @param offset(int) Key to determine which part of the temporary storage array to look into
-     */
-    void set_value(int offset = -1);
-
-    // DocString: mult_param_top_lev_node_set_test_value
-    /**
-     * @brief Set the values of the test data for the feature inside of the value storage arrays
-     *
-     * @param offset(int) Key to determine which part of the temporary storage array to look into
-     */
-    void set_test_value(int offset = -1);
-
-    // DocString: mult_param_top_lev_node_expr
-    /**
-     * @brief Get the expression for the overall feature (From root node down)
-     */
-    inline std::string expr(){return "[(" + _feats[0]->expr() + ") * (" + std::to_string(_params[0]) + "*" + _feats[1]->expr() + " + " + std::to_string(_params[1]) + ")]";}
-
-    // DocString: mult_param_top_lev_node_domain
-    /**
-     * @brief The domain for the feature (min/max values)
-     */
-    inline Domain domain(){return _feats[0]->domain().mult(_feats[1]->domain(), _params[0], _params[1]);}
-
-    // DocString: add_param_top_lev_node_set_bounds
-    /**
-     * @brief Set the bounds for the nl parameterization
-     *
-     * @param lb pointer to the lower bounds data
-     * @param ub pointer to the upper bounds data
-     */
-    void set_bounds(double* lb, double* ub, int from_parent=2);
-};
-
-void generateMultParamTopLevNode(std::vector<node_ptr>& feat_list, node_ptr feat_1, node_ptr feat_2, int& feat_ind, double l_bound, double u_bound, std::vector<double>& prop);
-
-#endif
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/mult/parameterized_multiply.cpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/mult/parameterized_multiply.cpp
index d86d18146f3798c3b56c305c102c61359c021a4a..d13b9f894ecd69e621c8df096bbf5e3f31650ba9 100644
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/mult/parameterized_multiply.cpp
+++ b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/mult/parameterized_multiply.cpp
@@ -49,26 +49,37 @@ void MultParamNode::get_parameters(std::vector<double>& prop)
     double min_res = nlopt_wrapper::optimize_feature_params(d);
 }
 
-void MultNode::set_value(const double* params, int offset)
+void MultNode::set_value(const double* params, int offset, int depth)
 {
     offset = (offset == -1) ? rung() : offset;
 
+    double* vp_0 = (depth < nlopt_wrapper::_max_param_depth) ? _feats[0]->value_ptr(params + _feats[1]->n_params() + 2, offset + 2, depth + 1) : _feats[0]->value_ptr(offset + 2);
+    double* vp_1 = (depth < nlopt_wrapper::_max_param_depth) ? _feats[1]->value_ptr(params + 2, offset + 1, depth + 1) : _feats[1]->value_ptr(offset + 1);
+
     if(_selected)
-        allowed_op_funcs::mult(_n_samp, _feats[0]->value_ptr(params + 2 + _feats[1]->n_params(), offset + 2), _feats[1]->value_ptr(params + 2, offset + 1), params[0], params[1], node_value_arrs::get_d_matrix_ptr(_d_mat_ind));
-    allowed_op_funcs::mult(_n_samp, _feats[0]->value_ptr(params + 2 + _feats[1]->n_params(), offset + 2), _feats[1]->value_ptr(params + 2, offset + 1), params[0], params[1], node_value_arrs::get_value_ptr(_arr_ind, _feat_ind, offset, false));
+        allowed_op_funcs::mult(_n_samp, vp_0, vp_1, params[0], params[1], node_value_arrs::get_d_matrix_ptr(_d_mat_ind));
+
+    allowed_op_funcs::mult(_n_samp, vp_0, vp_1, params[0], params[1], node_value_arrs::get_value_ptr(_arr_ind, _feat_ind, offset, false));
 }
 
-void MultNode::set_test_value(const double* params, int offset)
+void MultNode::set_test_value(const double* params, int offset, int depth)
 {
     offset = (offset == -1) ? rung() : offset;
-    allowed_op_funcs::mult(_n_test_samp, _feats[0]->test_value_ptr(params + 2 + _feats[1]->n_params(), offset + 2), _feats[1]->test_value_ptr(params + 2, offset + 1), params[0], params[1], node_value_arrs::get_test_value_ptr(_arr_ind, _feat_ind, offset, false));
+
+    double* vp_0 = (depth < nlopt_wrapper::_max_param_depth) ? _feats[0]->test_value_ptr(params + _feats[1]->n_params() + 2, offset + 2, depth + 1) : _feats[0]->test_value_ptr(offset + 2);
+    double* vp_1 = (depth < nlopt_wrapper::_max_param_depth) ? _feats[1]->test_value_ptr(params + 2, offset + 1, depth + 1) : _feats[1]->test_value_ptr(offset + 1);
+
+    allowed_op_funcs::mult(_n_test_samp, vp_0, vp_1, params[0], params[1], node_value_arrs::get_test_value_ptr(_arr_ind, _feat_ind, offset, false));
 }
 
-void MultNode::set_bounds(double* lb, double* ub, int from_parent)
+void MultNode::set_bounds(double* lb, double* ub, int from_parent, int depth)
 {
     lb[0] = 1.0;
     ub[0] = 1.0;
 
-    _feats[0]->set_bounds(lb + 2 + _feats[1]->n_params(), ub + 2 + _feats[1]->n_params(), 2 + _feats[1]->n_params());
-    _feats[1]->set_bounds(lb + 2, ub + 2);
+    if(depth >= nlopt_wrapper::_max_param_depth)
+        return;
+
+    _feats[0]->set_bounds(lb + 2 + _feats[1]->n_params(), ub + 2 + _feats[1]->n_params(), 2 + _feats[1]->n_params(), depth + 1);
+    _feats[1]->set_bounds(lb + 2, ub + 2, depth + 1);
 }
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/neg_exp/negative_exponential.hpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/neg_exp/negative_exponential.hpp
index 36adac57f2c897ccf6b5f38fb2cad0afa0787882..09f2e92443f34fb53f1351ebe588a457f2d5b0f5 100644
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/neg_exp/negative_exponential.hpp
+++ b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/neg_exp/negative_exponential.hpp
@@ -10,6 +10,7 @@
 #define NEG_EXP_NODE
 
 #include <feature_creation/node/operator_nodes/OperatorNode.hpp>
+#include <fmt/core.h>
 
 // DocString: cls_neg_exp_node
 /**
@@ -68,7 +69,13 @@ public:
     /**
      * @brief Get the expression for the overall feature (From root node down)
      */
-    inline std::string expr(){return "exp[-1.0*(" + _feats[0]->expr() + ")]";}
+    inline std::string expr()
+    {
+        return fmt::format(
+            "exp[-1.0 * ({})]",
+            _feats[0]->expr()
+        );
+    }
 
     // DocString: neg_exp_node_set_value
     /**
@@ -155,7 +162,7 @@ public:
          * @param offset(int) Key to determine which part of the temporary storage array to look into
          * @param params pointer to the parameter values
          */
-        void set_value(const double* params, int offset = -1);
+        void set_value(const double* params, int offset=-1, int depth=1);
 
         /**
          * @brief Set the values of the test data for the feature inside of the value storage arrays
@@ -163,7 +170,7 @@ public:
          * @param offset(int) Key to determine which part of the temporary storage array to look into
          * @param params pointer to the parameter values
          */
-        void set_test_value(const double* params, int offset = -1);
+        void set_test_value(const double* params, int offset=-1, int depth=1);
 
         /**
          * @brief The domain for the feature (min/max values)
@@ -171,7 +178,14 @@ public:
          * @param params parameter values for non-linear operations
          * @return the domain
          */
-        inline Domain domain(double* params){return _feats[0]->domain(params + 2).exp(-1.0 * params[0], params[1]);}
+        inline Domain domain(double* params, int depth=1)
+        {
+            if(depth < nlopt_wrapper::_max_param_depth)
+                return _feats[0]->domain(params + 2, depth + 1).exp(-1.0 * params[0], params[1]);
+            else
+                return _feats[0]->domain().exp(-1.0 * params[0], params[1]);
+        }
+        // inline Domain domain(double* params){return _feats[0]->domain(params + 2).exp(-1.0 * params[0], params[1]);}
 
         /**
          * @brief The expression of the feature
@@ -179,7 +193,16 @@ public:
          * @param params parameter values for non-linear operations
          * @return feature expression
          */
-        inline std::string expr(double* params){return "exp[-1.0*(" + std::to_string(params[0]) + "*" + _feats[0]->expr(params + 2) + " + " + std::to_string(params[1]) + ")]";}
+        inline std::string expr(double* params, int depth=1)
+        {
+            return fmt::format(
+                "exp[-{:.10e} * {} {:+15.10e}]",
+                params[0],
+                (depth < nlopt_wrapper::_max_param_depth ? _feats[0]->expr(params + 2, depth + 1) : _feats[0]->expr()),
+                params[1]
+            );
+        }
+        // inline std::string expr(double* params){return "exp[-1.0*(" + std::to_string(params[0]) + "*" + _feats[0]->expr(params + 2) + " + " + std::to_string(params[1]) + ")]";}
 
         /**
          * @brief Set the bounds for the nl parameterization
@@ -187,7 +210,7 @@ public:
          * @param lb pointer to the lower bounds data
          * @param ub pointer to the upper bounds data
          */
-        void set_bounds(double* lb, double* ub, int from_parent=2);
+        void set_bounds(double* lb, double* ub, int from_parent=2, int depth=1);
 
         /**
          * @brief Calculates the derivative of an operation with respect to the parameters for a given sample
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/neg_exp/parameterize_top_lev_negative_exponential.cpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/neg_exp/parameterize_top_lev_negative_exponential.cpp
deleted file mode 100644
index 2f11c69c7bf82be132f588bdaf3b6a2113ef8eb4..0000000000000000000000000000000000000000
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/neg_exp/parameterize_top_lev_negative_exponential.cpp
+++ /dev/null
@@ -1,69 +0,0 @@
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/neg_exp/parameterize_top_lev_negative_exponential.hpp>
-
-void generateNegExpParamTopLevNode(std::vector<node_ptr>& feat_list, node_ptr feat, int& feat_ind, double l_bound, double u_bound, std::vector<double>& prop)
-{
-    ++feat_ind;
-
-    if((feat->type() == NODE_TYPE::NEG_EXP) || (feat->type() == NODE_TYPE::EXP) || (feat->type() == NODE_TYPE::ADD) || (feat->type() == NODE_TYPE::SUB) || (feat->type() == NODE_TYPE::LOG))
-        return;
-
-    node_ptr new_feat = std::make_shared<NegExpParamTopLevNode>(feat, feat_ind, prop);
-
-    new_feat->set_value();
-    if(new_feat->is_nan() || new_feat->is_const() || (util_funcs::max_abs_val<double>(new_feat->value_ptr(), new_feat->n_samp()) > u_bound) || (util_funcs::max_abs_val<double>(new_feat->value_ptr(), new_feat->n_samp()) < l_bound))
-        return;
-
-    feat_list.push_back(new_feat);
-}
-
-NegExpParamTopLevNode::NegExpParamTopLevNode()
-{}
-
-NegExpParamTopLevNode::NegExpParamTopLevNode(node_ptr feat, int feat_ind, double l_bound, double u_bound, std::vector<double>& prop) :
-    NegExpParamNode(feat, feat_ind, l_bound, u_bound)
-{
-    if((feat->type() == NODE_TYPE::NEG_EXP) || (feat->type() == NODE_TYPE::EXP) || (feat->type() == NODE_TYPE::ADD) || (feat->type() == NODE_TYPE::SUB) || (feat->type() == NODE_TYPE::LOG))
-        throw InvalidFeatureException();
-
-    _params.resize(n_params(),  0.0);
-    get_parameters(prop);
-
-    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();
-}
-
-NegExpParamTopLevNode::NegExpParamTopLevNode(node_ptr feat, int feat_ind, std::vector<double>& prop) :
-    NegExpParamNode(feat, feat_ind)
-{
-    _params.resize(n_params(),  0.0);
-    get_parameters(prop);
-}
-
-NegExpParamTopLevNode::NegExpParamTopLevNode(node_ptr feat, int feat_ind, double l_bound, double u_bound) :
-    NegExpParamNode(feat, feat_ind, l_bound, u_bound)
-{
-    _params.resize(n_params(),  0.0);
-}
-
-void NegExpParamTopLevNode::set_value(int offset)
-{
-    offset = (offset == -1) ? rung() : offset;
-    if(_selected)
-        allowed_op_funcs::neg_exp(_n_samp, _feats[0]->value_ptr(offset + 2), _params[0], _params[1], node_value_arrs::get_d_matrix_ptr(_d_mat_ind));
-
-    allowed_op_funcs::neg_exp(_n_samp, _feats[0]->value_ptr(offset + 2), _params[0], _params[1], node_value_arrs::get_value_ptr(_arr_ind, _feat_ind, offset, false));
-}
-
-void NegExpParamTopLevNode::set_test_value(int offset)
-{
-    offset = (offset == -1) ? rung() : offset;
-    allowed_op_funcs::neg_exp(_n_test_samp, _feats[0]->test_value_ptr(offset + 2), _params[0], _params[1], node_value_arrs::get_test_value_ptr(_arr_ind, _feat_ind, offset, false));
-}
-
-void NegExpParamTopLevNode::set_bounds(double* lb, double* ub, int from_parent)
-{
-    lb[0] = 0.0;
-
-    *(lb - from_parent) = 1.0;
-    *(ub - from_parent) = 1.0;
-}
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/neg_exp/parameterize_top_lev_negative_exponential.hpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/neg_exp/parameterize_top_lev_negative_exponential.hpp
deleted file mode 100644
index c33ecb0650ffd522ed1634158be91e824f28bf9a..0000000000000000000000000000000000000000
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/neg_exp/parameterize_top_lev_negative_exponential.hpp
+++ /dev/null
@@ -1,130 +0,0 @@
-/** @file feature_creation/node/operator_nodes/allowed_operator_nodes/neg_exp/parameterize_top_levnegative_exponetial.hpp
- *  @brief Class describing the parameterized neg_exp operator
- *
- *  This class represents the parameterized unary operator -> exp(-alpha * A + a)
- *
- *  @author Thomas A. R. Purcell (tpurcell)
- *  @bug No known bugs.
- */
-#ifndef PARAM_TOP_LEV_NEG_EXP_NODE
-#define PARAM_TOP_LEV_NEG_EXP_NODE
-
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/neg_exp/parameterized_negative_exponential.hpp>
-#include <nl_opt/NLOptWrapper.hpp>
-
-// DocString: cls_abs_node
-/**
- * @brief ParamNode for the absolute value operator
- *
- */
-class NegExpParamTopLevNode: public NegExpParamNode
-{
-    using NegExpParamNode::set_value;
-    using NegExpParamNode::set_test_value;
-    using NegExpParamNode::value_ptr;
-    using NegExpParamNode::test_value_ptr;
-    using NegExpParamNode::domain;
-    using NegExpParamNode::expr;
-
-    friend class boost::serialization::access;
-
-    /**
-     * @brief Serialization function to send over MPI
-     *
-     * @param ar Archive representation of node
-     */
-    template <typename Archive>
-    void serialize(Archive& ar, const unsigned int version)
-    {
-        ar & boost::serialization::base_object<NegExpParamNode>(*this);
-    }
-
-public:
-    /**
-     * @brief Base Constructor
-     * @details This is only used for serialization
-     */
-    NegExpParamTopLevNode();
-
-    /**
-     * @brief Constructor
-     * @details Constructs the ParamNode from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_ind Index of the new feature
-     * @param l_bound Minimum absolute value allowed for the feature.
-     * @param u_bound Maximum absolute value allowed for the feature.
-     * @param prop The property to fit to
-     */
-    NegExpParamTopLevNode(node_ptr feat, int feat_ind, double l_bound, double u_bound, std::vector<double>& prop);
-
-    /**
-     * @brief Constructor
-     * @details Constructs the ParamNode from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_ind Index of the new feature
-     * @param prop The property to fit to
-     */
-    NegExpParamTopLevNode(node_ptr feat, int feat_ind, std::vector<double>& prop);
-
-    /**
-     * @brief Constructor
-     * @details Constructs the ParamNode from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_ind Index of the new feature
-     * @param l_bound Minimum absolute value allowed for the feature.
-     * @param u_bound Maximum absolute value allowed for the feature.
-     * @param param_list The list of parameters to optimize using non-linear least squares
-     */
-    NegExpParamTopLevNode(node_ptr feat, int feat_ind, double l_bound=1e-50, double u_bound=1e50);
-
-    // DocString: neg_exp_param_top_lev_node_n_params
-    /**
-     * @brief returns the number of theoretical parameters for this feature
-     * @return the number of theoretical parameters
-     */
-    inline int n_params(int n_cur = 0){return 2;}
-
-    // DocString: neg_exp_param_top_lev_node_set_value
-    /**
-     * @brief Set the values of the training data for the feature inside of the value storage arrays
-     *
-     * @param offset(int) Key to determine which part of the temporary storage array to look into
-     */
-    void set_value(int offset = -1);
-
-    // DocString: neg_exp_param_top_lev_node_set_test_value
-    /**
-     * @brief Set the values of the test data for the feature inside of the value storage arrays
-     *
-     * @param offset(int) Key to determine which part of the temporary storage array to look into
-     */
-    void set_test_value(int offset = -1);
-
-    // DocString: neg_exp_param_top_lev_node_expr
-    /**
-     * @brief Get the expression for the overall feature (From root node down)
-     */
-    inline std::string expr(){return "exp[-1.0*(" + std::to_string(_params[0]) + "*" + _feats[0]->expr() + " + " + std::to_string(_params[1]) + ")]";}
-
-    // DocString: neg_exp_param_top_lev_node_domain
-    /**
-     * @brief The domain for the feature (min/max values)
-     */
-    inline Domain domain(){return _feats[0]->domain().exp(-1.0 * _params[0], _params[1]);}
-
-    // DocString: add_param_top_lev_node_set_bounds
-    /**
-     * @brief Set the bounds for the nl parameterization
-     *
-     * @param lb pointer to the lower bounds data
-     * @param ub pointer to the upper bounds data
-     */
-    void set_bounds(double* lb, double* ub, int from_parent=2);
-};
-
-void generateNegExpParamTopLevNode(std::vector<node_ptr>& feat_list, node_ptr feat, int& feat_ind, double l_bound, double u_bound, std::vector<double>& prop);
-
-#endif
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/neg_exp/parameterized_negative_exponential.cpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/neg_exp/parameterized_negative_exponential.cpp
index f5251c061d951ebf17151993673f36de3db9e1cf..82fe4e14f2e4446ba6f941aeecc636908f905835 100644
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/neg_exp/parameterized_negative_exponential.cpp
+++ b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/neg_exp/parameterized_negative_exponential.cpp
@@ -56,27 +56,34 @@ void NegExpParamNode::get_parameters(std::vector<double>& prop)
     double min_res = nlopt_wrapper::optimize_feature_params(d);
 }
 
-void NegExpNode::set_value(const double* params, int offset)
+void NegExpNode::set_value(const double* params, int offset, int depth)
 {
     offset = (offset == -1) ? rung() : offset;
+    double* vp_0 = (depth < nlopt_wrapper::_max_param_depth) ? _feats[0]->value_ptr(params + 2, offset + 2, depth + 1) : _feats[0]->value_ptr(offset + 2);
+
     if(_selected)
         allowed_op_funcs::neg_exp(_n_samp, _feats[0]->value_ptr(params + 2, offset + 2), params[0], params[1], node_value_arrs::get_d_matrix_ptr(_d_mat_ind));
 
     allowed_op_funcs::neg_exp(_n_samp, _feats[0]->value_ptr(params + 2, offset + 2), params[0], params[1], node_value_arrs::get_value_ptr(_arr_ind, _feat_ind, offset, false));
 }
 
-void NegExpNode::set_test_value(const double* params, int offset)
+void NegExpNode::set_test_value(const double* params, int offset, int depth)
 {
     offset = (offset == -1) ? rung() : offset;
+    double* vp_0 = (depth < nlopt_wrapper::_max_param_depth) ? _feats[0]->test_value_ptr(params + 2, offset + 2, depth + 1) : _feats[0]->test_value_ptr(offset + 2);
+
     allowed_op_funcs::neg_exp(_n_test_samp, _feats[0]->test_value_ptr(params + 2, offset + 2), params[0], params[1], node_value_arrs::get_test_value_ptr(_arr_ind, _feat_ind, offset, false));
 }
 
-void NegExpNode::set_bounds(double* lb, double* ub, int from_parent)
+void NegExpNode::set_bounds(double* lb, double* ub, int from_parent, int depth)
 {
     lb[0] = 0.0;
 
     *(lb - from_parent) = 1.0;
     *(ub - from_parent) = 1.0;
 
-    _feats[0]->set_bounds(lb + 2, ub + 2);
+    if(depth >= nlopt_wrapper::_max_param_depth)
+        return;
+
+    _feats[0]->set_bounds(lb + 2, ub + 2, depth + 1);
 }
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sin/parameterize_top_lev_sin.cpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sin/parameterize_top_lev_sin.cpp
deleted file mode 100644
index aa48bf560845872442a2c386f54fe0a19b11f873..0000000000000000000000000000000000000000
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sin/parameterize_top_lev_sin.cpp
+++ /dev/null
@@ -1,67 +0,0 @@
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/sin/parameterize_top_lev_sin.hpp>
-
-void generateSinParamTopLevNode(std::vector<node_ptr>& feat_list, node_ptr feat, int& feat_ind, double l_bound, double u_bound, std::vector<double>& prop)
-{
-    ++feat_ind;
-
-    if((feat->type() == NODE_TYPE::SIN) || (feat->type() == NODE_TYPE::COS))
-        return;
-
-    node_ptr new_feat = std::make_shared<SinParamTopLevNode>(feat, feat_ind, prop);
-
-    new_feat->set_value();
-    if(new_feat->is_nan() || new_feat->is_const() || (util_funcs::max_abs_val<double>(new_feat->value_ptr(), new_feat->n_samp()) > u_bound) || (util_funcs::max_abs_val<double>(new_feat->value_ptr(), new_feat->n_samp()) < l_bound))
-        return;
-
-    feat_list.push_back(new_feat);
-}
-
-SinParamTopLevNode::SinParamTopLevNode()
-{}
-
-SinParamTopLevNode::SinParamTopLevNode(node_ptr feat, int feat_ind, double l_bound, double u_bound, std::vector<double>& prop) :
-    SinParamNode(feat, feat_ind, l_bound, u_bound)
-{
-    if((feat->type() == NODE_TYPE::SIN) || (feat->type() == NODE_TYPE::COS))
-        throw InvalidFeatureException();
-
-    _params.resize(n_params(), 0.0);
-    get_parameters(prop);
-
-    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();
-}
-
-SinParamTopLevNode::SinParamTopLevNode(node_ptr feat, int feat_ind, std::vector<double>& prop) :
-    SinParamNode(feat, feat_ind)
-{
-    _params.resize(n_params(), 0.0);
-    get_parameters(prop);
-}
-
-SinParamTopLevNode::SinParamTopLevNode(node_ptr feat, int feat_ind, double l_bound, double u_bound) :
-    SinParamNode(feat, feat_ind, l_bound, u_bound)
-{
-    _params.resize(n_params(), 0.0);
-}
-
-void SinParamTopLevNode::set_value(int offset)
-{
-    offset = (offset == -1) ? rung() : offset;
-    if(_selected)
-        allowed_op_funcs::sin(_n_samp, _feats[0]->value_ptr(offset + 2), _params[0], _params[1], node_value_arrs::get_d_matrix_ptr(_d_mat_ind));
-
-    allowed_op_funcs::sin(_n_samp, _feats[0]->value_ptr(offset + 2), _params[0], _params[1], node_value_arrs::get_value_ptr(_arr_ind, _feat_ind, offset, false));
-}
-
-void SinParamTopLevNode::set_test_value(int offset)
-{
-    offset = (offset == -1) ? rung() : offset;
-    allowed_op_funcs::sin(_n_test_samp, _feats[0]->test_value_ptr(offset + 2), _params[0], _params[1], node_value_arrs::get_test_value_ptr(_arr_ind, _feat_ind, offset, false));
-}
-
-void SinParamTopLevNode::set_bounds(double* lb, double* ub, int from_parent)
-{
-    lb[1] = -1.0 * M_PI;
-    ub[1] = M_PI;
-}
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sin/parameterize_top_lev_sin.hpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sin/parameterize_top_lev_sin.hpp
deleted file mode 100644
index 4989ec8c84a20537ce1eb45290d3245485d72f10..0000000000000000000000000000000000000000
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sin/parameterize_top_lev_sin.hpp
+++ /dev/null
@@ -1,130 +0,0 @@
-/** @file feature_creation/node/operator_nodes/allowed_operator_nodes/sin/parameterize_top_levsin.hpp
- *  @brief Class describing the parameterized sin operator
- *
- *  This class represents the parameterized unary operator -> sin(alpha * A + a)
- *
- *  @author Thomas A. R. Purcell (tpurcell)
- *  @bug No known bugs.
- */
-#ifndef PARAM_TOP_LEV_SIN_NODE
-#define PARAM_TOP_LEV_SIN_NODE
-
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/sin/parameterized_sin.hpp>
-#include <nl_opt/NLOptWrapper.hpp>
-
-// DocString: cls_abs_node
-/**
- * @brief ParamNode for the absolute value operator
- *
- */
-class SinParamTopLevNode: public SinParamNode
-{
-    using SinParamNode::set_value;
-    using SinParamNode::set_test_value;
-    using SinParamNode::value_ptr;
-    using SinParamNode::test_value_ptr;
-    using SinParamNode::domain;
-    using SinParamNode::expr;
-
-    friend class boost::serialization::access;
-
-    /**
-     * @brief Serialization function to send over MPI
-     *
-     * @param ar Archive representation of node
-     */
-    template <typename Archive>
-    void serialize(Archive& ar, const unsigned int version)
-    {
-        ar & boost::serialization::base_object<SinParamNode>(*this);
-    }
-
-public:
-    /**
-     * @brief Base Constructor
-     * @details This is only used for serialization
-     */
-    SinParamTopLevNode();
-
-    /**
-     * @brief Constructor
-     * @details Constructs the ParamNode from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_ind Index of the new feature
-     * @param l_bound Minimum absolute value allowed for the feature.
-     * @param u_bound Maximum absolute value allowed for the feature.
-     * @param prop The property to fit to
-     */
-    SinParamTopLevNode(node_ptr feat, int feat_ind, double l_bound, double u_bound, std::vector<double>& prop);
-
-    /**
-     * @brief Constructor
-     * @details Constructs the ParamNode from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_ind Index of the new feature
-     * @param prop The property to fit to
-     */
-    SinParamTopLevNode(node_ptr feat, int feat_ind, std::vector<double>& prop);
-
-    /**
-     * @brief Constructor
-     * @details Constructs the ParamNode from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_ind Index of the new feature
-     * @param l_bound Minimum absolute value allowed for the feature.
-     * @param u_bound Maximum absolute value allowed for the feature.
-     * @param param_list The list of parameters to optimize using non-linear least squares
-     */
-    SinParamTopLevNode(node_ptr feat, int feat_ind, double l_bound=1e-50, double u_bound=1e50);
-
-    // DocString: sin_param_top_lev_node_n_params
-    /**
-     * @brief returns the number of theoretical parameters for this feature
-     * @return the number of theoretical parameters
-     */
-    inline int n_params(int n_cur = 0){return 2;}
-
-    // DocString: sin_param_top_lev_node_set_value
-    /**
-     * @brief Set the values of the training data for the feature inside of the value storage arrays
-     *
-     * @param offset(int) Key to determine which part of the temporary storage array to look into
-     */
-    void set_value(int offset = -1);
-
-    // DocString: sin_param_top_lev_node_set_test_value
-    /**
-     * @brief Set the values of the test data for the feature inside of the value storage arrays
-     *
-     * @param offset(int) Key to determine which part of the temporary storage array to look into
-     */
-    void set_test_value(int offset = -1);
-
-    // DocString: sin_param_top_lev_node_expr
-    /**
-     * @brief Get the expression for the overall feature (From root node down)
-     */
-    inline std::string expr(){return "sin(" + std::to_string(_params[0]) + "*" + _feats[0]->expr() + " + " + std::to_string(_params[1]) + ")";}
-
-    // DocString: sin_param_top_lev_node_domain
-    /**
-     * @brief The domain for the feature (min/max values)
-     */
-    inline Domain domain(){return Domain(-1.0, 1.0);}
-
-    // DocString: add_param_top_lev_node_set_bounds
-    /**
-     * @brief Set the bounds for the nl parameterization
-     *
-     * @param lb pointer to the lower bounds data
-     * @param ub pointer to the upper bounds data
-     */
-    void set_bounds(double* lb, double* ub, int from_parent=2);
-};
-
-void generateSinParamTopLevNode(std::vector<node_ptr>& feat_list, node_ptr feat, int& feat_ind, double l_bound, double u_bound, std::vector<double>& prop);
-
-#endif
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sin/parameterized_sin.cpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sin/parameterized_sin.cpp
index b0a006ec8bfd15e0994b4e139ebb725661dc6c26..2ea0e6adfa10914697cdc316f051ef9e40029a60 100644
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sin/parameterized_sin.cpp
+++ b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sin/parameterized_sin.cpp
@@ -56,25 +56,32 @@ void SinParamNode::get_parameters(std::vector<double>& prop)
     double min_res = nlopt_wrapper::optimize_feature_params(d);
 }
 
-void SinNode::set_value(const double* params, int offset)
+void SinNode::set_value(const double* params, int offset, int depth)
 {
     offset = (offset == -1) ? rung() : offset;
+    double* vp_0 = (depth < nlopt_wrapper::_max_param_depth) ? _feats[0]->value_ptr(params + 2, offset + 2, depth + 1) : _feats[0]->value_ptr(offset + 2);
+
     if(_selected)
         allowed_op_funcs::sin(_n_samp, _feats[0]->value_ptr(params + 2, offset + 2), params[0], params[1], node_value_arrs::get_d_matrix_ptr(_d_mat_ind));
 
     allowed_op_funcs::sin(_n_samp, _feats[0]->value_ptr(params + 2, offset + 2), params[0], params[1], node_value_arrs::get_value_ptr(_arr_ind, _feat_ind, offset, false));
 }
 
-void SinNode::set_test_value(const double* params, int offset)
+void SinNode::set_test_value(const double* params, int offset, int depth)
 {
     offset = (offset == -1) ? rung() : offset;
+    double* vp_0 = (depth < nlopt_wrapper::_max_param_depth) ? _feats[0]->test_value_ptr(params + 2, offset + 2, depth + 1) : _feats[0]->test_value_ptr(offset + 2);
+
     allowed_op_funcs::sin(_n_test_samp, _feats[0]->test_value_ptr(params + 2, offset + 2), params[0], params[1], node_value_arrs::get_test_value_ptr(_arr_ind, _feat_ind, offset, false));
 }
 
-void SinNode::set_bounds(double* lb, double* ub, int from_parent)
+void SinNode::set_bounds(double* lb, double* ub, int from_parent, int depth)
 {
     lb[1] = -1.0 * M_PI;
     ub[1] = M_PI;
 
-    _feats[0]->set_bounds(lb + 2, ub + 2);
+    if(depth >= nlopt_wrapper::_max_param_depth)
+        return;
+
+    _feats[0]->set_bounds(lb + 2, ub + 2, depth + 1);
 }
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sin/sin.hpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sin/sin.hpp
index 7f1ab9abf4af1a7f2fd72a05e9c2341f27712b2b..b353fd9949730c756e380f15eb071df64bb97c71 100644
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sin/sin.hpp
+++ b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sin/sin.hpp
@@ -10,6 +10,7 @@
 #define SIN_NODE
 
 #include <feature_creation/node/operator_nodes/OperatorNode.hpp>
+#include <fmt/core.h>
 
 // DocString: cls_sin_node
 /**
@@ -68,7 +69,13 @@ public:
     /**
      * @brief Get the expression for the overall feature (From root node down)
      */
-    inline std::string expr(){return "sin(" + _feats[0]->expr() + ")";}
+    inline std::string expr()
+    {
+        return fmt::format(
+            "sin[{}]",
+            _feats[0]->expr()
+        );
+    }
 
     // DocString: sin_node_set_value
     /**
@@ -155,7 +162,7 @@ public:
          * @param offset(int) Key to determine which part of the temporary storage array to look into
          * @param params pointer to the parameter values
          */
-        void set_value(const double* params, int offset = -1);
+        void set_value(const double* params, int offset=-1, int depth=1);
 
         /**
          * @brief Set the values of the test data for the feature inside of the value storage arrays
@@ -163,7 +170,7 @@ public:
          * @param offset(int) Key to determine which part of the temporary storage array to look into
          * @param params pointer to the parameter values
          */
-        void set_test_value(const double* params, int offset = -1);
+        void set_test_value(const double* params, int offset=-1, int depth=1);
 
         /**
          * @brief The domain for the feature (min/max values)
@@ -171,7 +178,7 @@ public:
          * @param params parameter values for non-linear operations
          * @return the domain
          */
-        inline Domain domain(double* params){return Domain(-1.0, 1.0);}
+        inline Domain domain(double* params, int depth=1){return Domain(-1.0, 1.0);}
 
         /**
          * @brief The expression of the feature
@@ -179,7 +186,16 @@ public:
          * @param params parameter values for non-linear operations
          * @return feature expression
          */
-        inline std::string expr(double* params){return "sin(" + std::to_string(params[0]) + "*" + _feats[0]->expr(params + 2) + " + " + std::to_string(params[1]) + ")";}
+        inline std::string expr(double* params, int depth=1)
+        {
+            return fmt::format(
+                "sin[{:.10e} * {} {:+15.10e}]",
+                params[0],
+                (depth < nlopt_wrapper::_max_param_depth ? _feats[0]->expr(params + 2, depth + 1) : _feats[0]->expr()),
+                params[1]
+            );
+        }
+        // inline std::string expr(double* params){return "sin(" + std::to_string(params[0]) + "*" + _feats[0]->expr(params + 2) + " + " + std::to_string(params[1]) + ")";}
 
         /**
          * @brief Set the bounds for the nl parameterization
@@ -187,7 +203,7 @@ public:
          * @param lb pointer to the lower bounds data
          * @param ub pointer to the upper bounds data
          */
-        void set_bounds(double* lb, double* ub, int from_parent=2);
+        void set_bounds(double* lb, double* ub, int from_parent=2, int depth = 1);
 
         /**
          * @brief Calculates the derivative of an operation with respect to the parameters for a given sample
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/six_pow/parameterize_top_lev_sixth_power.cpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/six_pow/parameterize_top_lev_sixth_power.cpp
deleted file mode 100644
index 56f3fff8a99e465dcd81a891d1c380086f2349ab..0000000000000000000000000000000000000000
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/six_pow/parameterize_top_lev_sixth_power.cpp
+++ /dev/null
@@ -1,60 +0,0 @@
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/six_pow/parameterize_top_lev_sixth_power.hpp>
-
-void generateSixPowParamTopLevNode(std::vector<node_ptr>& feat_list, node_ptr feat, int& feat_ind, double l_bound, double u_bound, std::vector<double>& prop)
-{
-    ++feat_ind;
-    node_ptr new_feat = std::make_shared<SixPowParamTopLevNode>(feat, feat_ind, prop);
-
-    new_feat->set_value();
-    if(new_feat->is_nan() || new_feat->is_const() || (util_funcs::max_abs_val<double>(new_feat->value_ptr(), new_feat->n_samp()) > u_bound) || (util_funcs::max_abs_val<double>(new_feat->value_ptr(), new_feat->n_samp()) < l_bound))
-        return;
-
-    feat_list.push_back(new_feat);
-}
-
-SixPowParamTopLevNode::SixPowParamTopLevNode()
-{}
-
-SixPowParamTopLevNode::SixPowParamTopLevNode(node_ptr feat, int feat_ind, double l_bound, double u_bound, std::vector<double>& prop) :
-    SixPowParamNode(feat, feat_ind, l_bound, u_bound)
-{
-    _params.resize(n_params(), 0.0);
-    get_parameters(prop);
-
-    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();
-}
-
-SixPowParamTopLevNode::SixPowParamTopLevNode(node_ptr feat, int feat_ind, std::vector<double>& prop) :
-    SixPowParamNode(feat, feat_ind)
-{
-    _params.resize(n_params(), 0.0);
-    get_parameters(prop);
-}
-
-SixPowParamTopLevNode::SixPowParamTopLevNode(node_ptr feat, int feat_ind, double l_bound, double u_bound) :
-    SixPowParamNode(feat, feat_ind, l_bound, u_bound)
-{
-    _params.resize(n_params(), 0.0);
-}
-
-void SixPowParamTopLevNode::set_value(int offset)
-{
-    offset = (offset == -1) ? rung() : offset;
-    if(_selected)
-        allowed_op_funcs::sixth_pow(_n_samp, _feats[0]->value_ptr(offset + 2), _params[0], _params[1], node_value_arrs::get_d_matrix_ptr(_d_mat_ind));
-
-    allowed_op_funcs::sixth_pow(_n_samp, _feats[0]->value_ptr(offset + 2), _params[0], _params[1], node_value_arrs::get_value_ptr(_arr_ind, _feat_ind, offset, false));
-}
-
-void SixPowParamTopLevNode::set_test_value(int offset)
-{
-    offset = (offset == -1) ? rung() : offset;
-    allowed_op_funcs::sixth_pow(_n_test_samp, _feats[0]->test_value_ptr(offset + 2), _params[0], _params[1], node_value_arrs::get_test_value_ptr(_arr_ind, _feat_ind, offset, false));
-}
-
-void SixPowParamTopLevNode::set_bounds(double* lb, double* ub, int from_parent)
-{
-    lb[0] = 1.0;
-    ub[0] = 1.0;
-}
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/six_pow/parameterize_top_lev_sixth_power.hpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/six_pow/parameterize_top_lev_sixth_power.hpp
deleted file mode 100644
index b49b8ffabd460e6bf838c839136a0f7b054eb17c..0000000000000000000000000000000000000000
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/six_pow/parameterize_top_lev_sixth_power.hpp
+++ /dev/null
@@ -1,131 +0,0 @@
-/** @file feature_creation/node/operator_nodes/allowed_operator_nodes/six_pow/parameterize_top_levsixth_pow.hpp
- *  @brief Class describing the parameterized sixth power operator
- *
- *  This class represents the parameterized unary operator -> (alpha * A + a)^6
- *
- *  @author Thomas A. R. Purcell (tpurcell)
- *  @bug No known bugs.
- */
-#ifndef PARAM_TOP_LEV_SIX_POW_NODE
-#define PARAM_TOP_LEV_SIX_POW_NODE
-
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/six_pow/parameterized_sixth_power.hpp>
-#include <nl_opt/NLOptWrapper.hpp>
-
-// DocString: cls_abs_node
-/**
- * @brief ParamNode for the absolute value operator
- *
- */
-class SixPowParamTopLevNode: public SixPowParamNode
-{
-    using SixPowParamNode::set_value;
-    using SixPowParamNode::set_test_value;
-    using SixPowParamNode::value_ptr;
-    using SixPowParamNode::test_value_ptr;
-    using SixPowParamNode::domain;
-    using SixPowParamNode::expr;
-
-    friend class boost::serialization::access;
-
-    /**
-     * @brief Serialization function to send over MPI
-     *
-     * @param ar Archive representation of node
-     */
-    template <typename Archive>
-    void serialize(Archive& ar, const unsigned int version)
-    {
-        ar & boost::serialization::base_object<SixPowParamNode>(*this);
-    }
-
-public:
-    /**
-     * @brief Base Constructor
-     * @details This is only used for serialization
-     */
-    SixPowParamTopLevNode();
-
-    /**
-     * @brief Constructor
-     * @details Constructs the ParamNode from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_ind Index of the new feature
-     * @param l_bound Minimum absolute value allowed for the feature.
-     * @param u_bound Maximum absolute value allowed for the feature.
-     * @param param_list The list of parameters to optimize using non-linear least squares
-     * @param prop The property to fit to
-     */
-    SixPowParamTopLevNode(node_ptr feat, int feat_ind, double l_bound, double u_bound, std::vector<double>& prop);
-
-    /**
-     * @brief Constructor
-     * @details Constructs the ParamNode from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_ind Index of the new feature
-     * @param prop The property to fit to
-     */
-    SixPowParamTopLevNode(node_ptr feat, int feat_ind, std::vector<double>& prop);
-
-    /**
-     * @brief Constructor
-     * @details Constructs the ParamNode from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_ind Index of the new feature
-     * @param l_bound Minimum absolute value allowed for the feature.
-     * @param u_bound Maximum absolute value allowed for the feature.
-     * @param param_list The list of parameters to optimize using non-linear least squares
-     */
-    SixPowParamTopLevNode(node_ptr feat, int feat_ind, double l_bound=1e-50, double u_bound=1e50);
-
-    // DocString: six_pow_param_top_lev_node_n_params
-    /**
-     * @brief returns the number of theoretical parameters for this feature
-     * @return the number of theoretical parameters
-     */
-    inline int n_params(int n_cur = 0){return 2;}
-
-    // DocString: six_pow_param_top_lev_node_set_value
-    /**
-     * @brief Set the values of the training data for the feature inside of the value storage arrays
-     *
-     * @param offset(int) Key to determine which part of the temporary storage array to look into
-     */
-    void set_value(int offset = -1);
-
-    // DocString: six_pow_param_top_lev_node_set_test_value
-    /**
-     * @brief Set the values of the test data for the feature inside of the value storage arrays
-     *
-     * @param offset(int) Key to determine which part of the temporary storage array to look into
-     */
-    void set_test_value(int offset = -1);
-
-    // DocString: six_pow_param_top_lev_node_expr
-    /**
-     * @brief Get the expression for the overall feature (From root node down)
-     */
-    inline std::string expr(){return "(" + std::to_string(_params[0]) + "*" + _feats[0]->expr() + " + " + std::to_string(_params[1]) + ")^6";}
-
-    // DocString: six_pow_param_top_lev_node_domain
-    /**
-     * @brief The domain for the feature (min/max values)
-     */
-    inline Domain domain(){return _feats[0]->domain().pow(6.0, _params[0], _params[1]);}
-
-    // DocString: add_param_top_lev_node_set_bounds
-    /**
-     * @brief Set the bounds for the nl parameterization
-     *
-     * @param lb pointer to the lower bounds data
-     * @param ub pointer to the upper bounds data
-     */
-    void set_bounds(double* lb, double* ub, int from_parent=2);
-};
-
-void generateSixPowParamTopLevNode(std::vector<node_ptr>& feat_list, node_ptr feat, int& feat_ind, double l_bound, double u_bound, std::vector<double>& prop);
-
-#endif
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/six_pow/parameterized_sixth_power.cpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/six_pow/parameterized_sixth_power.cpp
index e650e8cf9a83db79d53e6a8905778e4db09407ab..3a8b891d3822ec5e3f464c3a621a4b4e613da83b 100644
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/six_pow/parameterized_sixth_power.cpp
+++ b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/six_pow/parameterized_sixth_power.cpp
@@ -49,25 +49,32 @@ void SixPowParamNode::get_parameters(std::vector<double>& prop)
     double min_res = nlopt_wrapper::optimize_feature_params(d);
 }
 
-void SixPowNode::set_value(const double* params, int offset)
+void SixPowNode::set_value(const double* params, int offset, int depth)
 {
     offset = (offset == -1) ? rung() : offset;
+    double* vp_0 = (depth < nlopt_wrapper::_max_param_depth) ? _feats[0]->value_ptr(params + 2, offset + 2, depth + 1) : _feats[0]->value_ptr(offset + 2);
+
     if(_selected)
         allowed_op_funcs::sixth_pow(_n_samp, _feats[0]->value_ptr(params + 2, offset + 2), params[0], params[1], node_value_arrs::get_d_matrix_ptr(_d_mat_ind));
 
     allowed_op_funcs::sixth_pow(_n_samp, _feats[0]->value_ptr(params + 2, offset + 2), params[0], params[1], node_value_arrs::get_value_ptr(_arr_ind, _feat_ind, offset, false));
 }
 
-void SixPowNode::set_test_value(const double* params, int offset)
+void SixPowNode::set_test_value(const double* params, int offset, int depth)
 {
     offset = (offset == -1) ? rung() : offset;
+    double* vp_0 = (depth < nlopt_wrapper::_max_param_depth) ? _feats[0]->test_value_ptr(params + 2, offset + 2, depth + 1) : _feats[0]->test_value_ptr(offset + 2);
+
     allowed_op_funcs::sixth_pow(_n_test_samp, _feats[0]->test_value_ptr(params + 2, offset + 2), params[0], params[1], node_value_arrs::get_test_value_ptr(_arr_ind, _feat_ind, offset, false));
 }
 
-void SixPowNode::set_bounds(double* lb, double* ub, int from_parent)
+void SixPowNode::set_bounds(double* lb, double* ub, int from_parent, int depth)
 {
     lb[0] = 1.0;
     ub[0] = 1.0;
 
-    _feats[0]->set_bounds(lb + 2, ub + 2);
+    if(depth >= nlopt_wrapper::_max_param_depth)
+        return;
+
+    _feats[0]->set_bounds(lb + 2, ub + 2, depth + 1);
 }
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/six_pow/sixth_power.hpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/six_pow/sixth_power.hpp
index a1994fadcb499c7470529f3d50045c3e45e833ba..576c0f5b17f590b6bf4191d28ba33afc0a9fc9df 100644
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/six_pow/sixth_power.hpp
+++ b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/six_pow/sixth_power.hpp
@@ -10,6 +10,7 @@
 #define SIXTH_POW_NODE
 
 #include <feature_creation/node/operator_nodes/OperatorNode.hpp>
+#include <fmt/core.h>
 
 // DocString: cls_six_pow_node
 /**
@@ -68,7 +69,13 @@ public:
     /**
      * @brief Get the expression for the overall feature (From root node down)
      */
-    inline std::string expr(){return "(" + _feats[0]->expr() + ")^6";}
+    inline std::string expr()
+    {
+        return fmt::format(
+            "[{}]^6",
+            _feats[0]->expr()
+        );
+    }
 
     // DocString: six_pow_node_set_value
     /**
@@ -155,7 +162,7 @@ public:
          * @param offset(int) Key to determine which part of the temporary storage array to look into
          * @param params pointer to the parameter values
          */
-        void set_value(const double* params, int offset = -1);
+        void set_value(const double* params, int offset=-1, int depth=1);
 
         /**
          * @brief Set the values of the test data for the feature inside of the value storage arrays
@@ -163,7 +170,7 @@ public:
          * @param offset(int) Key to determine which part of the temporary storage array to look into
          * @param params pointer to the parameter values
          */
-        void set_test_value(const double* params, int offset = -1);
+        void set_test_value(const double* params, int offset=-1, int depth=1);
 
         /**
          * @brief The domain for the feature (min/max values)
@@ -171,7 +178,14 @@ public:
          * @param params parameter values for non-linear operations
          * @return the domain
          */
-        inline Domain domain(double* params){return _feats[0]->domain(params + 2).pow(6.0, params[0], params[1]);}
+        inline Domain domain(double* params, int depth=1)
+        {
+            if(depth < nlopt_wrapper::_max_param_depth)
+                return _feats[0]->domain(params + 2, depth + 1).pow(6.0, params[0], params[1]);
+            else
+                return _feats[0]->domain().pow(6.0, params[0], params[1]);
+        }
+        // inline Domain domain(double* params){return _feats[0]->domain(params + 2).pow(6.0, params[0], params[1]);}
 
         /**
          * @brief The expression of the feature
@@ -179,7 +193,16 @@ public:
          * @param params parameter values for non-linear operations
          * @return feature expression
          */
-        inline std::string expr(double* params){return "(" + std::to_string(params[0]) + "*" + _feats[0]->expr(params + 2) + " + " + std::to_string(params[1]) + ")^6";}
+        inline std::string expr(double* params, int depth=1)
+        {
+            return fmt::format(
+                "[{:.10e} * {} {:+15.10e}]^6",
+                params[0],
+                (depth < nlopt_wrapper::_max_param_depth ? _feats[0]->expr(params + 2, depth + 1) : _feats[0]->expr()),
+                params[1]
+            );
+        }
+        // inline std::string expr(double* params){return "(" + std::to_string(params[0]) + "*" + _feats[0]->expr(params + 2) + " + " + std::to_string(params[1]) + ")^6";}
 
         /**
          * @brief Set the bounds for the nl parameterization
@@ -187,7 +210,7 @@ public:
          * @param lb pointer to the lower bounds data
          * @param ub pointer to the upper bounds data
          */
-        void set_bounds(double* lb, double* ub, int from_parent=2);
+        void set_bounds(double* lb, double* ub, int from_parent=2, int depth = 1);
 
         /**
          * @brief Calculates the derivative of an operation with respect to the parameters for a given sample
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sq/parameterize_top_lev_square.cpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sq/parameterize_top_lev_square.cpp
deleted file mode 100644
index 1aab76018ad2e5ada2eecb6269271b029eeb2fb5..0000000000000000000000000000000000000000
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sq/parameterize_top_lev_square.cpp
+++ /dev/null
@@ -1,60 +0,0 @@
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/sq/parameterize_top_lev_square.hpp>
-
-void generateSqParamTopLevNode(std::vector<node_ptr>& feat_list, node_ptr feat, int& feat_ind, double l_bound, double u_bound, std::vector<double>& prop)
-{
-    ++feat_ind;
-    node_ptr new_feat = std::make_shared<SqParamTopLevNode>(feat, feat_ind, prop);
-
-    new_feat->set_value();
-    if(new_feat->is_nan() || new_feat->is_const() || (util_funcs::max_abs_val<double>(new_feat->value_ptr(), new_feat->n_samp()) > u_bound) || (util_funcs::max_abs_val<double>(new_feat->value_ptr(), new_feat->n_samp()) < l_bound))
-        return;
-
-    feat_list.push_back(new_feat);
-}
-
-SqParamTopLevNode::SqParamTopLevNode()
-{}
-
-SqParamTopLevNode::SqParamTopLevNode(node_ptr feat, int feat_ind, double l_bound, double u_bound, std::vector<double>& prop) :
-    SqParamNode(feat, feat_ind, l_bound, u_bound)
-{
-    _params.resize(n_params(), 0.0);
-    get_parameters(prop);
-
-    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();
-}
-
-SqParamTopLevNode::SqParamTopLevNode(node_ptr feat, int feat_ind, std::vector<double>& prop) :
-    SqParamNode(feat, feat_ind)
-{
-    _params.resize(n_params(), 0.0);
-    get_parameters(prop);
-}
-
-SqParamTopLevNode::SqParamTopLevNode(node_ptr feat, int feat_ind, double l_bound, double u_bound) :
-    SqParamNode(feat, feat_ind, l_bound, u_bound)
-{
-    _params.resize(n_params(), 0.0);
-}
-
-void SqParamTopLevNode::set_value(int offset)
-{
-    offset = (offset == -1) ? rung() : offset;
-    if(_selected)
-        allowed_op_funcs::sq(_n_samp, _feats[0]->value_ptr(offset + 2), _params[0], _params[1], node_value_arrs::get_d_matrix_ptr(_d_mat_ind));
-
-    allowed_op_funcs::sq(_n_samp, _feats[0]->value_ptr(offset + 2), _params[0], _params[1], node_value_arrs::get_value_ptr(_arr_ind, _feat_ind, offset, false));
-}
-
-void SqParamTopLevNode::set_test_value(int offset)
-{
-    offset = (offset == -1) ? rung() : offset;
-    allowed_op_funcs::sq(_n_test_samp, _feats[0]->test_value_ptr(offset + 2), _params[0], _params[1], node_value_arrs::get_test_value_ptr(_arr_ind, _feat_ind, offset, false));
-}
-
-void SqParamTopLevNode::set_bounds(double* lb, double* ub, int from_parent)
-{
-    lb[0] = 1.0;
-    ub[0] = 1.0;
-}
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sq/parameterize_top_lev_square.hpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sq/parameterize_top_lev_square.hpp
deleted file mode 100644
index 5c2a9c094e498307cfaad78f379733b134e98b7a..0000000000000000000000000000000000000000
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sq/parameterize_top_lev_square.hpp
+++ /dev/null
@@ -1,130 +0,0 @@
-/** @file feature_creation/node/operator_nodes/allowed_operator_nodes/sq/parameterize_top_levsquare.hpp
- *  @brief Class describing the parameterized square operator
- *
- *  This class represents the parameterized unary operator -> (alpha * A + a)^2
- *
- *  @author Thomas A. R. Purcell (tpurcell)
- *  @bug No known bugs.
- */
-#ifndef PARAM_TOP_LEV_SQ_NODE
-#define PARAM_TOP_LEV_SQ_NODE
-
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/sq/parameterized_square.hpp>
-#include <nl_opt/NLOptWrapper.hpp>
-
-// DocString: cls_abs_node
-/**
- * @brief ParamNode for the absolute value operator
- *
- */
-class SqParamTopLevNode: public SqParamNode
-{
-    using SqParamNode::set_value;
-    using SqParamNode::set_test_value;
-    using SqParamNode::value_ptr;
-    using SqParamNode::test_value_ptr;
-    using SqParamNode::domain;
-    using SqParamNode::expr;
-
-    friend class boost::serialization::access;
-
-    /**
-     * @brief Serialization function to send over MPI
-     *
-     * @param ar Archive representation of node
-     */
-    template <typename Archive>
-    void serialize(Archive& ar, const unsigned int version)
-    {
-        ar & boost::serialization::base_object<SqParamNode>(*this);
-    }
-
-public:
-    /**
-     * @brief Base Constructor
-     * @details This is only used for serialization
-     */
-    SqParamTopLevNode();
-
-    /**
-     * @brief Constructor
-     * @details Constructs the ParamNode from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_ind Index of the new feature
-     * @param l_bound Minimum absolute value allowed for the feature.
-     * @param u_bound Maximum absolute value allowed for the feature.
-     * @param prop The property to fit to
-     */
-    SqParamTopLevNode(node_ptr feat, int feat_ind, double l_bound, double u_bound, std::vector<double>& prop);
-
-    /**
-     * @brief Constructor
-     * @details Constructs the ParamNode from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_ind Index of the new feature
-     * @param prop The property to fit to
-     */
-    SqParamTopLevNode(node_ptr feat, int feat_ind, std::vector<double>& prop);
-
-    /**
-     * @brief Constructor
-     * @details Constructs the ParamNode from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_ind Index of the new feature
-     * @param l_bound Minimum absolute value allowed for the feature.
-     * @param u_bound Maximum absolute value allowed for the feature.
-     * @param param_list The list of parameters to optimize using non-linear least squares
-     */
-    SqParamTopLevNode(node_ptr feat, int feat_ind, double l_bound=1e-50, double u_bound=1e50);
-
-    // DocString: sq_param_top_lev_node_n_params
-    /**
-     * @brief returns the number of theoretical parameters for this feature
-     * @return the number of theoretical parameters
-     */
-    inline int n_params(int n_cur = 0){return 2;}
-
-    // DocString: sq_param_top_lev_node_set_value
-    /**
-     * @brief Set the values of the training data for the feature inside of the value storage arrays
-     *
-     * @param offset(int) Key to determine which part of the temporary storage array to look into
-     */
-    void set_value(int offset = -1);
-
-    // DocString: sq_param_top_lev_node_set_test_value
-    /**
-     * @brief Set the values of the test data for the feature inside of the value storage arrays
-     *
-     * @param offset(int) Key to determine which part of the temporary storage array to look into
-     */
-    void set_test_value(int offset = -1);
-
-    // DocString: sq_param_top_lev_node_expr
-    /**
-     * @brief Get the expression for the overall feature (From root node down)
-     */
-    inline std::string expr(){return "sqrt(" + std::to_string(_params[0]) + "*" + _feats[0]->expr() + " + " + std::to_string(_params[1]) + ")";}
-
-    // DocString: sq_param_top_lev_node_domain
-    /**
-     * @brief The domain for the feature (min/max values)
-     */
-    inline Domain domain(){return _feats[0]->domain().pow(1.0 / 2.0, _params[0], _params[1]);}
-
-    // DocString: add_param_top_lev_node_set_bounds
-    /**
-     * @brief Set the bounds for the nl parameterization
-     *
-     * @param lb pointer to the lower bounds data
-     * @param ub pointer to the upper bounds data
-     */
-    void set_bounds(double* lb, double* ub, int from_parent=2);
-};
-
-void generateSqParamTopLevNode(std::vector<node_ptr>& feat_list, node_ptr feat, int& feat_ind, double l_bound, double u_bound, std::vector<double>& prop);
-
-#endif
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sq/parameterized_square.cpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sq/parameterized_square.cpp
index 6982914a23ce00eb4e3739212ecf82ca22b286c8..af206b16846b72032aaa6d81feaa18394cfb5c38 100644
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sq/parameterized_square.cpp
+++ b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sq/parameterized_square.cpp
@@ -50,25 +50,32 @@ void SqParamNode::get_parameters(std::vector<double>& prop)
     double min_res = nlopt_wrapper::optimize_feature_params(d);
 }
 
-void SqNode::set_value(const double* params, int offset)
+void SqNode::set_value(const double* params, int offset, int depth)
 {
     offset = (offset == -1) ? rung() : offset;
+    double* vp_0 = (depth < nlopt_wrapper::_max_param_depth) ? _feats[0]->value_ptr(params + 2, offset + 2, depth + 1) : _feats[0]->value_ptr(offset + 2);
+
     if(_selected)
-        allowed_op_funcs::sq(_n_samp, _feats[0]->value_ptr(params + 2, offset + 2), params[0], params[1], node_value_arrs::get_d_matrix_ptr(_d_mat_ind));
+        allowed_op_funcs::sq(_n_samp, vp_0, params[0], params[1], node_value_arrs::get_d_matrix_ptr(_d_mat_ind));
 
-    allowed_op_funcs::sq(_n_samp, _feats[0]->value_ptr(params + 2, offset + 2), params[0], params[1], node_value_arrs::get_value_ptr(_arr_ind, _feat_ind, offset, false));
+    allowed_op_funcs::sq(_n_samp, vp_0, params[0], params[1], node_value_arrs::get_value_ptr(_arr_ind, _feat_ind, offset, false));
 }
 
-void SqNode::set_test_value(const double* params, int offset)
+void SqNode::set_test_value(const double* params, int offset, int depth)
 {
     offset = (offset == -1) ? rung() : offset;
+    double* vp_0 = (depth < nlopt_wrapper::_max_param_depth) ? _feats[0]->test_value_ptr(params + 2, offset + 2, depth + 1) : _feats[0]->test_value_ptr(offset + 2);
+
     allowed_op_funcs::sq(_n_test_samp, _feats[0]->test_value_ptr(params + 2, offset + 2), params[0], params[1], node_value_arrs::get_test_value_ptr(_arr_ind, _feat_ind, offset, false));
 }
 
-void SqNode::set_bounds(double* lb, double* ub, int from_parent)
+void SqNode::set_bounds(double* lb, double* ub, int from_parent, int depth)
 {
     lb[0] = 1.0;
     ub[0] = 1.0;
 
-    _feats[0]->set_bounds(lb + 2, ub + 2);
+    if(depth >= nlopt_wrapper::_max_param_depth)
+        return;
+
+    _feats[0]->set_bounds(lb + 2, ub + 2, depth + 1);
 }
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sq/square.hpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sq/square.hpp
index ff79e1d709cd5627c9ad5f5e7bc801ae0c7b2184..241d34c450e2bb45d275424998204e446c87bd15 100644
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sq/square.hpp
+++ b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sq/square.hpp
@@ -10,6 +10,7 @@
 #define SQ_NODE
 
 #include <feature_creation/node/operator_nodes/OperatorNode.hpp>
+#include <fmt/core.h>
 
 // DocString: cls_sq_node
 /**
@@ -67,7 +68,13 @@ public:
     /**
      * @brief Get the expression for the overall feature (From root node down)
      */
-    inline std::string expr(){return "(" + _feats[0]->expr() + ")^2";}
+    inline std::string expr()
+    {
+        return fmt::format(
+            "[{}]^2",
+            _feats[0]->expr()
+        );
+    }
 
     // DocString: sq_node_set_value
     /**
@@ -154,7 +161,7 @@ public:
          * @param offset(int) Key to determine which part of the temporary storage array to look into
          * @param params pointer to the parameter values
          */
-        void set_value(const double* params, int offset = -1);
+        void set_value(const double* params, int offset=-1, int depth=1);
 
         /**
          * @brief Set the values of the test data for the feature inside of the value storage arrays
@@ -162,7 +169,7 @@ public:
          * @param offset(int) Key to determine which part of the temporary storage array to look into
          * @param params pointer to the parameter values
          */
-        void set_test_value(const double* params, int offset = -1);
+        void set_test_value(const double* params, int offset=-1, int depth=1);
 
         /**
          * @brief The domain for the feature (min/max values)
@@ -170,7 +177,14 @@ public:
          * @param params parameter values for non-linear operations
          * @return the domain
          */
-        inline Domain domain(double* params){return _feats[0]->domain(params + 2).pow(2.0, params[0], params[1]);}
+        inline Domain domain(double* params, int depth=1)
+        {
+            if(depth < nlopt_wrapper::_max_param_depth)
+                return _feats[0]->domain(params + 2, depth + 1).pow(2.0, params[0], params[1]);
+            else
+                return _feats[0]->domain().pow(2.0, params[0], params[1]);
+        }
+        // inline Domain domain(double* params){return _feats[0]->domain(params + 2).pow(2.0, params[0], params[1]);}
 
         /**
          * @brief The expression of the feature
@@ -178,7 +192,16 @@ public:
          * @param params parameter values for non-linear operations
          * @return feature expression
          */
-        inline std::string expr(double* params){return "(" + std::to_string(params[0]) + "*" + _feats[0]->expr(params + 2) + " + " + std::to_string(params[1]) + ")^2";}
+        inline std::string expr(double* params, int depth=1)
+        {
+            return fmt::format(
+                "[{:.10e} * {} {:+15.10e}]^2",
+                params[0],
+                (depth < nlopt_wrapper::_max_param_depth ? _feats[0]->expr(params + 2, depth + 1) : _feats[0]->expr()),
+                params[1]
+            );
+        }
+        // inline std::string expr(double* params){return "(" + std::to_string(params[0]) + "*" + _feats[0]->expr(params + 2) + " + " + std::to_string(params[1]) + ")^2";}
 
         /**
          * @brief Set the bounds for the nl parameterization
@@ -186,7 +209,7 @@ public:
          * @param lb pointer to the lower bounds data
          * @param ub pointer to the upper bounds data
          */
-        void set_bounds(double* lb, double* ub, int from_parent=2);
+        void set_bounds(double* lb, double* ub, int from_parent=2, int depth = 1);
 
         /**
          * @brief Calculates the derivative of an operation with respect to the parameters for a given sample
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sqrt/parameterize_top_lev_square_root.cpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sqrt/parameterize_top_lev_square_root.cpp
deleted file mode 100644
index fc4fe6599d50006fd229196efbb61234e9124af7..0000000000000000000000000000000000000000
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sqrt/parameterize_top_lev_square_root.cpp
+++ /dev/null
@@ -1,66 +0,0 @@
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/sqrt/parameterize_top_lev_square_root.hpp>
-
-void generateSqrtParamTopLevNode(std::vector<node_ptr>& feat_list, node_ptr feat, int& feat_ind, double l_bound, double u_bound, std::vector<double>& prop)
-{
-    ++feat_ind;
-    node_ptr new_feat = std::make_shared<SqrtParamTopLevNode>(feat, feat_ind, prop);
-
-    if(new_feat->parameters()[0] * feat->domain() + new_feat->parameters()[1] <= 0.0)
-        return;
-
-    new_feat->set_value();
-    if(new_feat->is_nan() || new_feat->is_const() || (util_funcs::max_abs_val<double>(new_feat->value_ptr(), new_feat->n_samp()) > u_bound) || (util_funcs::max_abs_val<double>(new_feat->value_ptr(), new_feat->n_samp()) < l_bound))
-        return;
-
-    feat_list.push_back(new_feat);
-}
-
-SqrtParamTopLevNode::SqrtParamTopLevNode()
-{}
-
-SqrtParamTopLevNode::SqrtParamTopLevNode(node_ptr feat, int feat_ind, double l_bound, double u_bound, std::vector<double>& prop) :
-    SqrtParamNode(feat, feat_ind, l_bound, u_bound)
-{
-    _params.resize(n_params(), 0.0);
-    get_parameters(prop);
-
-    if((_params[0] * _feats[0]->domain() + _params[1]) <= 0.0)
-        throw InvalidFeatureException();
-
-    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();
-}
-
-SqrtParamTopLevNode::SqrtParamTopLevNode(node_ptr feat, int feat_ind, std::vector<double>& prop) :
-    SqrtParamNode(feat, feat_ind)
-{
-    _params.resize(n_params(), 0.0);
-    get_parameters(prop);
-}
-
-SqrtParamTopLevNode::SqrtParamTopLevNode(node_ptr feat, int feat_ind, double l_bound, double u_bound) :
-    SqrtParamNode(feat, feat_ind, l_bound, u_bound)
-{
-    _params.resize(n_params(), 0.0);
-}
-
-void SqrtParamTopLevNode::set_value(int offset)
-{
-    offset = (offset == -1) ? rung() : offset;
-    if(_selected)
-        allowed_op_funcs::sqrt(_n_samp, _feats[0]->value_ptr(offset + 2), _params[0], _params[1], node_value_arrs::get_d_matrix_ptr(_d_mat_ind));
-
-    allowed_op_funcs::sqrt(_n_samp, _feats[0]->value_ptr(offset + 2), _params[0], _params[1], node_value_arrs::get_value_ptr(_arr_ind, _feat_ind, offset, false));
-}
-
-void SqrtParamTopLevNode::set_test_value(int offset)
-{
-    offset = (offset == -1) ? rung() : offset;
-    allowed_op_funcs::sqrt(_n_test_samp, _feats[0]->test_value_ptr(offset + 2), _params[0], _params[1], node_value_arrs::get_test_value_ptr(_arr_ind, _feat_ind, offset, false));
-}
-
-void SqrtParamTopLevNode::set_bounds(double* lb, double* ub, int from_parent)
-{
-    lb[0] = _sign_alpha;
-    ub[0] = _sign_alpha;
-}
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sqrt/parameterize_top_lev_square_root.hpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sqrt/parameterize_top_lev_square_root.hpp
deleted file mode 100644
index 40158ff01b9c896ffc6aeb9520e3e71196f3ff0f..0000000000000000000000000000000000000000
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sqrt/parameterize_top_lev_square_root.hpp
+++ /dev/null
@@ -1,130 +0,0 @@
-/** @file feature_creation/node/operator_nodes/allowed_operator_nodes/sqrt/parameterize_top_levsquare_root.hpp
- *  @brief Class describing the parameterized square root operator
- *
- *  This class represents the parameterized unary operator -> sqrt(alpha * A + a)
- *
- *  @author Thomas A. R. Purcell (tpurcell)
- *  @bug No known bugs.
- */
-#ifndef PARAM_TOP_LEV_SQRT_NODE
-#define PARAM_TOP_LEV_SQRT_NODE
-
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/sqrt/parameterized_square_root.hpp>
-#include <nl_opt/NLOptWrapper.hpp>
-
-// DocString: cls_abs_node
-/**
- * @brief ParamNode for the absolute value operator
- *
- */
-class SqrtParamTopLevNode: public SqrtParamNode
-{
-    using SqrtParamNode::set_value;
-    using SqrtParamNode::set_test_value;
-    using SqrtParamNode::value_ptr;
-    using SqrtParamNode::test_value_ptr;
-    using SqrtParamNode::domain;
-    using SqrtParamNode::expr;
-
-    friend class boost::serialization::access;
-
-    /**
-     * @brief Serialization function to send over MPI
-     *
-     * @param ar Archive representation of node
-     */
-    template <typename Archive>
-    void serialize(Archive& ar, const unsigned int version)
-    {
-        ar & boost::serialization::base_object<SqrtParamNode>(*this);
-    }
-
-public:
-    /**
-     * @brief Base Constructor
-     * @details This is only used for serialization
-     */
-    SqrtParamTopLevNode();
-
-    /**
-     * @brief Constructor
-     * @details Constructs the ParamNode from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_ind Index of the new feature
-     * @param l_bound Minimum absolute value allowed for the feature.
-     * @param u_bound Maximum absolute value allowed for the feature.
-     * @param prop The property to fit to
-     */
-    SqrtParamTopLevNode(node_ptr feat, int feat_ind, double l_bound, double u_bound, std::vector<double>& prop);
-
-    /**
-     * @brief Constructor
-     * @details Constructs the ParamNode from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_ind Index of the new feature
-     * @param prop The property to fit to
-     */
-    SqrtParamTopLevNode(node_ptr feat, int feat_ind, std::vector<double>& prop);
-
-    /**
-     * @brief Constructor
-     * @details Constructs the ParamNode from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_ind Index of the new feature
-     * @param l_bound Minimum absolute value allowed for the feature.
-     * @param u_bound Maximum absolute value allowed for the feature.
-     * @param param_list The list of parameters to optimize using non-linear least squares
-     */
-    SqrtParamTopLevNode(node_ptr feat, int feat_ind, double l_bound=1e-50, double u_bound=1e50);
-
-    // DocString: sqrt_param_top_lev_node_n_params
-    /**
-     * @brief returns the number of theoretical parameters for this feature
-     * @return the number of theoretical parameters
-     */
-    inline int n_params(int n_cur = 0){return 2;}
-
-    // DocString: sqrt_param_top_lev_node_set_value
-    /**
-     * @brief Set the values of the training data for the feature inside of the value storage arrays
-     *
-     * @param offset(int) Key to determine which part of the temporary storage array to look into
-     */
-    void set_value(int offset = -1);
-
-    // DocString: sqrt_param_top_lev_node_set_test_value
-    /**
-     * @brief Set the values of the test data for the feature inside of the value storage arrays
-     *
-     * @param offset(int) Key to determine which part of the temporary storage array to look into
-     */
-    void set_test_value(int offset = -1);
-
-    // DocString: sqrt_param_top_lev_node_expr
-    /**
-     * @brief Get the expression for the overall feature (From root node down)
-     */
-    inline std::string expr(){return "sqrt(" + std::to_string(_params[0]) + "*" + _feats[0]->expr() + " + " + std::to_string(_params[1]) + ")";}
-
-    // DocString: sqrt_param_top_lev_node_domain
-    /**
-     * @brief The domain for the feature (min/max values)
-     */
-    inline Domain domain(){return _feats[0]->domain().pow(1.0 / 2.0, _params[0], _params[1]);}
-
-    // DocString: add_param_top_lev_node_set_bounds
-    /**
-     * @brief Set the bounds for the nl parameterization
-     *
-     * @param lb pointer to the lower bounds data
-     * @param ub pointer to the upper bounds data
-     */
-    void set_bounds(double* lb, double* ub, int from_parent=2);
-};
-
-void generateSqrtParamTopLevNode(std::vector<node_ptr>& feat_list, node_ptr feat, int& feat_ind, double l_bound, double u_bound, std::vector<double>& prop);
-
-#endif
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sqrt/parameterized_square_root.cpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sqrt/parameterized_square_root.cpp
index 2edeb2da2dcc1f4da9442b00e73ac2956b2e6037..7e93032f093903d29330c3341af51a88d7ea304c 100644
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sqrt/parameterized_square_root.cpp
+++ b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sqrt/parameterized_square_root.cpp
@@ -66,31 +66,43 @@ void SqrtParamNode::get_parameters(std::vector<double>& prop)
     }
 }
 
-void SqrtNode::set_value(const double* params, int offset)
+void SqrtNode::set_value(const double* params, int offset, int depth)
 {
     offset = (offset == -1) ? rung() : offset;
+    double* vp_0 = (depth < nlopt_wrapper::_max_param_depth) ? _feats[0]->value_ptr(params + 2, offset + 2, depth + 1) : _feats[0]->value_ptr(offset + 2);
+
     if(_selected)
         allowed_op_funcs::sqrt(_n_samp, _feats[0]->value_ptr(params + 2, offset + 2), params[0], params[1], node_value_arrs::get_d_matrix_ptr(_d_mat_ind));
 
     allowed_op_funcs::sqrt(_n_samp, _feats[0]->value_ptr(params + 2, offset + 2), params[0], params[1], node_value_arrs::get_value_ptr(_arr_ind, _feat_ind, offset, false));
 }
 
-void SqrtNode::set_test_value(const double* params, int offset)
+void SqrtNode::set_test_value(const double* params, int offset, int depth)
 {
     offset = (offset == -1) ? rung() : offset;
+    double* vp_0 = (depth < nlopt_wrapper::_max_param_depth) ? _feats[0]->test_value_ptr(params + 2, offset + 2, depth + 1) : _feats[0]->test_value_ptr(offset + 2);
+
     allowed_op_funcs::sqrt(_n_test_samp, _feats[0]->test_value_ptr(params + 2, offset + 2), params[0], params[1], node_value_arrs::get_test_value_ptr(_arr_ind, _feat_ind, offset, false));
 }
 
-void SqrtNode::set_bounds(double* lb, double* ub, int from_parent)
+void SqrtNode::set_bounds(double* lb, double* ub, int from_parent, int depth)
 {
     lb[0] = 1.0;
     ub[0] = 1.0;
-    _feats[0]->set_bounds(lb + 2, ub + 2);
+
+    if(depth >= nlopt_wrapper::_max_param_depth)
+        return;
+
+    _feats[0]->set_bounds(lb + 2, ub + 2, depth + 1);
 }
 
-void SqrtParamNode::set_bounds(double* lb, double* ub, int from_parent)
+void SqrtParamNode::set_bounds(double* lb, double* ub, int from_parent, int depth)
 {
     lb[0] = _sign_alpha;
     ub[0] = _sign_alpha;
-    _feats[0]->set_bounds(lb + 2, ub + 2);
+
+    if(depth >= nlopt_wrapper::_max_param_depth)
+        return;
+
+    _feats[0]->set_bounds(lb + 2, ub + 2, depth + 1);
 }
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sqrt/parameterized_square_root.hpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sqrt/parameterized_square_root.hpp
index d8ee9f35535b264c3d7d8dbfc43f2860a20459fc..c85c5774581c61920b5c359343e1ec4b6073dd13 100644
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sqrt/parameterized_square_root.hpp
+++ b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sqrt/parameterized_square_root.hpp
@@ -163,7 +163,7 @@ public:
      * @param lb pointer to the lower bounds data
      * @param ub pointer to the upper bounds data
      */
-    void set_bounds(double* lb, double* ub, int from_parent=2);
+    void set_bounds(double* lb, double* ub, int from_parent=2, int depth=1);
 
     /**
      * @brief Converts a feature into a postfix expression (reverse polish notation)
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sqrt/square_root.hpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sqrt/square_root.hpp
index 4f5d345a539cd27bbb2fbfa910b7e00a6fe82795..b97dd110815328e58d7ab770c27ff60df8777903 100644
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sqrt/square_root.hpp
+++ b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sqrt/square_root.hpp
@@ -10,6 +10,7 @@
 #define SQRT_NODE
 
 #include <feature_creation/node/operator_nodes/OperatorNode.hpp>
+#include <fmt/core.h>
 
 // DocString: cls_sqrt_node
 /**
@@ -68,7 +69,13 @@ public:
     /**
      * @brief Get the expression for the overall feature (From root node down)
      */
-    inline std::string expr(){return "sqrt(" + _feats[0]->expr() + ")";}
+    inline std::string expr()
+    {
+        return fmt::format(
+            "sqrt[{}]",
+            _feats[0]->expr()
+        );
+    }
 
     // DocString: sqrt_node_set_value
     /**
@@ -155,7 +162,7 @@ public:
          * @param offset(int) Key to determine which part of the temporary storage array to look into
          * @param params pointer to the parameter values
          */
-        void set_value(const double* params, int offset = -1);
+        void set_value(const double* params, int offset=-1, int depth=1);
 
         /**
          * @brief Set the values of the test data for the feature inside of the value storage arrays
@@ -163,7 +170,7 @@ public:
          * @param offset(int) Key to determine which part of the temporary storage array to look into
          * @param params pointer to the parameter values
          */
-        void set_test_value(const double* params, int offset = -1);
+        void set_test_value(const double* params, int offset=-1, int depth=1);
 
         /**
          * @brief The domain for the feature (min/max values)
@@ -171,7 +178,14 @@ public:
          * @param params parameter values for non-linear operations
          * @return the domain
          */
-        inline Domain domain(double* params){return _feats[0]->domain(params + 2).pow(1.0 / 2.0, params[0], params[1]);}
+        inline Domain domain(double* params, int depth=1)
+        {
+            if(depth < nlopt_wrapper::_max_param_depth)
+                return _feats[0]->domain(params + 2, depth + 1).pow(0.5, params[0], params[1]);
+            else
+                return _feats[0]->domain().pow(0.5, params[0], params[1]);
+        }
+        // inline Domain domain(double* params){return _feats[0]->domain(params + 2).pow(1.0 / 2.0, params[0], params[1]);}
 
         /**
          * @brief The expression of the feature
@@ -179,7 +193,16 @@ public:
          * @param params parameter values for non-linear operations
          * @return feature expression
          */
-        inline std::string expr(double* params){return "sqrt(" + std::to_string(params[0]) + "*" + _feats[0]->expr(params + 2) + " + " + std::to_string(params[1]) + ")";}
+        inline std::string expr(double* params, int depth=1)
+        {
+            return fmt::format(
+                "sqrt[{:.10e} * {} {:+15.10e}]",
+                params[0],
+                (depth < nlopt_wrapper::_max_param_depth ? _feats[0]->expr(params + 2, depth + 1) : _feats[0]->expr()),
+                params[1]
+            );
+        }
+        // inline std::string expr(double* params){return "sqrt(" + std::to_string(params[0]) + "*" + _feats[0]->expr(params + 2) + " + " + std::to_string(params[1]) + ")";}
 
         /**
          * @brief Set the bounds for the nl parameterization
@@ -187,7 +210,7 @@ public:
          * @param lb pointer to the lower bounds data
          * @param ub pointer to the upper bounds data
          */
-        virtual void set_bounds(double* lb, double* ub, int from_parent=2);
+        virtual void set_bounds(double* lb, double* ub, int from_parent=2, int depth=1);
 
         /**
          * @brief Calculates the derivative of an operation with respect to the parameters for a given sample
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sub/parameterize_top_lev_subtract.cpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sub/parameterize_top_lev_subtract.cpp
deleted file mode 100644
index da611609fd51ec8af2e0ba86e4f76f104fb59dac..0000000000000000000000000000000000000000
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sub/parameterize_top_lev_subtract.cpp
+++ /dev/null
@@ -1,60 +0,0 @@
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/sub/parameterize_top_lev_subtract.hpp>
-
-void generateSubParamTopLevNode(std::vector<node_ptr>& feat_list, node_ptr feat_1, node_ptr feat_2, int& feat_ind, double l_bound, double u_bound, std::vector<double>& prop)
-{
-    ++feat_ind;
-    node_ptr new_feat = std::make_shared<SubParamTopLevNode>(feat_1, feat_2, feat_ind, prop);
-
-    new_feat->set_value();
-    if(new_feat->is_nan() || new_feat->is_const() || (util_funcs::max_abs_val<double>(new_feat->value_ptr(), new_feat->n_samp()) > u_bound) || (util_funcs::max_abs_val<double>(new_feat->value_ptr(), new_feat->n_samp()) < l_bound))
-        return;
-
-    feat_list.push_back(new_feat);
-}
-
-SubParamTopLevNode::SubParamTopLevNode()
-{}
-
-SubParamTopLevNode::SubParamTopLevNode(node_ptr feat_1, node_ptr feat_2, int feat_ind, double l_bound, double u_bound, std::vector<double>& prop) :
-    SubParamNode(feat_1, feat_2, feat_ind, l_bound, u_bound)
-{
-    _params.resize(n_params(), 0.0);
-    get_parameters(prop);
-
-    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();
-}
-
-SubParamTopLevNode::SubParamTopLevNode(node_ptr feat_1, node_ptr feat_2, int feat_ind, std::vector<double>& prop) :
-    SubParamNode(feat_1, feat_2, feat_ind)
-{
-    _params.resize(n_params(), 0.0);
-    get_parameters(prop);
-}
-
-SubParamTopLevNode::SubParamTopLevNode(node_ptr feat_1, node_ptr feat_2, int feat_ind, double l_bound, double u_bound) :
-    SubParamNode(feat_1, feat_2, feat_ind, l_bound, u_bound)
-{
-    _params.resize(n_params(), 0.0);
-}
-
-void SubParamTopLevNode::set_value(int offset)
-{
-    offset = (offset == -1) ? rung() : offset;
-
-    if(_selected)
-        allowed_op_funcs::sub(_n_samp, _feats[0]->value_ptr(offset + 2), _feats[1]->value_ptr(offset + 1), _params[0], _params[1], node_value_arrs::get_d_matrix_ptr(_d_mat_ind));
-    allowed_op_funcs::sub(_n_samp, _feats[0]->value_ptr(offset + 2), _feats[1]->value_ptr(offset + 1), _params[0], _params[1], node_value_arrs::get_value_ptr(_arr_ind, _feat_ind, offset, false));
-}
-
-void SubParamTopLevNode::set_test_value(int offset)
-{
-    offset = (offset == -1) ? rung() : offset;
-    allowed_op_funcs::sub(_n_test_samp, _feats[0]->test_value_ptr(offset + 2), _feats[1]->test_value_ptr(offset + 1), _params[0], _params[1], node_value_arrs::get_test_value_ptr(_arr_ind, _feat_ind, offset, false));
-}
-
-void SubParamTopLevNode::set_bounds(double* lb, double* ub, int from_parent)
-{
-    lb[1] = 0.0;
-    ub[1] = 0.0;
-}
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sub/parameterize_top_lev_subtract.hpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sub/parameterize_top_lev_subtract.hpp
deleted file mode 100644
index 5fac02dc2413910d336513f4433226bc49ea7f8e..0000000000000000000000000000000000000000
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sub/parameterize_top_lev_subtract.hpp
+++ /dev/null
@@ -1,132 +0,0 @@
-/** @file feature_creation/node/operator_nodes/allowed_operator_nodes/sub/parameterize_top_levsubtract.hpp
- *  @brief Class describing the parameterized addition operator
- *
- *  This class represents the parameterized unary operator -> A - (alpha * B + a)
- *
- *  @author Thomas A. R. Purcell (tpurcell)
- *  @bug No known bugs.
- */
-#ifndef PARAM_TOP_LEV_SUB_NODE
-#define PARAM_TOP_LEV_SUB_NODE
-
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/sub/parameterized_subtract.hpp>
-#include <nl_opt/NLOptWrapper.hpp>
-
-// DocString: cls_param_add_node
-/**
- * @brief ParamNode for the absolute value operator
- *
- */
-class SubParamTopLevNode: public SubParamNode
-{
-    using SubParamNode::set_value;
-    using SubParamNode::set_test_value;
-    using SubParamNode::value_ptr;
-    using SubParamNode::test_value_ptr;
-    using SubParamNode::domain;
-    using SubParamNode::expr;
-
-    friend class boost::serialization::access;
-
-    /**
-     * @brief Serialization function to send over MPI
-     *
-     * @param ar Archive representation of node
-     */
-    template <typename Archive>
-    void serialize(Archive& ar, const unsigned int version)
-    {
-        ar & boost::serialization::base_object<SubParamNode>(*this);
-    }
-
-public:
-    /**
-     * @brief Base Constructor
-     * @details This is only used for serialization
-     */
-    SubParamTopLevNode();
-
-    /**
-     * @brief Constructor
-     * @details Constructs the ParamNode from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_2 shared_ptr of the feature to operate on (B)
-     * @param feat_ind Index of the new feature
-     * @param l_bound Minimum absolute value allowed for the feature.
-     * @param u_bound Maximum absolute value allowed for the feature.
-     * @param prop The property to fit to
-     */
-    SubParamTopLevNode(node_ptr feat_1, node_ptr feat_2, int feat_ind, double l_bound, double u_bound, std::vector<double>& prop);
-
-    /**
-     * @brief Constructor
-     * @details Constructs the ParamNode from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_2 shared_ptr of the feature to operate on (B)
-     * @param feat_ind Index of the new feature
-     * @param prop The property to fit to
-     */
-    SubParamTopLevNode(node_ptr feat_1, node_ptr feat_2, int feat_ind, std::vector<double>& prop);
-
-    /**
-     * @brief Constructor
-     * @details Constructs the ParamNode from node pointer of the feature to operate on
-     *
-     * @param feat_1 shared_ptr of the feature to operate on (A)
-     * @param feat_ind Index of the new feature
-     * @param l_bound Minimum absolute value allowed for the feature.
-     * @param u_bound Maximum absolute value allowed for the feature.
-     * @param param_list The list of parameters to optimize using non-linear least squares
-     */
-    SubParamTopLevNode(node_ptr feat_1, node_ptr feat_2, int feat_ind, double l_bound=1e-50, double u_bound=1e50);
-
-    // DocString: sub_param_top_lev_node_n_params
-    /**
-     * @brief returns the number of theoretical parameters for this feature
-     * @return the number of theoretical parameters
-     */
-    inline int n_params(int n_cur = 0){return 2;}
-
-    // DocString: sub_param_top_lev_node_set_value
-    /**
-     * @brief Set the values of the training data for the feature inside of the value storage arrays
-     *
-     * @param offset(int) Key to determine which part of the temporary storage array to look into
-     */
-    void set_value(int offset = -1);
-
-    // DocString: sub_param_top_lev_node_set_test_value
-    /**
-     * @brief Set the values of the test data for the feature inside of the value storage arrays
-     *
-     * @param offset(int) Key to determine which part of the temporary storage array to look into
-     */
-    void set_test_value(int offset = -1);
-
-    // DocString: sub_param_top_lev_node_expr
-    /**
-     * @brief Get the expression for the overall feature (From root node down)
-     */
-    inline std::string expr(){return "[(" + _feats[0]->expr() + ") - (" + std::to_string(_params[0]) + "*" + _feats[1]->expr() + " + " + std::to_string(_params[1]) + ")]";}
-
-    // DocString: sub_param_top_lev_node_domain
-    /**
-     * @brief The domain for the feature (min/max values)
-     */
-    inline Domain domain(){return _feats[0]->domain().sub(_feats[1]->domain(), _params[0], _params[1]);}
-
-    // DocString: add_param_top_lev_node_set_bounds
-    /**
-     * @brief Set the bounds for the nl parameterization
-     *
-     * @param lb pointer to the lower bounds data
-     * @param ub pointer to the upper bounds data
-     */
-    void set_bounds(double* lb, double* ub, int from_parent=2);
-};
-
-void generateSubParamTopLevNode(std::vector<node_ptr>& feat_list, node_ptr feat_1, node_ptr feat_2, int& feat_ind, double l_bound, double u_bound, std::vector<double>& prop);
-
-#endif
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sub/parameterized_subtract.cpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sub/parameterized_subtract.cpp
index cb2235800d48b50109076c2218dbcc84c75e2029..fc726710b57d82f9ebdaff03ddf05fa9f0437a7c 100644
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sub/parameterized_subtract.cpp
+++ b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sub/parameterized_subtract.cpp
@@ -49,26 +49,37 @@ void SubParamNode::get_parameters(std::vector<double>& prop)
     double min_res = nlopt_wrapper::optimize_feature_params(d);
 }
 
-void SubNode::set_value(const double* params, int offset)
+void SubNode::set_value(const double* params, int offset, int depth)
 {
     offset = (offset == -1) ? rung() : offset;
 
+    double* vp_0 = (depth < nlopt_wrapper::_max_param_depth) ? _feats[0]->value_ptr(params + _feats[1]->n_params() + 2, offset + 2, depth + 1) : _feats[0]->value_ptr(offset + 2);
+    double* vp_1 = (depth < nlopt_wrapper::_max_param_depth) ? _feats[1]->value_ptr(params + 2, offset + 1, depth + 1) : _feats[1]->value_ptr(offset + 1);
+
     if(_selected)
-        allowed_op_funcs::sub(_n_samp, _feats[0]->value_ptr(params + 2 + _feats[1]->n_params(), offset + 2), _feats[1]->value_ptr(params + 2, offset + 1), params[0], params[1], node_value_arrs::get_d_matrix_ptr(_d_mat_ind));
-    allowed_op_funcs::sub(_n_samp, _feats[0]->value_ptr(params + 2 + _feats[1]->n_params(), offset + 2), _feats[1]->value_ptr(params + 2, offset + 1), params[0], params[1], node_value_arrs::get_value_ptr(_arr_ind, _feat_ind, offset, false));
+        allowed_op_funcs::sub(_n_samp, vp_0, vp_1, params[0], params[1], node_value_arrs::get_d_matrix_ptr(_d_mat_ind));
+
+    allowed_op_funcs::sub(_n_samp, vp_0, vp_1, params[0], params[1], node_value_arrs::get_value_ptr(_arr_ind, _feat_ind, offset, false));
 }
 
-void SubNode::set_test_value(const double* params, int offset)
+void SubNode::set_test_value(const double* params, int offset, int depth)
 {
     offset = (offset == -1) ? rung() : offset;
-    allowed_op_funcs::sub(_n_test_samp, _feats[0]->test_value_ptr(params + 2 + _feats[1]->n_params(), offset + 2), _feats[1]->test_value_ptr(params + 2, offset + 1), params[0], params[1], node_value_arrs::get_test_value_ptr(_arr_ind, _feat_ind, offset, false));
+
+    double* vp_0 = (depth < nlopt_wrapper::_max_param_depth) ? _feats[0]->test_value_ptr(params + _feats[1]->n_params() + 2, offset + 2, depth + 1) : _feats[0]->test_value_ptr(offset + 2);
+    double* vp_1 = (depth < nlopt_wrapper::_max_param_depth) ? _feats[1]->test_value_ptr(params + 2, offset + 1, depth + 1) : _feats[1]->test_value_ptr(offset + 1);
+
+    allowed_op_funcs::sub(_n_test_samp, vp_0, vp_1, params[0], params[1], node_value_arrs::get_test_value_ptr(_arr_ind, _feat_ind, offset, false));
 }
 
-void SubNode::set_bounds(double* lb, double* ub, int from_parent)
+void SubNode::set_bounds(double* lb, double* ub, int from_parent, int depth)
 {
     lb[1] = 0.0;
     ub[1] = 0.0;
 
-    _feats[0]->set_bounds(lb + 2 + _feats[1]->n_params(), ub + 2 + _feats[1]->n_params(), 2 + _feats[1]->n_params());
-    _feats[1]->set_bounds(lb + 2, ub + 2);
+    if(depth >= nlopt_wrapper::_max_param_depth)
+        return;
+
+    _feats[0]->set_bounds(lb + 2 + _feats[1]->n_params(), ub + 2 + _feats[1]->n_params(), 2 + _feats[1]->n_params(), depth + 1);
+    _feats[1]->set_bounds(lb + 2, ub + 2, depth + 1);
 }
diff --git a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sub/subtract.hpp b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sub/subtract.hpp
index b26ff84c02bc651987a856b1ae96da9da920cfad..fda9a2ddff7dc4f1995f978cc91a3a02bc7e6064 100644
--- a/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sub/subtract.hpp
+++ b/src/feature_creation/node/operator_nodes/allowed_operator_nodes/sub/subtract.hpp
@@ -10,6 +10,7 @@
 #define SUB_NODE
 
 #include <feature_creation/node/operator_nodes/OperatorNode.hpp>
+#include <fmt/core.h>
 
 // DocString: cls_sub_node
 /**
@@ -70,7 +71,15 @@ public:
     /**
      * @brief Get the expression for the overall feature (From root node down)
      */
-    inline std::string expr(){return "[(" + _feats[0]->expr() + ") - (" + _feats[1]->expr() + ")]";}
+    inline std::string expr()
+    {
+        return fmt::format(
+            "[({}) - ({})]",
+            _feats[0]->expr(),
+            _feats[1]->expr()
+        );
+    }
+    // inline std::string expr(){return "[(" + _feats[0]->expr() + ") - (" + _feats[1]->expr() + ")]";}
 
     // DocString: sub_node_set_value
     /**
@@ -163,7 +172,7 @@ public:
          * @param offset(int) Key to determine which part of the temporary storage array to look into
          * @param params pointer to the parameter values
          */
-        void set_value(const double* params, int offset = -1);
+        void set_value(const double* params, int offset=-1, int depth=1);
 
         /**
          * @brief Set the values of the test data for the feature inside of the value storage arrays
@@ -171,7 +180,7 @@ public:
          * @param offset(int) Key to determine which part of the temporary storage array to look into
          * @param params pointer to the parameter values
          */
-        void set_test_value(const double* params, int offset = -1);
+        void set_test_value(const double* params, int offset=-1, int depth=1);
 
         /**
          * @brief The domain for the feature (min/max values)
@@ -179,7 +188,14 @@ public:
          * @param params parameter values for non-linear operations
          * @return the domain
          */
-        inline Domain domain(double* params){return _feats[0]->domain(params + _feats[1]->n_params() + 2).sub(_feats[1]->domain(params + 2), params[0], params[1]);}
+        inline Domain domain(double* params, int depth=1)
+        {
+            if(depth < nlopt_wrapper::_max_param_depth)
+                return _feats[0]->domain(params + _feats[1]->n_params() + 2, depth + 1).sub(_feats[1]->domain(params + 2, depth + 1), params[0], params[1]);
+            else
+                return _feats[0]->domain().sub(_feats[1]->domain(), params[0], params[1]);
+        }
+        // inline Domain domain(double* params){return _feats[0]->domain(params + _feats[1]->n_params() + 2).sub(_feats[1]->domain(params + 2), params[0], params[1]);}
 
         /**
          * @brief The expression of the feature
@@ -187,7 +203,17 @@ public:
          * @param params parameter values for non-linear operations
          * @return feature expression
          */
-        inline std::string expr(double* params){return "[(" + _feats[0]->expr(params + _feats[1]->n_params() + 2) + ") - (" + std::to_string(params[0]) + "*" + _feats[1]->expr(params + 2) + " + " + std::to_string(params[1]) + ")]";}
+        inline std::string expr(double* params, int depth=1)
+        {
+            return fmt::format(
+                "[({}) - ({:.10e} * {} {:+15.10e})]",
+                (depth < nlopt_wrapper::_max_param_depth ? _feats[0]->expr(params + _feats[1]->n_params() + 2, depth + 1) : _feats[0]->expr()),
+                params[0],
+                (depth < nlopt_wrapper::_max_param_depth ? _feats[1]->expr(params + 2, depth + 1) : _feats[1]->expr()),
+                params[1]
+            );
+        }
+        // inline std::string expr(double* params){return "[(" + _feats[0]->expr(params + _feats[1]->n_params() + 2) + ") - (" + std::to_string(params[0]) + "*" + _feats[1]->expr(params + 2) + " + " + std::to_string(params[1]) + ")]";}
 
         /**
          * @brief Set the bounds for the nl parameterization
@@ -195,7 +221,7 @@ public:
          * @param lb pointer to the lower bounds data
          * @param ub pointer to the upper bounds data
          */
-        void set_bounds(double* lb, double* ub, int from_parent=2);
+        void set_bounds(double* lb, double* ub, int from_parent=2, int depth = 1);
 
         /**
          * @brief Calculates the derivative of an operation with respect to the parameters for a given sample
diff --git a/src/feature_creation/node/operator_nodes/allowed_ops.hpp b/src/feature_creation/node/operator_nodes/allowed_ops.hpp
index 07cc1fed8197895de68f96168d15746d75c64a83..265e247084f0987604a3df181246e7f07057c60e 100644
--- a/src/feature_creation/node/operator_nodes/allowed_ops.hpp
+++ b/src/feature_creation/node/operator_nodes/allowed_ops.hpp
@@ -7,23 +7,23 @@
 #ifndef ALLOWED_OP_NODES
 #define ALLOWED_OP_NODES
 
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/add/parameterize_top_lev_add.hpp>
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/sub/parameterize_top_lev_subtract.hpp>
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/abs/parameterize_top_lev_absolute_value.hpp>
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/abs_diff/parameterize_top_lev_absolute_difference.hpp>
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/mult/parameterize_top_lev_multiply.hpp>
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/div/parameterize_top_lev_divide.hpp>
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/inv/parameterize_top_lev_inverse.hpp>
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/exp/parameterize_top_lev_exponential.hpp>
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/neg_exp/parameterize_top_lev_negative_exponential.hpp>
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/log/parameterize_top_lev_log.hpp>
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/sq/parameterize_top_lev_square.hpp>
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/cb/parameterize_top_lev_cube.hpp>
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/six_pow/parameterize_top_lev_sixth_power.hpp>
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/sqrt/parameterize_top_lev_square_root.hpp>
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/cbrt/parameterize_top_lev_cube_root.hpp>
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/sin/parameterize_top_lev_sin.hpp>
-#include <feature_creation/node/operator_nodes/allowed_operator_nodes/cos/parameterize_top_lev_cos.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/add/parameterized_add.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/sub/parameterized_subtract.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/abs/parameterized_absolute_value.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/abs_diff/parameterized_absolute_difference.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/mult/parameterized_multiply.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/div/parameterized_divide.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/inv/parameterized_inverse.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/exp/parameterized_exponential.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/neg_exp/parameterized_negative_exponential.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/log/parameterized_log.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/sq/parameterized_square.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/cb/parameterized_cube.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/six_pow/parameterized_sixth_power.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/sqrt/parameterized_square_root.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/cbrt/parameterized_cube_root.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/sin/parameterized_sin.hpp>
+#include <feature_creation/node/operator_nodes/allowed_operator_nodes/cos/parameterized_cos.hpp>
 #include <map>
 #include <iostream>
 
diff --git a/src/feature_creation/node/operator_nodes/allowed_parameter_ops.cpp b/src/feature_creation/node/operator_nodes/allowed_parameter_ops.cpp
index 6d089f0b6acc7a146e570a373d653969578ff4da..17fea3f1a1cf2be10cdb7ad1a1fb6dd1ff3f7c5c 100644
--- a/src/feature_creation/node/operator_nodes/allowed_parameter_ops.cpp
+++ b/src/feature_creation/node/operator_nodes/allowed_parameter_ops.cpp
@@ -5,46 +5,22 @@ std::map<std::string, bin_param_op_node_gen> allowed_op_maps::binary_param_opera
 
 void allowed_op_maps::set_param_node_maps()
 {
-    if(nlopt_wrapper::_param_internal)
-    {
-        allowed_op_maps::binary_param_operator_map["add"] = generateAddParamNode;
-        allowed_op_maps::binary_param_operator_map["sub"] = generateSubParamNode;
-        allowed_op_maps::binary_param_operator_map["abs_diff"] = generateAbsDiffParamNode;
-        allowed_op_maps::binary_param_operator_map["mult"] = generateMultParamNode;
-        allowed_op_maps::binary_param_operator_map["div"] = generateDivParamNode;
+    allowed_op_maps::binary_param_operator_map["add"] = generateAddParamNode;
+    allowed_op_maps::binary_param_operator_map["sub"] = generateSubParamNode;
+    allowed_op_maps::binary_param_operator_map["abs_diff"] = generateAbsDiffParamNode;
+    allowed_op_maps::binary_param_operator_map["mult"] = generateMultParamNode;
+    allowed_op_maps::binary_param_operator_map["div"] = generateDivParamNode;
 
-        allowed_op_maps::unary_param_operator_map["exp"] = generateExpParamNode;
-        allowed_op_maps::unary_param_operator_map["neg_exp"] = generateNegExpParamNode;
-        allowed_op_maps::unary_param_operator_map["inv"] = generateInvParamNode;
-        allowed_op_maps::unary_param_operator_map["sq"] = generateSqParamNode;
-        allowed_op_maps::unary_param_operator_map["cb"] = generateCbParamNode;
-        allowed_op_maps::unary_param_operator_map["six_pow"] = generateSixPowParamNode;
-        allowed_op_maps::unary_param_operator_map["sqrt"] = generateSqrtParamNode;
-        allowed_op_maps::unary_param_operator_map["cbrt"] = generateCbrtParamNode;
-        allowed_op_maps::unary_param_operator_map["log"] = generateLogParamNode;
-        allowed_op_maps::unary_param_operator_map["abs"] = generateAbsParamNode;
-        allowed_op_maps::unary_param_operator_map["sin"] = generateSinParamNode;
-        allowed_op_maps::unary_param_operator_map["cos"] = generateCosParamNode;
-    }
-    else
-    {
-        allowed_op_maps::binary_param_operator_map["add"] = generateAddParamTopLevNode;
-        allowed_op_maps::binary_param_operator_map["sub"] = generateSubParamTopLevNode;
-        allowed_op_maps::binary_param_operator_map["abs_diff"] = generateAbsDiffParamTopLevNode;
-        allowed_op_maps::binary_param_operator_map["mult"] = generateMultParamTopLevNode;
-        allowed_op_maps::binary_param_operator_map["div"] = generateDivParamTopLevNode;
-
-        allowed_op_maps::unary_param_operator_map["exp"] = generateExpParamTopLevNode;
-        allowed_op_maps::unary_param_operator_map["neg_exp"] = generateNegExpParamTopLevNode;
-        allowed_op_maps::unary_param_operator_map["inv"] = generateInvParamTopLevNode;
-        allowed_op_maps::unary_param_operator_map["sq"] = generateSqParamTopLevNode;
-        allowed_op_maps::unary_param_operator_map["cb"] = generateCbParamTopLevNode;
-        allowed_op_maps::unary_param_operator_map["six_pow"] = generateSixPowParamTopLevNode;
-        allowed_op_maps::unary_param_operator_map["sqrt"] = generateSqrtParamTopLevNode;
-        allowed_op_maps::unary_param_operator_map["cbrt"] = generateCbrtParamTopLevNode;
-        allowed_op_maps::unary_param_operator_map["log"] = generateLogParamTopLevNode;
-        allowed_op_maps::unary_param_operator_map["abs"] = generateAbsParamTopLevNode;
-        allowed_op_maps::unary_param_operator_map["sin"] = generateSinParamTopLevNode;
-        allowed_op_maps::unary_param_operator_map["cos"] = generateCosParamTopLevNode;
-    }
+    allowed_op_maps::unary_param_operator_map["exp"] = generateExpParamNode;
+    allowed_op_maps::unary_param_operator_map["neg_exp"] = generateNegExpParamNode;
+    allowed_op_maps::unary_param_operator_map["inv"] = generateInvParamNode;
+    allowed_op_maps::unary_param_operator_map["sq"] = generateSqParamNode;
+    allowed_op_maps::unary_param_operator_map["cb"] = generateCbParamNode;
+    allowed_op_maps::unary_param_operator_map["six_pow"] = generateSixPowParamNode;
+    allowed_op_maps::unary_param_operator_map["sqrt"] = generateSqrtParamNode;
+    allowed_op_maps::unary_param_operator_map["cbrt"] = generateCbrtParamNode;
+    allowed_op_maps::unary_param_operator_map["log"] = generateLogParamNode;
+    allowed_op_maps::unary_param_operator_map["abs"] = generateAbsParamNode;
+    allowed_op_maps::unary_param_operator_map["sin"] = generateSinParamNode;
+    allowed_op_maps::unary_param_operator_map["cos"] = generateCosParamNode;
 }
diff --git a/src/inputs/InputParser.cpp b/src/inputs/InputParser.cpp
index da318dafaee47a706eed098a0dfbfe5f606685ed..c868fab71075b7460550fd1cbdfc17b5457fdd66 100644
--- a/src/inputs/InputParser.cpp
+++ b/src/inputs/InputParser.cpp
@@ -20,8 +20,8 @@ InputParser::InputParser(pt::ptree IP, std::string fn, std::shared_ptr<MPI_Inter
     _n_samp(0),
     _n_residuals(IP.get<int>("n_residual", 1)),
     _n_models_store(IP.get<int>("n_models_store", _n_residuals)),
-    _fix_intercept(IP.get<bool>("fix_intercept", false)),
-    _param_internal(IP.get<bool>("param_internal", true))
+    _max_param_depth(IP.get<int>("max_feat_param_depth", _max_rung)),
+    _fix_intercept(IP.get<bool>("fix_intercept", false))
 {
     std::ifstream data_stream;
     std::string line;
@@ -307,7 +307,7 @@ void InputParser::generate_feature_space(std::shared_ptr<MPI_Interface> comm, st
     }
 
     if(_calc_type.compare("log_regression_fit_c") == 0)
-        nlopt_wrapper::setup_data(_task_sizes_train, _n_dim, _max_rung, _param_internal);
+        nlopt_wrapper::setup_data(_task_sizes_train, _n_dim, _max_rung, _max_param_depth);
 
     _feat_space = std::make_shared<FeatureSpace>(comm, phi_0, _opset, _param_opset, _prop_train, _task_sizes_train, _calc_type, _max_rung, _n_sis_select, _max_store_rung, _n_rung_generate, _cross_cor_max, _l_bound, _u_bound);
 }
diff --git a/src/inputs/InputParser.hpp b/src/inputs/InputParser.hpp
index b31e44d57975816d73de73e206b833eabff06f41..711720269a8b197ae7cbc25e965dac3f226c85f4 100644
--- a/src/inputs/InputParser.hpp
+++ b/src/inputs/InputParser.hpp
@@ -67,9 +67,9 @@ public:
     int _n_samp; //!< //!< Number of samples (training data)
     int _n_residuals; //!< Number of residuals to pass to the next sis model
     int _n_models_store; //!< Number of models to store
+    int _max_param_depth; //!< Max depth to parameterize a feature (default=_max_rung)
 
     bool _fix_intercept; //!< If true force intercept to be 0.0
-    bool _param_internal; //!< If true parameterize features fully (not just top level)
     /**
      * @brief Constructor
      *
diff --git a/src/nl_opt/NLOptWrapper.cpp b/src/nl_opt/NLOptWrapper.cpp
index 99fc9a59238001a21170a7dae534a0f028ef4b6a..9cee7ca1d5ab068c01b2bebc06afeb1baea2e2ff 100644
--- a/src/nl_opt/NLOptWrapper.cpp
+++ b/src/nl_opt/NLOptWrapper.cpp
@@ -11,15 +11,15 @@ std::vector<double> nlopt_wrapper::_work;
 std::vector<double> nlopt_wrapper::_a_copy;
 std::vector<double> nlopt_wrapper::_prop_copy;
 
-bool nlopt_wrapper::_param_internal;
+int nlopt_wrapper::_max_param_depth;
 
 nlopt::algorithm nlopt_wrapper::_local_opt_alg = nlopt::LD_VAR2;
 double nlopt_wrapper::_cauchy_scaling = 0.5 * 0.5;
 int nlopt_wrapper::_n_samp = 0;
 
-void nlopt_wrapper::setup_data(std::vector<int> task_sizes, int max_dim, int n_rung, bool param_internal)
+void nlopt_wrapper::setup_data(std::vector<int> task_sizes, int max_dim, int n_rung, int max_param_depth)
 {
-    _param_internal = param_internal;
+    _max_param_depth = std::min(n_rung, max_param_depth);
     _task_sizes = task_sizes;
     _zeros.resize(50, 0.0);
     _n_samp = std::accumulate(task_sizes.begin(), task_sizes.end(), 0.0);
@@ -31,32 +31,32 @@ void nlopt_wrapper::setup_data(std::vector<int> task_sizes, int max_dim, int n_r
     #pragma omp parallel
     {
         _log_trans.resize(_n_samp, 0.0);
-        _residuals.resize(_n_samp, 0.0);
+        _work.resize(_n_samp * max_dim, 0.0);
         _a_copy.resize(_n_samp * 2, 0.0);
+        _residuals.resize(_n_samp, 0.0);
+        _feature_gradient.resize(max_params * _n_samp, 0.0);
         _prop_copy.resize(_n_samp, 0.0);
-        _work.resize(_n_samp * max_dim, 0.0);
-        _feature_gradient.resize(max_params * _n_samp);
     }
 }
 
-void nlopt_wrapper::set_objective(std::string calc_type, double* prop, std::vector<int> sizes, int n_rung, bool param_internal)
+void nlopt_wrapper::set_objective(std::string calc_type, double* prop, std::vector<int> sizes, int n_rung, int max_param_depth)
 {
     if(calc_type.compare("classification") == 0)
     {
         convex_hull::initialize_projection(sizes, prop);
         _objective = objective_class;
         _local_opt_alg = nlopt::LN_SBPLX;
-        setup_data(sizes, 1, 0, param_internal);
+        setup_data(sizes, 1, 0, max_param_depth);
     }
     else if(calc_type.compare("regression") == 0)
     {
         _objective = objective_reg;
-        setup_data(sizes, 1, n_rung, param_internal);
+        setup_data(sizes, 1, n_rung, max_param_depth);
     }
     else if(calc_type.compare("log_regression") == 0)
     {
         _objective = objective_log_reg;
-        setup_data(sizes, 1, n_rung, param_internal);
+        setup_data(sizes, 1, n_rung, max_param_depth);
     }
     else if(calc_type.compare("log_regression_fit_c") == 0)
     {
@@ -72,9 +72,8 @@ void nlopt_wrapper::set_objective(std::string calc_type, double* prop, std::vect
 double nlopt_wrapper::optimize_feature_params(feat_data data, bool use_simplex)
 {
     double minf = 0.0;
-
-    std::vector<double> params((_param_internal ? data._feat->parameters().size() : 2) + 2 * _task_sizes.size(), 1.0);
-    std::vector<double> params_final((_param_internal ? data._feat->parameters().size() : 2), 1.0);
+    std::vector<double> params(data._feat->parameters().size() + 2 * _task_sizes.size(), 1.0);
+    std::vector<double> params_final(data._feat->parameters().size(), 1.0);
 
     dcopy_(params.size() / 2, _zeros.data(), 1, &params[1], 2);
     dcopy_(params_final.size() / 2, _zeros.data(), 1, &params_final[1], 2);
diff --git a/src/nl_opt/NLOptWrapper.hpp b/src/nl_opt/NLOptWrapper.hpp
index a5880a7cb772ece88580e9d1460f3baa1451e223..dbe049b1573e8437a3751ac974a665571f023a3b 100644
--- a/src/nl_opt/NLOptWrapper.hpp
+++ b/src/nl_opt/NLOptWrapper.hpp
@@ -27,7 +27,7 @@ namespace nlopt_wrapper
     extern int _n_samp; //!< total number of samples
     extern nlopt::algorithm _local_opt_alg; //!< Algorithm used for local optimization
 
-    extern bool _param_internal; //!< parameterize features to all depths of the tree
+    extern int _max_param_depth; //!< parameterize features to all depths of the tree
 
     #pragma omp threadprivate(_log_trans, _work, _a_copy, _residuals, _feature_gradient,_prop_copy)
 
@@ -252,7 +252,7 @@ namespace nlopt_wrapper
      * @param max_dim Maximum dimension of the features
      * @param n_rung maximum rung of a feature
      */
-    void setup_data(std::vector<int> task_sizes, int max_dim, int n_rung, bool param_internal=true);
+    void setup_data(std::vector<int> task_sizes, int max_dim, int n_rung, int max_param_depth=100);
 
     /**
      * @brief Set up the projection operator for the objective function
@@ -262,7 +262,7 @@ namespace nlopt_wrapper
      * @param sizes number of samples per task
      * @param n_rung maximum rung of a feature
      */
-    void set_objective(std::string calc_type, double* prop, std::vector<int> sizes, int n_rung, bool param_internal=true);
+    void set_objective(std::string calc_type, double* prop, std::vector<int> sizes, int n_rung, int max_param_depth=100);
 
     #ifdef PY_BINDINGS
         /**
@@ -272,7 +272,7 @@ namespace nlopt_wrapper
          * @param max_dim Maximum dimension of the features
          * @param n_rung maximum rung of a feature
          */
-        inline void setup_data(py::list task_sizes, int max_dim, int n_rung, bool param_internal=true){setup_data(python_conv_utils::from_list<int>(task_sizes), max_dim, n_rung, param_internal);}
+        inline void setup_data(py::list task_sizes, int max_dim, int n_rung, int max_param_depth=100){setup_data(python_conv_utils::from_list<int>(task_sizes), max_dim, n_rung, max_param_depth);}
 
         /**
          * @brief Set up the projection operator for the objective function
@@ -281,7 +281,7 @@ namespace nlopt_wrapper
          * @param max_dim Maximum dimension of the features
          * @param n_rung maximum rung of a feature
          */
-        inline void setup_data(np::ndarray task_sizes, int max_dim, int n_rung, bool param_internal=true){setup_data(python_conv_utils::from_ndarray<int>(task_sizes), max_dim, n_rung, param_internal);}
+        inline void setup_data(np::ndarray task_sizes, int max_dim, int n_rung, int max_param_depth=100){setup_data(python_conv_utils::from_ndarray<int>(task_sizes), max_dim, n_rung, max_param_depth);}
 
         /**
          * @brief Set up the projection operator for the objective function
@@ -291,10 +291,10 @@ namespace nlopt_wrapper
          * @param N number of samples per task
          * @param n_rung maximum rung of a feature
          */
-        inline void set_objective(std::string calc_type, py::list prop, py::list sizes, int n_rung, bool param_internal=true)
+        inline void set_objective(std::string calc_type, py::list prop, py::list sizes, int n_rung, int max_param_depth=100)
         {
             std::vector<double> prop_vec = python_conv_utils::from_list<double>(prop);
-            return set_objective(calc_type, prop_vec.data(), python_conv_utils::from_list<int>(sizes), n_rung, param_internal);
+            return set_objective(calc_type, prop_vec.data(), python_conv_utils::from_list<int>(sizes), n_rung, max_param_depth);
         }
 
         /**
@@ -305,10 +305,10 @@ namespace nlopt_wrapper
          * @param N number of samples per task
          * @param n_rung maximum rung of a feature
          */
-        inline void set_objective(std::string calc_type, np::ndarray prop, py::list sizes, int n_rung, bool param_internal=true)
+        inline void set_objective(std::string calc_type, np::ndarray prop, py::list sizes, int n_rung, int max_param_depth=100)
         {
             std::vector<double> prop_vec = python_conv_utils::from_ndarray<double>(prop);
-            return set_objective(calc_type, prop_vec.data(), python_conv_utils::from_list<int>(sizes), n_rung, param_internal);
+            return set_objective(calc_type, prop_vec.data(), python_conv_utils::from_list<int>(sizes), n_rung, max_param_depth);
         }
 
         /**
@@ -319,10 +319,10 @@ namespace nlopt_wrapper
          * @param N number of samples per task
          * @param n_rung maximum rung of a feature
          */
-        inline void set_objective(std::string calc_type, np::ndarray prop, np::ndarray sizes, int n_rung, bool param_internal=true)
+        inline void set_objective(std::string calc_type, np::ndarray prop, np::ndarray sizes, int n_rung, int max_param_depth=100)
         {
             std::vector<double> prop_vec = python_conv_utils::from_ndarray<double>(prop);
-            return set_objective(calc_type, prop_vec.data(), python_conv_utils::from_ndarray<int>(sizes), n_rung, param_internal);
+            return set_objective(calc_type, prop_vec.data(), python_conv_utils::from_ndarray<int>(sizes), n_rung, max_param_depth);
         }
     #endif
 }
diff --git a/src/python/bindings_docstring_keyed.cpp b/src/python/bindings_docstring_keyed.cpp
index 0b554fd442530d358acaf134be92932a92d5d25e..413d35fa713bbefa18ddd6c108d7fb92432ec991 100644
--- a/src/python/bindings_docstring_keyed.cpp
+++ b/src/python/bindings_docstring_keyed.cpp
@@ -61,16 +61,16 @@ void sisso::register_all()
     def("initialize_values_arr", &node_value_arrs::initialize_values_arr);
     def("initialize_d_matrix_arr", &node_value_arrs::initialize_d_matrix_arr);
 
-    void (*set_objective_list_list)(std::string, py::list, py::list, int, bool) = &nlopt_wrapper::set_objective;
-    void (*set_objective_arr_list)(std::string, np::ndarray, py::list, int, bool) = &nlopt_wrapper::set_objective;
-    void (*set_objective_arr_arr)(std::string, np::ndarray, np::ndarray, int, bool) = &nlopt_wrapper::set_objective;
+    void (*set_objective_list_list)(std::string, py::list, py::list, int, int) = &nlopt_wrapper::set_objective;
+    void (*set_objective_arr_list)(std::string, np::ndarray, py::list, int, int) = &nlopt_wrapper::set_objective;
+    void (*set_objective_arr_arr)(std::string, np::ndarray, np::ndarray, int, int) = &nlopt_wrapper::set_objective;
 
     def("set_objective", set_objective_list_list);
     def("set_objective", set_objective_arr_list);
     def("set_objective", set_objective_arr_arr);
 
-    void(*setup_data_list)(py::list, int, int, bool) = &nlopt_wrapper::setup_data;
-    void(*setup_data_arr)(np::ndarray, int, int, bool) = &nlopt_wrapper::setup_data;
+    void(*setup_data_list)(py::list, int, int, int) = &nlopt_wrapper::setup_data;
+    void(*setup_data_arr)(np::ndarray, int, int, int) = &nlopt_wrapper::setup_data;
     def("setup_data", setup_data_list);
     def("setup_data", setup_data_arr);
 }
@@ -80,8 +80,8 @@ void sisso::feature_creation::registerFeatureSpace()
     void (FeatureSpace::*sis_list)(list) = &FeatureSpace::sis;
     void (FeatureSpace::*sis_ndarray)(np::ndarray) = &FeatureSpace::sis;
 
-    class_<FeatureSpace>("FeatureSpace", init<list, list, list, np::ndarray, list, optional<std::string, int, int, int, int, double, double, double, bool>>())
-        .def(init<list, list, list, list, list, optional<std::string, int, int, int, int, double, double, double, bool>>())
+    class_<FeatureSpace>("FeatureSpace", init<list, list, list, np::ndarray, list, optional<std::string, int, int, int, int, double, double, double, int>>())
+        .def(init<list, list, list, list, list, optional<std::string, int, int, int, int, double, double, double, int>>())
         .def(init<std::string, list, list, optional<std::string, int, double>>())
         .def("sis", sis_list, "@DocString_feat_space_sis_list@")
         .def("sis", sis_ndarray, "@DocString_feat_space_sis_arr@")
diff --git a/src/python/bindings_docstring_keyed.hpp b/src/python/bindings_docstring_keyed.hpp
index a505444633e291946d4edb1cd72b2de52133236d..b4bcb3b249c2a3a18a58b5919f1bd39d128e9fb4 100644
--- a/src/python/bindings_docstring_keyed.hpp
+++ b/src/python/bindings_docstring_keyed.hpp
@@ -41,18 +41,18 @@ namespace sisso
             {
             public:
                 inline std::string expr(){return this->get_override("expr")();}
-                inline std::string expr(double*){return this->get_override("expr")();}
+                inline std::string expr(double*, int depth=1){return this->get_override("expr")();}
                 inline Unit unit(){return this->get_override("unit")();}
                 inline std::vector<double> value(){return this->get_override("value")();}
                 inline std::vector<double> test_value(){return this->get_override("test_value")();}
-                inline void set_value(int offset = -1){this->get_override("set_value")();}
-                inline void set_test_value(int offset = -1){this->get_override("set_test_value")();}
-                inline double* value_ptr(int offset = -1){return this->get_override("value_ptr")();}
-                inline double* test_value_ptr(int offset = -1){return this->get_override("test_value_ptr")();}
-                inline void set_value(double* params, int offset = -1){this->get_override("set_value")();}
-                inline void set_test_value(double* params, int offset = -1){this->get_override("set_test_value")();}
-                inline double* value_ptr(double* params, int offset = -1){return this->get_override("value_ptr")();}
-                inline double* test_value_ptr(double* params, int offset = -1){return this->get_override("test_value_ptr")();}
+                inline void set_value(int offset=-1){this->get_override("set_value")();}
+                inline void set_test_value(int offset=-1){this->get_override("set_test_value")();}
+                inline double* value_ptr(int offset=-1){return this->get_override("value_ptr")();}
+                inline double* test_value_ptr(int offset=-1){return this->get_override("test_value_ptr")();}
+                inline void set_value(double* params, int offset=-1, int depth=1){this->get_override("set_value")();}
+                inline void set_test_value(double* params, int offset=-1, int depth=1){this->get_override("set_test_value")();}
+                inline double* value_ptr(double* params, int offset=-1, int depth=1){return this->get_override("value_ptr")();}
+                inline double* test_value_ptr(double* params, int offset=-1, int depth=1){return this->get_override("test_value_ptr")();}
                 inline bool is_nan(){return this->get_override("is_nan")();}
                 inline bool is_const(){return this->get_override("is_const")();}
                 inline NODE_TYPE type(){return this->get_override("type")();}
@@ -65,9 +65,9 @@ namespace sisso
                 inline void update_div_mult_leaves(std::map<std::string, double>& div_mult_leaves, double fact, double& expected_abs_tot){this->get_override("update_div_mult_leaves")();}
                 inline std::vector<double> parameters(){return this->get_override("parameters")();}
                 inline void set_parameters(std::vector<double>){this->get_override("set_parameters")();}
-                inline void set_bounds(double* lb, double* ub, int from_parent=2){this->get_override("set_bounds")();}
+                inline void set_bounds(double* lb, double* ub, int from_parent=2, int depth=1){this->get_override("set_bounds")();}
                 inline Domain domain(){return this->get_override("domain")();}
-                inline Domain domain(double* params){return this->get_override("domain")();}
+                inline Domain domain(double* params, int depth=1){return this->get_override("domain")();}
                 inline int n_feats(){this->get_override("n_feats");}
                 inline std::shared_ptr<Node> feat(int ind){this->get_override("feat");}
                 inline void param_derivative(const double* params, double* dfdp){this->get_override("param_derivative");}
@@ -81,24 +81,24 @@ namespace sisso
             template<int N>
             struct OperatorNodeWrap : OperatorNode<N>, py::wrapper<OperatorNode<N>>
             {
-                inline void set_value(int offset = -1){this->get_override("set_value")();}
-                inline void set_test_value(int offset = -1){this->get_override("set_test_value")();}
-                inline void set_value(const double* params, int offset = -1){this->get_override("set_value")();}
-                inline void set_test_value(const double* params, int offset = -1){this->get_override("set_test_value")();}
+                inline void set_value(int offset=-1){this->get_override("set_value")();}
+                inline void set_test_value(int offset=-1){this->get_override("set_test_value")();}
+                inline void set_value(const double* params, int offset=-1, int depth=1){this->get_override("set_value")();}
+                inline void set_test_value(const double* params, int offset=-1, int depth=1){this->get_override("set_test_value")();}
                 inline NODE_TYPE type(){return this->get_override("type")();}
                 inline int rung(int cur_rung = 0){return this->get_override("rung")();}
                 inline Unit unit(){return this->get_override("unit")();}
                 inline std::string get_postfix_term(){return this->get_override("get_postfix_term")();}
-                inline Domain domain(double* params){return this->get_override("domain")();}
+                inline Domain domain(double* params, int depth=1){return this->get_override("domain")();}
                 inline Domain domain(){return this->get_override("domain")();}
-                inline std::string expr(double* params){return this->get_override("expr")();}
+                inline std::string expr(double* params, int depth=1){return this->get_override("expr")();}
                 inline std::string expr(){return this->get_override("expr")();}
                 inline void update_add_sub_leaves(std::map<std::string, int>& add_sub_leaves, int pl_mn, int& expected_abs_tot){this->get_override("update_add_sub_leaves")();}
                 inline void update_div_mult_leaves(std::map<std::string, double>& div_mult_leaves, double fact, double& expected_abs_tot){this->get_override("update_div_mult_leaves")();}
                 inline void get_parameters(std::vector<double>& prop){this->get_override("get_parameters")();}
                 inline void set_parameters(std::vector<double>){this->get_override("set_parameters")();}
                 inline std::vector<double> parameters(){return this->get_override("parameters")();}
-                inline void set_bounds(double* lb, double* ub, int from_parent=2){this->get_override("set_bounds")();}
+                inline void set_bounds(double* lb, double* ub, int from_parent=2, int depth=1){this->get_override("set_bounds")();}
                 inline void param_derivative(const double* params, double* dfdp){this->get_override("param_derivative");}
             };
 
diff --git a/src/python/feature_creation/FeatureSpace.cpp b/src/python/feature_creation/FeatureSpace.cpp
index 5857678e7fb2dbe7137268f6250c926951cd48a2..d6b0bb853a234c36d7b4b8d736eca7fbbd9b7f01 100644
--- a/src/python/feature_creation/FeatureSpace.cpp
+++ b/src/python/feature_creation/FeatureSpace.cpp
@@ -14,7 +14,7 @@ FeatureSpace::FeatureSpace(
     double cross_corr_max,
     double min_abs_feat_val,
     double max_abs_feat_val,
-    bool param_internal
+    int max_param_depth
 ):
     _phi(python_conv_utils::shared_ptr_vec_from_list<Node, FeatureNode>(phi_0)),
     _phi_0(_phi),
@@ -36,7 +36,7 @@ FeatureSpace::FeatureSpace(
     _n_rung_store(max_store_rung),
     _n_rung_generate(n_rung_generate),
     _n_samp(_phi[0]->n_samp()),
-    _param_internal(param_internal)
+    _max_param_depth(_max_param_depth)
 {
     initialize_fs(project_type);
 }
@@ -55,7 +55,7 @@ FeatureSpace::FeatureSpace(
     double cross_corr_max,
     double min_abs_feat_val,
     double max_abs_feat_val,
-    bool param_internal
+    int max_param_depth
 ):
     _phi(python_conv_utils::shared_ptr_vec_from_list<Node, FeatureNode>(phi_0)),
     _phi_0(_phi),
@@ -77,7 +77,7 @@ FeatureSpace::FeatureSpace(
     _n_rung_store(max_store_rung),
     _n_rung_generate(n_rung_generate),
     _n_samp(_phi[0]->n_samp()),
-    _param_internal(param_internal)
+    _max_param_depth(_max_param_depth)
 {
     initialize_fs(project_type);
 }
@@ -104,7 +104,7 @@ FeatureSpace::FeatureSpace(
     _n_rung_store(0),
     _n_rung_generate(0),
     _n_samp(_phi_0[0]->n_samp()),
-    _param_internal(true)
+    _max_param_depth(100)
 {
     std::cout << "project: " << _phi_0.size() << std::endl;
     if(project_type.compare("regression") == 0)
@@ -171,6 +171,7 @@ FeatureSpace::FeatureSpace(
     std::cout << "create" << std::endl;
     std::vector<int> rung_inds = util_funcs::argsort(rungs);
     _max_phi = *std::max_element(rungs.begin(), rungs.end());
+    _max_param_depth = _max_phi;
     _phi[0] = phi_temp[rung_inds[0]];
     for(int ff = 1; ff < _n_feat; ++ff)
     {
diff --git a/src/python/postprocess/__init__.py b/src/python/postprocess/__init__.py
index b3e3616452ccd967911a706fdcff8ad1118df69c..3ec8f58d26171a0aa36169350991fdb154eabddd 100644
--- a/src/python/postprocess/__init__.py
+++ b/src/python/postprocess/__init__.py
@@ -38,7 +38,7 @@ def generate_plot(dir_expr, filename, fig_settings=None):
     )
     fig.subplots_adjust(**fig_config["subplots_adjust"])
 
-    if "y_lim" in fig_settings["axis_limits"]:
+    if "axis_limits" in fig_settings and "y_lim" in fig_settings["axis_limits"]:
         ax.set_ylim(fig_settings["axis_limits"]["y_lim"])
 
     ax.set_xlabel("Model Dimension")
@@ -53,8 +53,8 @@ def generate_plot(dir_expr, filename, fig_settings=None):
         "whiskers": fig_config["features"]["whiskers"],
         "mean_markers": fig_config["features"]["mean_marker_type"],
         "median_markers": fig_config["features"]["median_marker_type"],
-        "showcaps": fig_config["features"]["show_box_outliers"],
-        "showfliers": fig_config["features"]["show_box_caps"],
+        "showcaps": fig_config["features"]["show_box_caps"],
+        "showfliers": fig_config["features"]["show_box_outliers"],
     }
 
     if fig_config["plot_options"]["type"] == "split":
@@ -71,15 +71,15 @@ def generate_plot(dir_expr, filename, fig_settings=None):
                     data,
                     np.column_stack(
                         (
-                            np.ones(train_error.shape) * nn,
+                            np.ones(train_error.shape) * (nn + 1),
                             train_error,
                             np.zeros(train_error.shape),
                         )
                     ),
                 )
             )
-            mean_data = np.vstack((mean_data, [nn, train_error.mean(), 0.0]))
-            median_data = np.vstack((median_data, [nn, np.median(train_error), 0.0]))
+            mean_data = np.vstack((mean_data, [nn + 1, train_error.mean(), 0.0]))
+            median_data = np.vstack((median_data, [nn + 1, np.median(train_error), 0.0]))
 
             test_error = np.array(
                 [np.abs(model.test_error) for model in models[nn]]
@@ -96,8 +96,8 @@ def generate_plot(dir_expr, filename, fig_settings=None):
                     ),
                 )
             )
-            mean_data = np.vstack((mean_data, [nn, test_error.mean(), 1.0]))
-            median_data = np.vstack((median_data, [nn, np.median(test_error), 1.0]))
+            mean_data = np.vstack((mean_data, [nn + 1, test_error.mean(), 1.0]))
+            median_data = np.vstack((median_data, [nn + 1, np.median(test_error), 1.0]))
 
         # Populate plot_settings
         plot_settings["colors"] = [
@@ -135,8 +135,8 @@ def generate_plot(dir_expr, filename, fig_settings=None):
                     ),
                 )
             )
-            mean_data = np.vstack((mean_data, [nn, train_error.mean(), 0.0]))
-            median_data = np.vstack((median_data, [nn, np.median(train_error), 0.0]))
+            mean_data = np.vstack((mean_data, [nn + 1, train_error.mean(), 0.0]))
+            median_data = np.vstack((median_data, [nn + 1, np.median(train_error), 0.0]))
 
         # Populate plot_settings
         plot_settings["colors"] = [fig_config["colors"]["train"]]
@@ -166,8 +166,8 @@ def generate_plot(dir_expr, filename, fig_settings=None):
                     ),
                 )
             )
-            mean_data = np.vstack((mean_data, [nn, test_error.mean(), 1.0]))
-            median_data = np.vstack((median_data, [nn, np.median(test_error), 1.0]))
+            mean_data = np.vstack((mean_data, [nn + 1, test_error.mean(), 1.0]))
+            median_data = np.vstack((median_data, [nn + 1, np.median(test_error), 1.0]))
 
         # Populate plot_settings
         plot_settings["colors"] = [fig_config["colors"]["test"]]
@@ -255,7 +255,7 @@ def add_violin(ax, data, plot_settings):
         fig_config (adict): The settings for the figure
     """
     sns.violinplot(
-        x=data[:, 0],
+        x=data[:, 0].astype(np.int64),
         y=data[:, 1],
         hue=data[:, 2],
         split=plot_settings["split"],
@@ -276,7 +276,7 @@ def add_boxplot(ax, data, plot_settings):
         fig_config (adict): The settings for the figure
     """
     sns.boxplot(
-        x=data[:, 0],
+        x=data[:, 0].astype(np.int64),
         y=data[:, 1],
         hue=data[:, 2],
         palette=plot_settings["box_colors"],
@@ -313,7 +313,7 @@ def add_pointplot(ax, data, plot_settings):
         fig_config (adict): The settings for the figure
     """
     sns.pointplot(
-        x=data[:, 0],
+        x=data[:, 0].astype(np.int64),
         y=data[:, 1],
         hue=data[:, 2],
         join=plot_settings["join"],