From e0ccc620a858e1f85d23382d9e5e91fd1ba3c033 Mon Sep 17 00:00:00 2001
From: Thomas Purcell <purcell@fhi-berlin.mpg.de>
Date: Wed, 19 Aug 2020 12:01:55 +0200
Subject: [PATCH] Add explicit feature generation tests

Make sure every error case is actually hit
---
 tests/.~lock.data.csv#                        |   1 -
 .../test_abs_diff_node.py                     |  74 +++++++++
 tests/test_feat_generation/test_abs_node.py   |  54 +++++++
 tests/test_feat_generation/test_add_node.py   |  73 +++++++++
 tests/test_feat_generation/test_cb_node.py    |  61 ++++++++
 tests/test_feat_generation/test_cbrt_node.py  |  77 ++++++++++
 tests/test_feat_generation/test_cos_node.py   |  68 +++++++++
 tests/test_feat_generation/test_div_node.py   |  90 +++++++++++
 tests/test_feat_generation/test_exp_node.py   |  81 ++++++++++
 tests/test_feat_generation/test_inv_node.py   |  69 +++++++++
 tests/test_feat_generation/test_log_node.py   | 141 ++++++++++++++++++
 tests/test_feat_generation/test_mult_node.py  |  87 +++++++++++
 .../test_feat_generation/test_neg_exp_node.py |  85 +++++++++++
 tests/test_feat_generation/test_sin_node.py   |  67 +++++++++
 .../test_feat_generation/test_six_pow_node.py |  92 ++++++++++++
 tests/test_feat_generation/test_sq_node.py    |  62 ++++++++
 tests/test_feat_generation/test_sqrt_node.py  |  84 +++++++++++
 tests/test_feat_generation/test_sub_node.py   |  74 +++++++++
 tests/test_sisso.py                           |   2 +-
 19 files changed, 1340 insertions(+), 2 deletions(-)
 delete mode 100644 tests/.~lock.data.csv#
 create mode 100644 tests/test_feat_generation/test_abs_diff_node.py
 create mode 100644 tests/test_feat_generation/test_abs_node.py
 create mode 100644 tests/test_feat_generation/test_add_node.py
 create mode 100644 tests/test_feat_generation/test_cb_node.py
 create mode 100644 tests/test_feat_generation/test_cbrt_node.py
 create mode 100644 tests/test_feat_generation/test_cos_node.py
 create mode 100644 tests/test_feat_generation/test_div_node.py
 create mode 100644 tests/test_feat_generation/test_exp_node.py
 create mode 100644 tests/test_feat_generation/test_inv_node.py
 create mode 100644 tests/test_feat_generation/test_log_node.py
 create mode 100644 tests/test_feat_generation/test_mult_node.py
 create mode 100644 tests/test_feat_generation/test_neg_exp_node.py
 create mode 100644 tests/test_feat_generation/test_sin_node.py
 create mode 100644 tests/test_feat_generation/test_six_pow_node.py
 create mode 100644 tests/test_feat_generation/test_sq_node.py
 create mode 100644 tests/test_feat_generation/test_sqrt_node.py
 create mode 100644 tests/test_feat_generation/test_sub_node.py

diff --git a/tests/.~lock.data.csv# b/tests/.~lock.data.csv#
deleted file mode 100644
index 607a7a6e..00000000
--- a/tests/.~lock.data.csv#
+++ /dev/null
@@ -1 +0,0 @@
-,purcell,theobook151,20.07.2020 15:50,file:///home/purcell/.config/libreoffice/4;
\ No newline at end of file
diff --git a/tests/test_feat_generation/test_abs_diff_node.py b/tests/test_feat_generation/test_abs_diff_node.py
new file mode 100644
index 00000000..cb3afab0
--- /dev/null
+++ b/tests/test_feat_generation/test_abs_diff_node.py
@@ -0,0 +1,74 @@
+from cpp_sisso import FeatureNode, AddNode, SubNode, AbsDiffNode, Unit
+
+import numpy as np
+
+
+class InvalidFeatureMade(Exception):
+    pass
+
+
+def test_abs_diff_node():
+    data_1 = np.random.random(90) * 1e4 + 1e-10
+    test_data_1 = np.random.random(10) * 1e4 + 1e-10
+
+    data_2 = np.random.random(90) * 2e4 - 1e4
+    test_data_2 = np.random.random(10) * 2e4 - 1e4
+
+    data_3 = np.random.random(90) * 1e10 + 1e-10
+    test_data_3 = np.random.random(10) * 1e10 + 1e-10
+
+    feat_1 = FeatureNode(0, "t_a", data_1, test_data_1, Unit("s"))
+    feat_2 = FeatureNode(1, "x_a", data_2, test_data_2, Unit("m"))
+    feat_3 = FeatureNode(2, "t_b", data_3, test_data_3, Unit("s"))
+    feat_4 = FeatureNode(3, "t_c", data_3, test_data_3, Unit("s"))
+    feats = []
+    try:
+        feats.append(AbsDiffNode(feat_1, feat_2, 4, 1e-50, 1e50))
+        raise InvalidFeatureMade("Absolute difference with mismatching units")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(AbsDiffNode(feat_3, feat_4, 4, 1e-50, 1e50))
+        raise InvalidFeatureMade("Absolute Difference leading to a constant feature")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(AbsDiffNode(feat_1, feat_2, 4, 1e-50, 1e0))
+        raise InvalidFeatureMade("Absolute Difference outside of user specified bounds")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(AbsDiffNode(feat_1, feat_2, 4, 1e0, 1e50))
+        raise InvalidFeatureMade("Absolute Difference outside of user specified bounds")
+    except RuntimeError:
+        pass
+
+    feats.append(AddNode(feat_1, feat_3, 5, 1e-50, 1e50))
+    feats.append(SubNode(feat_1, feat_3, 6, 1e-50, 1e50))
+    feats.append(SubNode(feat_3, feat_1, 7, 1e-50, 1e50))
+    feats.append(AbsDiffNode(feat_3, feat_1, 8, 1e-50, 1e50))
+
+    try:
+        feats.append(AbsDiffNode(feats[0], feats[1], 9, 1e-50, 1e50))
+        raise InvalidFeatureMade("Absolute difference with canceled out terms")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(AbsDiffNode(feat_1, feat_1, 9, 1e-50, 1e50))
+        raise InvalidFeatureMade("Taking the absolute difference of the same feature")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(AbsDiffNode(feats[-3], feats[-2], 9, 1e-50, 1e50))
+        raise InvalidFeatureMade("Absolute difference with non-one prefactor")
+    except RuntimeError:
+        pass
+
+
+if __name__ == "__main__":
+    test_abs_diff_node()
diff --git a/tests/test_feat_generation/test_abs_node.py b/tests/test_feat_generation/test_abs_node.py
new file mode 100644
index 00000000..1a674870
--- /dev/null
+++ b/tests/test_feat_generation/test_abs_node.py
@@ -0,0 +1,54 @@
+from cpp_sisso import FeatureNode, AbsNode, AbsDiffNode, Unit
+
+import numpy as np
+
+
+class InvalidFeatureMade(Exception):
+    pass
+
+
+def test_abs_node():
+    data_1 = np.random.random(90) * 1e4 + 1e-10
+    test_data_1 = np.random.random(10) * 1e4 + 1e-10
+
+    data_2 = np.random.choice([1.0, -1.0], 90)
+    test_data_2 = np.random.choice([1.0, -1.0], 10)
+
+    feat_1 = FeatureNode(0, "t_a", data_1, test_data_1, Unit())
+    feat_2 = FeatureNode(1, "x_a", data_2, test_data_2, Unit())
+    feats = []
+
+    try:
+        feats.append(AbsNode(feat_2, 2, 1e-50, 1e50))
+        raise InvalidFeatureMade(
+            "Taking the absolute value leading to a constant feature"
+        )
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(AbsNode(feat_1, 2, 1e-50, 1e-1))
+        raise InvalidFeatureMade(
+            "Taking the absolute value outside of user specified bounds"
+        )
+    except RuntimeError:
+        pass
+
+    feats.append(AbsNode(feat_1, 3, 1e-50, 1e50))
+    feats.append(AbsDiffNode(feat_1, feat_2, 4, 1e-50, 1e50))
+
+    try:
+        feats.append(AbsNode(feats[0], 5, 1e-50, 1e50))
+        raise InvalidFeatureMade("Taking the absolute value of an absolute value")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(AbsNode(feats[1], 5, 1e-50, 1e50))
+        raise InvalidFeatureMade("Taking the absolute value of an absolute difference")
+    except RuntimeError:
+        pass
+
+
+if __name__ == "__main__":
+    test_abs_node()
diff --git a/tests/test_feat_generation/test_add_node.py b/tests/test_feat_generation/test_add_node.py
new file mode 100644
index 00000000..821f7bbe
--- /dev/null
+++ b/tests/test_feat_generation/test_add_node.py
@@ -0,0 +1,73 @@
+from cpp_sisso import FeatureNode, AddNode, SubNode, Unit
+
+import numpy as np
+
+
+class InvalidFeatureMade(Exception):
+    pass
+
+
+def test_add_node():
+    data_1 = np.random.random(90) * 1e4 + 1e-10
+    test_data_1 = np.random.random(10) * 1e4 + 1e-10
+
+    data_2 = np.random.random(90) * 2e4 - 1e4
+    test_data_2 = np.random.random(10) * 2e4 - 1e4
+
+    data_3 = np.random.random(90) * 1e10 + 1e-10
+    test_data_3 = np.random.random(10) * 1e10 + 1e-10
+
+    feat_1 = FeatureNode(0, "t_a", data_1, test_data_1, Unit("s"))
+    feat_2 = FeatureNode(1, "x_a", data_2, test_data_2, Unit("m"))
+    feat_3 = FeatureNode(2, "t_b", data_3, test_data_3, Unit("s"))
+    feat_4 = FeatureNode(3, "t_c", -1.0 * data_3, -1.0 * test_data_3, Unit("s"))
+
+    feats = []
+    try:
+        feats.append(AddNode(feat_1, feat_2, 4, 1e-50, 1e50))
+        raise InvalidFeatureMade("Addition with mismatching units")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(AddNode(feat_3, feat_4, 4, 1e-50, 1e50))
+        raise InvalidFeatureMade("Addition leading to a constant feature")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(AddNode(feat_1, feat_2, 4, 1e-50, 1e0))
+        raise InvalidFeatureMade("Addition outside of user specified bounds")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(AddNode(feat_1, feat_2, 4, 1e-1, 1e50))
+        raise InvalidFeatureMade("Addition outside of user specified bounds")
+    except RuntimeError:
+        pass
+
+    feats.append(AddNode(feat_1, feat_3, 4, 1e-50, 1e50))
+    feats.append(SubNode(feat_1, feat_3, 5, 1e-50, 1e50))
+
+    try:
+        feats.append(AddNode(feats[0], feats[1], 6, 1e-50, 1e50))
+        raise InvalidFeatureMade("Addition with canceled out terms")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(AddNode(feat_1, feat_1, 6, 1e-50, 1e50))
+        raise InvalidFeatureMade("Adding the same feature")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(AddNode(feats[0], feats[0], 6, 1e-50, 1e50))
+        raise InvalidFeatureMade("Addition with non-one prefactor")
+    except RuntimeError:
+        pass
+
+
+if __name__ == "__main__":
+    test_add_node()
diff --git a/tests/test_feat_generation/test_cb_node.py b/tests/test_feat_generation/test_cb_node.py
new file mode 100644
index 00000000..1e4ebc8c
--- /dev/null
+++ b/tests/test_feat_generation/test_cb_node.py
@@ -0,0 +1,61 @@
+from cpp_sisso import FeatureNode, InvNode, SqNode, CbNode, SixPowNode, CbrtNode, Unit
+
+import numpy as np
+
+
+class InvalidFeatureMade(Exception):
+    pass
+
+
+def test_cube_node():
+    data_1 = np.random.random(90) * 1e4 + 1e-10
+    test_data_1 = np.random.random(10) * 1e4 + 1e-10
+
+    data_2 = np.random.random(90) * 2 - 1
+    test_data_2 = np.random.random(10) * 2 - 1
+
+    feat_1 = FeatureNode(0, "t_a", data_1, test_data_1, Unit())
+    feat_2 = FeatureNode(1, "x_a", data_2, test_data_2, Unit())
+
+    feats = []
+    try:
+        feats.append(CbNode(feat_1, 3, 1e-50, 1e-10))
+        raise InvalidFeatureMade(
+            "Taking the cube of the feature leads to values outside of user specified bounds"
+        )
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(CbNode(feat_2, 3, 1e0, 1e50))
+        raise InvalidFeatureMade(
+            "Taking the cube of the feature leads to values outside of user specified bounds"
+        )
+    except RuntimeError:
+        pass
+
+    feats.append(InvNode(feat_1, 5, 1e-50, 1e50))
+    feats.append(SqNode(feat_2, 6, 1e-50, 1e50))
+    feats.append(CbrtNode(feat_1, 7, 1e-50, 1e50))
+
+    try:
+        feats.append(CbNode(feats[0], 13, 1e-50, 1e50))
+        raise InvalidFeatureMade("Taking the cube of a InvNode")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(CbNode(feats[1], 13, 1e-50, 1e50))
+        raise InvalidFeatureMade("Taking the cube of an SqNode")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(CbNode(feats[2], 13, 1e-50, 1e50))
+        raise InvalidFeatureMade("Taking the cube of a CbrtNode")
+    except RuntimeError:
+        pass
+
+
+if __name__ == "__main__":
+    test_cube_node()
diff --git a/tests/test_feat_generation/test_cbrt_node.py b/tests/test_feat_generation/test_cbrt_node.py
new file mode 100644
index 00000000..815e7d24
--- /dev/null
+++ b/tests/test_feat_generation/test_cbrt_node.py
@@ -0,0 +1,77 @@
+from cpp_sisso import (
+    FeatureNode,
+    InvNode,
+    SqNode,
+    CbNode,
+    SixPowNode,
+    SqrtNode,
+    CbrtNode,
+    Unit,
+)
+
+import numpy as np
+
+
+class InvalidFeatureMade(Exception):
+    pass
+
+
+def test_cbrt_node():
+    data_1 = np.random.random(90) * 1e4 + 1e-10
+    test_data_1 = np.random.random(10) * 1e4 + 1e-10
+
+    data_2 = np.random.random(90) * 2 - 1
+    test_data_2 = np.random.random(10) * 2 - 1
+
+    feat_1 = FeatureNode(0, "t_a", data_1, test_data_1, Unit())
+    feat_2 = FeatureNode(1, "x_a", data_2, test_data_2, Unit())
+
+    feats = []
+    try:
+        feats.append(CbrtNode(feat_1, 3, 1e-50, 1e-10))
+        raise InvalidFeatureMade(
+            "Taking the cbrt of the feature leads to values outside of user specified bounds"
+        )
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(CbrtNode(feat_1, 3, 1e4, 1e50))
+        raise InvalidFeatureMade(
+            "Taking the cbrt of the feature leads to values outside of user specified bounds"
+        )
+    except RuntimeError:
+        pass
+
+    feats.append(InvNode(feat_1, 5, 1e-50, 1e50))
+    feats.append(SqNode(feat_2, 6, 1e-50, 1e50))
+    feats.append(CbNode(feat_1, 7, 1e-50, 1e50))
+    feats.append(SixPowNode(feat_2, 8, 1e-50, 1e50))
+
+    try:
+        feats.append(CbrtNode(feats[0], 13, 1e-50, 1e50))
+        raise InvalidFeatureMade("Taking the cbrt of a InvNode")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(CbrtNode(feats[1], 13, 1e-50, 1e50))
+        raise InvalidFeatureMade("Taking the cbrt of an SqNode")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(CbrtNode(feats[2], 13, 1e-50, 1e50))
+        raise InvalidFeatureMade("Taking the cbrt of a CbNode")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(CbrtNode(feats[3], 13, 1e-50, 1e50))
+        raise InvalidFeatureMade("Taking the cbrt of a SixPowNode")
+    except RuntimeError:
+        pass
+
+
+if __name__ == "__main__":
+    test_cbrt_node()
diff --git a/tests/test_feat_generation/test_cos_node.py b/tests/test_feat_generation/test_cos_node.py
new file mode 100644
index 00000000..63ef07ff
--- /dev/null
+++ b/tests/test_feat_generation/test_cos_node.py
@@ -0,0 +1,68 @@
+from cpp_sisso import FeatureNode, SinNode, CosNode, Unit
+
+import numpy as np
+
+
+class InvalidFeatureMade(Exception):
+    pass
+
+
+def test_cos_node():
+    data_1 = np.random.randint(0, 10000, 90) * 2.0 * np.pi
+    test_data_1 = np.random.randint(0, 10000, 10) * 2.0 * np.pi
+
+    data_2 = np.random.random(90) * 2e4 - 1e4
+    test_data_2 = np.random.random(10) * 2e4 - 1e4
+
+    feat_1 = FeatureNode(0, "t_a", data_1, test_data_1, Unit())
+    feat_2 = FeatureNode(1, "x_a", data_2, test_data_2, Unit())
+    feat_3 = FeatureNode(2, "m_c", data_2, test_data_2, Unit("m"))
+    feats = []
+
+    try:
+        print(np.cos(data_1))
+        feats.append(CosNode(feat_1, 3, 1e-50, 1e50))
+        raise InvalidFeatureMade("Taking the cosine leads to a constant feature")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(CosNode(feat_2, 3, 1e-50, 1e-10))
+        raise InvalidFeatureMade(
+            "Taking the cosine leads to values outside of user specified bounds"
+        )
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(CosNode(feat_2, 3, 1e0, 1e50))
+        raise InvalidFeatureMade(
+            "Taking the cosine leads to values outside of user specified bounds"
+        )
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(CosNode(feat_3, 3, 1e-50, 1e50))
+        raise InvalidFeatureMade("Taking the cosine of united feature")
+    except RuntimeError:
+        pass
+
+    feats.append(SinNode(feat_2, 3, 1e-50, 1e50))
+    feats.append(CosNode(feat_2, 4, 1e-50, 1e50))
+
+    try:
+        feats.append(CosNode(feats[0], 5, 1e-50, 1e50))
+        raise InvalidFeatureMade("Taking the cosine of a sine")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(CosNode(feats[1], 5, 1e-50, 1e50))
+        raise InvalidFeatureMade("Taking the cosine of a cosine")
+    except RuntimeError:
+        pass
+
+
+if __name__ == "__main__":
+    test_cos_node()
diff --git a/tests/test_feat_generation/test_div_node.py b/tests/test_feat_generation/test_div_node.py
new file mode 100644
index 00000000..1bc81bdc
--- /dev/null
+++ b/tests/test_feat_generation/test_div_node.py
@@ -0,0 +1,90 @@
+from cpp_sisso import FeatureNode, MultNode, DivNode, InvNode, Unit
+
+import numpy as np
+
+
+class InvalidFeatureMade(Exception):
+    pass
+
+
+def test_div_node():
+    data_1 = np.random.random(90) * 1e4 + 1e-10
+    test_data_1 = np.random.random(10) * 1e4 + 1e-10
+
+    data_2 = np.random.random(90) * 2e4 - 1e4
+    test_data_2 = np.random.random(10) * 2e4 - 1e4
+
+    data_3 = np.random.random(90) * 1e10 + 1e-10
+    test_data_3 = np.random.random(10) * 1e10 + 1e-10
+
+    feat_1 = FeatureNode(0, "t_a", data_1, test_data_1, Unit("s"))
+    feat_2 = FeatureNode(1, "x_a", data_2, test_data_2, Unit("m"))
+    feat_3 = FeatureNode(2, "t_b", data_3, test_data_3, Unit("s"))
+    feat_4 = FeatureNode(3, "t_c", data_3, test_data_3, Unit("1/s"))
+    feat_5 = FeatureNode(4, "x_b", data_2, test_data_2, Unit("m"))
+
+    feats = []
+    try:
+        feats.append(DivNode(feat_3, feat_4, 5, 1e-50, 1e50))
+        raise InvalidFeatureMade("Division leading to a constant feature")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(DivNode(feat_1, feat_3, 5, 1e-50, 1e-10))
+        raise InvalidFeatureMade("Division outside of user specified bounds")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(DivNode(feat_1, feat_3, 5, 1e-1, 1e50))
+        raise InvalidFeatureMade("Division outside of user specified bounds")
+    except RuntimeError:
+        pass
+
+    feats.append(MultNode(feat_1, feat_2, 5, 1e-50, 1e50))
+    feats.append(DivNode(feat_1, feat_3, 6, 1e-50, 1e50))
+    feats.append(InvNode(feat_1, 7, 1e-50, 1e50))
+
+    try:
+        feats.append(DivNode(feat_3, feats[1], 8, 1e-50, 1e50))
+        raise InvalidFeatureMade("Division with a division feature")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(DivNode(feats[2], feat_3, 8, 1e-50, 1e50))
+        raise InvalidFeatureMade("Division with an inverse feature")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(DivNode(feat_3, feats[2], 8, 1e-50, 1e50))
+        raise InvalidFeatureMade("Division with an inverse feature")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(DivNode(feats[0], feats[1], 8, 1e-50, 1e50))
+        raise InvalidFeatureMade("Division with canceled out terms")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(DivNode(feat_1, feat_1, 8, 1e-50, 1e50))
+        raise InvalidFeatureMade("Dividing the same feature")
+    except RuntimeError:
+        pass
+
+    feats.append(DivNode(feat_3, feat_1, 8, 1e-50, 1e50))
+    try:
+        feats.append(DivNode(feats[0], feats[-1], 9, 1e-50, 1e50))
+        raise InvalidFeatureMade("Division with non-one prefactor")
+    except RuntimeError:
+        pass
+
+    feats.append(DivNode(feat_3, feat_5, 9, 1e-50, 1e50))
+
+
+if __name__ == "__main__":
+    test_div_node()
diff --git a/tests/test_feat_generation/test_exp_node.py b/tests/test_feat_generation/test_exp_node.py
new file mode 100644
index 00000000..da250382
--- /dev/null
+++ b/tests/test_feat_generation/test_exp_node.py
@@ -0,0 +1,81 @@
+from cpp_sisso import FeatureNode, ExpNode, NegExpNode, LogNode, AddNode, SubNode, Unit
+
+import numpy as np
+
+
+class InvalidFeatureMade(Exception):
+    pass
+
+
+def test_exp_node():
+    data_1 = np.random.random(90) + 1e-10
+    test_data_1 = np.random.random(10) + 1e-10
+
+    data_2 = np.random.random(90) * 2 - 1
+    test_data_2 = np.random.random(10) * 2 - 1
+
+    data_3 = np.random.random(90) * 10.0 + 1e-10
+    test_data_3 = np.random.random(10) * 10.0 + 1e-10
+
+    feat_1 = FeatureNode(0, "t_a", data_1, test_data_1, Unit())
+    feat_2 = FeatureNode(1, "x_a", data_2, test_data_2, Unit())
+    feat_3 = FeatureNode(2, "t_b", data_3, test_data_3, Unit("m"))
+
+    feats = []
+    try:
+        feats.append(ExpNode(feat_1, 3, 1e-50, 1e-10))
+        raise InvalidFeatureMade("Exponentiating outside of user specified bounds")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(ExpNode(feat_1, 3, 1e1, 1e50))
+        raise InvalidFeatureMade("Exponentiating outside of user specified bounds")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(ExpNode(feat_3, 3, 1e-50, 1e50))
+        raise InvalidFeatureMade("Exponentiating a united quantity")
+    except RuntimeError:
+        pass
+
+    feats.append(ExpNode(feat_2, 3, 1e-50, 1e50))
+    feats.append(NegExpNode(feat_2, 4, 1e-50, 1e50))
+    feats.append(LogNode(feat_1, 5, 1e-50, 1e50))
+    feats.append(AddNode(feat_1, feat_2, 6, 1e-50, 1e50))
+    feats.append(SubNode(feat_1, feat_2, 7, 1e-50, 1e50))
+
+    try:
+        feats.append(ExpNode(feats[0], 8, 1e-50, 1e50))
+        raise InvalidFeatureMade("Exponentiating an ExpNode")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(ExpNode(feats[1], 8, 1e-50, 1e50))
+        raise InvalidFeatureMade("Exponentiating a NegExpNode")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(ExpNode(feats[2], 8, 1e-50, 1e50))
+        raise InvalidFeatureMade("Exponentiating a LogNode")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(ExpNode(feats[3], 8, 1e-50, 1e50))
+        raise InvalidFeatureMade("Exponentiating an AddNode")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(ExpNode(feats[4], 8, 1e-50, 1e50))
+        raise InvalidFeatureMade("Exponentiating a SubNode")
+    except RuntimeError:
+        pass
+
+
+if __name__ == "__main__":
+    test_exp_node()
diff --git a/tests/test_feat_generation/test_inv_node.py b/tests/test_feat_generation/test_inv_node.py
new file mode 100644
index 00000000..bda6b405
--- /dev/null
+++ b/tests/test_feat_generation/test_inv_node.py
@@ -0,0 +1,69 @@
+from cpp_sisso import FeatureNode, ExpNode, NegExpNode, DivNode, InvNode, Unit
+
+import numpy as np
+
+
+class InvalidFeatureMade(Exception):
+    pass
+
+
+def test_inv_node():
+    data_1 = np.random.random(90) * 1e4 + 1e-10
+    test_data_1 = np.random.random(10) * 1e4 + 1e-10
+
+    data_2 = np.random.random(90) * 2 - 1
+    test_data_2 = np.random.random(10) * 2 - 1
+
+    data_3 = np.random.random(90) * 1e10 + 1e-10
+    test_data_3 = np.random.random(10) * 1e10 + 1e-10
+
+    feat_1 = FeatureNode(0, "t_a", data_1, test_data_1, Unit())
+    feat_2 = FeatureNode(1, "x_a", data_2, test_data_2, Unit())
+    feat_3 = FeatureNode(2, "t_b", data_3, test_data_3, Unit())
+    feat_4 = FeatureNode(3, "x_b", data_2, test_data_2, Unit("m"))
+
+    feats = []
+    try:
+        feats.append(InvNode(feat_1, 4, 1e-50, 1e-10))
+        raise InvalidFeatureMade("Inversion outside of user specified bounds")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(InvNode(feat_1, 4, 1e1, 1e50))
+        raise InvalidFeatureMade("Inversion outside of user specified bounds")
+    except RuntimeError:
+        pass
+
+    feats.append(InvNode(feat_4, 4, 1e-50, 1e50))
+    feats.append(ExpNode(feat_2, 5, 1e-50, 1e50))
+    feats.append(NegExpNode(feat_2, 6, 1e-50, 1e50))
+    feats.append(DivNode(feat_1, feat_3, 7, 1e-50, 1e50))
+
+    try:
+        feats.append(InvNode(feats[0], 8, 1e-50, 1e50))
+        raise InvalidFeatureMade("Attempting to invert an InvNode")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(InvNode(feats[1], 8, 1e-50, 1e50))
+        raise InvalidFeatureMade("Attempting to invert an ExpNode")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(InvNode(feats[2], 8, 1e-50, 1e50))
+        raise InvalidFeatureMade("Attempting to invert an NegExpNode")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(InvNode(feats[3], 8, 1e-50, 1e50))
+        raise InvalidFeatureMade("Attempting to invert an DivNode")
+    except RuntimeError:
+        pass
+
+
+if __name__ == "__main__":
+    test_inv_node()
diff --git a/tests/test_feat_generation/test_log_node.py b/tests/test_feat_generation/test_log_node.py
new file mode 100644
index 00000000..04870cc7
--- /dev/null
+++ b/tests/test_feat_generation/test_log_node.py
@@ -0,0 +1,141 @@
+from cpp_sisso import (
+    FeatureNode,
+    ExpNode,
+    NegExpNode,
+    LogNode,
+    MultNode,
+    DivNode,
+    InvNode,
+    SqNode,
+    CbNode,
+    SixPowNode,
+    SqrtNode,
+    CbrtNode,
+    Unit,
+)
+
+import numpy as np
+
+
+class InvalidFeatureMade(Exception):
+    pass
+
+
+def test_log_node():
+    data_1 = np.random.random(90) * 1e4 + 1e-10
+    test_data_1 = np.random.random(10) * 1e4 + 1e-10
+
+    data_2 = np.random.random(90) * 2 - 1
+    test_data_2 = np.random.random(10) * 2 - 1
+
+    data_3 = np.random.random(90) * 1.0 + 1e-10
+    test_data_3 = np.random.random(10) * 1.0 + 1e-10
+
+    feat_1 = FeatureNode(0, "t_a", data_1, test_data_1, Unit())
+    feat_2 = FeatureNode(1, "x_a", data_2, test_data_2, Unit())
+    feat_3 = FeatureNode(2, "t_b", data_3, test_data_3, Unit("m"))
+
+    feats = []
+    try:
+        feats.append(LogNode(feat_1, 3, 1e-50, 1e-10))
+        raise InvalidFeatureMade(
+            "Taking the log of the feature leads to values outside of user specified bounds"
+        )
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(LogNode(feat_1, 3, 1e1, 1e50))
+        raise InvalidFeatureMade(
+            "Taking the log of the feature leads to values outside of user specified bounds"
+        )
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(LogNode(feat_3, 3, 1e-50, 1e50))
+        raise InvalidFeatureMade("Taking the log of a united quantity")
+    except RuntimeError:
+        pass
+
+    feats.append(ExpNode(feat_2, 3, 1e-50, 1e50))
+    feats.append(NegExpNode(feat_2, 4, 1e-50, 1e50))
+    feats.append(LogNode(feat_1, 5, 1e-50, 1e50))
+    feats.append(MultNode(feat_1, feats[0], 6, 1e-50, 1e50))
+    feats.append(DivNode(feat_1, feats[0], 7, 1e-50, 1e50))
+    feats.append(InvNode(feat_1, 7, 1e-50, 1e50))
+    feats.append(CbrtNode(feat_1, 8, 1e-50, 1e50))
+    feats.append(SqrtNode(feat_1, 9, 1e-50, 1e50))
+    feats.append(SqNode(feat_2, 10, 1e-50, 1e50))
+    feats.append(CbNode(feat_1, 11, 1e-50, 1e50))
+    feats.append(SixPowNode(feat_2, 12, 1e-50, 1e50))
+
+    try:
+        feats.append(LogNode(feats[0], 13, 1e-50, 1e50))
+        raise InvalidFeatureMade("Taking the log of an ExpNode")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(LogNode(feats[1], 13, 1e-50, 1e50))
+        raise InvalidFeatureMade("Taking the log of a NegExpNode")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(LogNode(feats[2], 13, 1e-50, 1e50))
+        raise InvalidFeatureMade("Taking the log of a LogNode")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(LogNode(feats[3], 13, 1e-50, 1e50))
+        raise InvalidFeatureMade("Taking the log of an MultNode")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(LogNode(feats[4], 13, 1e-50, 1e50))
+        raise InvalidFeatureMade("Taking the log of a DivNode")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(LogNode(feats[5], 13, 1e-50, 1e50))
+        raise InvalidFeatureMade("Taking the log of a InvNode")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(LogNode(feats[6], 13, 1e-50, 1e50))
+        raise InvalidFeatureMade("Taking the log of a CbrtNode")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(LogNode(feats[7], 13, 1e-50, 1e50))
+        raise InvalidFeatureMade("Taking the log of a SqrtNode")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(LogNode(feats[8], 13, 1e-50, 1e50))
+        raise InvalidFeatureMade("Taking the log of an SqNode")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(LogNode(feats[9], 13, 1e-50, 1e50))
+        raise InvalidFeatureMade("Taking the log of a CbNode")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(LogNode(feats[10], 13, 1e-50, 1e50))
+        raise InvalidFeatureMade("Taking the log of a SixPowNode")
+    except RuntimeError:
+        pass
+
+
+if __name__ == "__main__":
+    test_log_node()
diff --git a/tests/test_feat_generation/test_mult_node.py b/tests/test_feat_generation/test_mult_node.py
new file mode 100644
index 00000000..d4e28c00
--- /dev/null
+++ b/tests/test_feat_generation/test_mult_node.py
@@ -0,0 +1,87 @@
+from cpp_sisso import FeatureNode, MultNode, DivNode, InvNode, Unit
+
+import numpy as np
+
+
+class InvalidFeatureMade(Exception):
+    pass
+
+
+def test_mult_node():
+    data_1 = np.random.random(90) * 1e4 + 1e-10
+    test_data_1 = np.random.random(10) * 1e4 + 1e-10
+
+    data_2 = np.random.random(90) * 2e4 - 1e4
+    test_data_2 = np.random.random(10) * 2e4 - 1e4
+
+    data_3 = np.random.random(90) * 1e10 + 1e-10
+    test_data_3 = np.random.random(10) * 1e10 + 1e-10
+
+    feat_1 = FeatureNode(0, "t_a", data_1, test_data_1, Unit("s"))
+    feat_2 = FeatureNode(1, "x_a", data_2, test_data_2, Unit("m"))
+    feat_3 = FeatureNode(2, "t_b", data_3, test_data_3, Unit("s"))
+    feat_4 = FeatureNode(3, "t_c", 1.0 / data_3, 1.0 / test_data_3, Unit("1/s"))
+
+    feats = []
+    try:
+        feats.append(MultNode(feat_3, feat_4, 4, 1e-50, 1e50))
+        raise InvalidFeatureMade("Multiplication leading to a constant feature")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(MultNode(feat_1, feat_2, 4, 1e-50, 1e0))
+        raise InvalidFeatureMade("Multiplication outside of user specified bounds")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(MultNode(feat_1, feat_2, 4, 1e10, 1e50))
+        raise InvalidFeatureMade("Multiplication outside of user specified bounds")
+    except RuntimeError:
+        pass
+
+    feats.append(MultNode(feat_1, feat_2, 4, 1e-50, 1e50))
+    feats.append(DivNode(feat_3, feat_1, 5, 1e-50, 1e50))
+    feats.append(DivNode(feat_2, feat_4, 6, 1e-50, 1e50))
+    feats.append(InvNode(feat_1, 7, 1e-50, 1e50))
+
+    try:
+        feats.append(MultNode(feats[2], feats[1], 8, 1e-50, 1e50))
+        raise InvalidFeatureMade("Multiplication of two division features")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(MultNode(feats[3], feat_3, 8, 1e-50, 1e50))
+        raise InvalidFeatureMade("Multiplication with an inverse feature")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(MultNode(feat_3, feats[3], 8, 1e-50, 1e50))
+        raise InvalidFeatureMade("Multiplication with an inverse feature")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(MultNode(feats[0], feats[1], 8, 1e-50, 1e50))
+        raise InvalidFeatureMade("Multiplication with canceled out terms")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(MultNode(feat_1, feat_1, 9, 1e-50, 1e50))
+        raise InvalidFeatureMade("Multiplying the same feature")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(MultNode(feats[0], feats[0], 6, 1e-50, 1e50))
+        raise InvalidFeatureMade("Multiplication with non-one prefactor")
+    except RuntimeError:
+        pass
+
+
+if __name__ == "__main__":
+    test_mult_node()
diff --git a/tests/test_feat_generation/test_neg_exp_node.py b/tests/test_feat_generation/test_neg_exp_node.py
new file mode 100644
index 00000000..9c60576a
--- /dev/null
+++ b/tests/test_feat_generation/test_neg_exp_node.py
@@ -0,0 +1,85 @@
+from cpp_sisso import FeatureNode, ExpNode, NegExpNode, LogNode, AddNode, SubNode, Unit
+
+import numpy as np
+
+
+class InvalidFeatureMade(Exception):
+    pass
+
+
+def test_neg_exp_node():
+    data_1 = np.random.random(90) + 1e-10
+    test_data_1 = np.random.random(10) + 1e-10
+
+    data_2 = np.random.random(90) * 2 - 1
+    test_data_2 = np.random.random(10) * 2 - 1
+
+    data_3 = np.random.random(90) * 10.0 + 1e-10
+    test_data_3 = np.random.random(10) * 10.0 + 1e-10
+
+    feat_1 = FeatureNode(0, "t_a", data_1, test_data_1, Unit())
+    feat_2 = FeatureNode(1, "x_a", data_2, test_data_2, Unit())
+    feat_3 = FeatureNode(2, "t_b", data_3, test_data_3, Unit("m"))
+
+    feats = []
+    try:
+        feats.append(NegExpNode(feat_1, 3, 1e-50, 1e-10))
+        raise InvalidFeatureMade(
+            "Negative exponentiating outside of user specified bounds"
+        )
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(NegExpNode(feat_1, 3, 1e1, 1e50))
+        raise InvalidFeatureMade(
+            "Negative exponentiating outside of user specified bounds"
+        )
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(NegExpNode(feat_3, 3, 1e-50, 1e50))
+        raise InvalidFeatureMade("Negative exponentiating a united quantity")
+    except RuntimeError:
+        pass
+
+    feats.append(ExpNode(feat_2, 3, 1e-50, 1e50))
+    feats.append(ExpNode(feat_2, 4, 1e-50, 1e50))
+    feats.append(LogNode(feat_1, 5, 1e-50, 1e50))
+    feats.append(AddNode(feat_1, feat_2, 6, 1e-50, 1e50))
+    feats.append(SubNode(feat_1, feat_2, 7, 1e-50, 1e50))
+
+    try:
+        feats.append(NegExpNode(feats[0], 8, 1e-50, 1e50))
+        raise InvalidFeatureMade("Negative exponentiating an ExpNode")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(NegExpNode(feats[1], 8, 1e-50, 1e50))
+        raise InvalidFeatureMade("Negative exponentiating a NegExpNode")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(NegExpNode(feats[2], 8, 1e-50, 1e50))
+        raise InvalidFeatureMade("Negative exponentiating a LogNode")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(NegExpNode(feats[3], 8, 1e-50, 1e50))
+        raise InvalidFeatureMade("Negative exponentiating an AddNode")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(NegExpNode(feats[4], 8, 1e-50, 1e50))
+        raise InvalidFeatureMade("Negative exponentiating a SubNode")
+    except RuntimeError:
+        pass
+
+
+if __name__ == "__main__":
+    test_neg_exp_node()
diff --git a/tests/test_feat_generation/test_sin_node.py b/tests/test_feat_generation/test_sin_node.py
new file mode 100644
index 00000000..d840ccda
--- /dev/null
+++ b/tests/test_feat_generation/test_sin_node.py
@@ -0,0 +1,67 @@
+from cpp_sisso import FeatureNode, SinNode, CosNode, Unit
+
+import numpy as np
+
+
+class InvalidFeatureMade(Exception):
+    pass
+
+
+def test_sin_node():
+    data_1 = np.random.randint(0, 10000, 90) * (2.0) * np.pi + np.pi / 2.0
+    test_data_1 = np.random.randint(0, 10000, 10) * (2.0) * np.pi + np.pi / 2.0
+
+    data_2 = np.random.random(90) * 2e4 - 1e4
+    test_data_2 = np.random.random(10) * 2e4 - 1e4
+
+    feat_1 = FeatureNode(0, "t_a", data_1, test_data_1, Unit())
+    feat_2 = FeatureNode(1, "x_a", data_2, test_data_2, Unit())
+    feat_3 = FeatureNode(2, "m_c", data_2, test_data_2, Unit("m"))
+    feats = []
+
+    try:
+        feats.append(SinNode(feat_1, 3, 1e-50, 1e50))
+        raise InvalidFeatureMade("Taking the sine leads to a constant feature")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(SinNode(feat_2, 3, 1e-50, 1e-10))
+        raise InvalidFeatureMade(
+            "Taking the sine leads to values outside of user specified bounds"
+        )
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(SinNode(feat_2, 3, 1e0, 1e50))
+        raise InvalidFeatureMade(
+            "Taking the sine leads to values outside of user specified bounds"
+        )
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(SinNode(feat_3, 3, 1e-50, 1e50))
+        raise InvalidFeatureMade("Taking the sine of united feature")
+    except RuntimeError:
+        pass
+
+    feats.append(SinNode(feat_2, 3, 1e-50, 1e50))
+    feats.append(CosNode(feat_2, 4, 1e-50, 1e50))
+
+    try:
+        feats.append(SinNode(feats[0], 5, 1e-50, 1e50))
+        raise InvalidFeatureMade("Taking the sine of a sine")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(SinNode(feats[1], 5, 1e-50, 1e50))
+        raise InvalidFeatureMade("Taking the sine of a a cosine")
+    except RuntimeError:
+        pass
+
+
+if __name__ == "__main__":
+    test_sin_node()
diff --git a/tests/test_feat_generation/test_six_pow_node.py b/tests/test_feat_generation/test_six_pow_node.py
new file mode 100644
index 00000000..55ed2bbe
--- /dev/null
+++ b/tests/test_feat_generation/test_six_pow_node.py
@@ -0,0 +1,92 @@
+from cpp_sisso import (
+    FeatureNode,
+    InvNode,
+    SqNode,
+    CbNode,
+    SixPowNode,
+    SqrtNode,
+    CbrtNode,
+    Unit,
+)
+
+import numpy as np
+
+
+class InvalidFeatureMade(Exception):
+    pass
+
+
+def test_six_pow_node():
+    data_1 = np.random.random(90) * 1e1 + 1e-10
+    test_data_1 = np.random.random(10) * 1e1 + 1e-10
+
+    data_2 = np.random.choice([1.0, -1.0], 90)
+    test_data_2 = np.random.choice([1.0, -1.0], 10)
+
+    feat_1 = FeatureNode(0, "t_a", data_1, test_data_1, Unit())
+    feat_2 = FeatureNode(1, "x_a", data_2, test_data_2, Unit())
+
+    feats = []
+    try:
+        feats.append(SixPowNode(feat_1, 3, 1e-50, 1e-10))
+        raise InvalidFeatureMade(
+            "Taking the sixth power of the feature leads to values outside of user specified bounds"
+        )
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(SixPowNode(feat_1, 3, 1e7, 1e50))
+        raise InvalidFeatureMade(
+            "Taking the sixth power of the feature leads to values outside of user specified bounds"
+        )
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(SixPowNode(feat_2, 2, 1e-50, 1e50))
+        raise InvalidFeatureMade(
+            "Taking the sixth power of the feature creates to a constant feature"
+        )
+    except RuntimeError:
+        pass
+
+    feats.append(CbrtNode(feat_1, 4, 1e-50, 1e50))
+    feats.append(InvNode(feat_1, 5, 1e-50, 1e50))
+    feats.append(SqNode(feat_1, 6, 1e-50, 1e50))
+    feats.append(CbNode(feat_1, 7, 1e-50, 1e50))
+    feats.append(SqrtNode(feat_1, 8, 1e-50, 1e50))
+
+    try:
+        feats.append(SixPowNode(feats[0], 13, 1e-50, 1e50))
+        raise InvalidFeatureMade("Taking the sixth power of a CbrtNode")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(SixPowNode(feats[1], 13, 1e-50, 1e50))
+        raise InvalidFeatureMade("Taking the sixth power of a InvNode")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(SixPowNode(feats[2], 13, 1e-50, 1e50))
+        raise InvalidFeatureMade("Taking the sixth power of an SqNode")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(SixPowNode(feats[3], 13, 1e-50, 1e50))
+        raise InvalidFeatureMade("Taking the sixth power of a CbNode")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(SixPowNode(feats[4], 13, 1e-50, 1e50))
+        raise InvalidFeatureMade("Taking the sixth power of a SqrtNode")
+    except RuntimeError:
+        pass
+
+
+if __name__ == "__main__":
+    test_six_pow_node()
diff --git a/tests/test_feat_generation/test_sq_node.py b/tests/test_feat_generation/test_sq_node.py
new file mode 100644
index 00000000..a13f7526
--- /dev/null
+++ b/tests/test_feat_generation/test_sq_node.py
@@ -0,0 +1,62 @@
+from cpp_sisso import FeatureNode, InvNode, SqNode, SqrtNode, Unit
+
+import numpy as np
+
+
+class InvalidFeatureMade(Exception):
+    pass
+
+
+def test_cube_node():
+    data_1 = np.random.random(90) * 1e4 + 1e-10
+    test_data_1 = np.random.random(10) * 1e4 + 1e-10
+
+    data_2 = np.random.choice([1.0, -1.0], 90)
+    test_data_2 = np.random.choice([1.0, -1.0], 10)
+
+    feat_1 = FeatureNode(0, "t_a", data_1, test_data_1, Unit())
+    feat_2 = FeatureNode(1, "x_a", data_2, test_data_2, Unit())
+
+    feats = []
+    try:
+        feats.append(SqNode(feat_1, 3, 1e-50, 1e-10))
+        raise InvalidFeatureMade(
+            "Taking the cube of the feature leads to values outside of user specified bounds"
+        )
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(SqNode(feat_1, 3, 1e8, 1e50))
+        raise InvalidFeatureMade(
+            "Taking the cube of the feature leads to values outside of user specified bounds"
+        )
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(SqNode(feat_2, 2, 1e-50, 1e50))
+        raise InvalidFeatureMade(
+            "Taking the absolute value leading to a constant feature"
+        )
+    except RuntimeError:
+        pass
+
+    feats.append(InvNode(feat_1, 5, 1e-50, 1e50))
+    feats.append(SqrtNode(feat_1, 6, 1e-50, 1e50))
+
+    try:
+        feats.append(SqNode(feats[0], 13, 1e-50, 1e50))
+        raise InvalidFeatureMade("Taking the cube of a InvNode")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(SqNode(feats[1], 13, 1e-50, 1e50))
+        raise InvalidFeatureMade("Taking the cube of an SqrtNode")
+    except RuntimeError:
+        pass
+
+
+if __name__ == "__main__":
+    test_cube_node()
diff --git a/tests/test_feat_generation/test_sqrt_node.py b/tests/test_feat_generation/test_sqrt_node.py
new file mode 100644
index 00000000..4154c3d9
--- /dev/null
+++ b/tests/test_feat_generation/test_sqrt_node.py
@@ -0,0 +1,84 @@
+from cpp_sisso import (
+    FeatureNode,
+    InvNode,
+    SqNode,
+    CbNode,
+    SixPowNode,
+    SqrtNode,
+    CbrtNode,
+    Unit,
+)
+
+import numpy as np
+
+
+class InvalidFeatureMade(Exception):
+    pass
+
+
+def test_sqrt_node():
+    data_1 = np.random.random(90) * 1e4 + 1e-10
+    test_data_1 = np.random.random(10) * 1e4 + 1e-10
+
+    data_2 = np.random.random(90) * 2 - 1
+    test_data_2 = np.random.random(10) * 2 - 1
+
+    feat_1 = FeatureNode(0, "t_a", data_1, test_data_1, Unit())
+    feat_2 = FeatureNode(1, "x_a", data_2, test_data_2, Unit())
+
+    feats = []
+    try:
+        feats.append(SqrtNode(feat_1, 3, 1e-50, 1e-10))
+        raise InvalidFeatureMade(
+            "Taking the sqrt of the feature leads to values outside of user specified bounds"
+        )
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(SqrtNode(feat_1, 3, 1e4, 1e50))
+        raise InvalidFeatureMade(
+            "Taking the sqrt of the feature leads to values outside of user specified bounds"
+        )
+    except RuntimeError:
+        pass
+
+    feats.append(CbrtNode(feat_1, 4, 1e-50, 1e50))
+    feats.append(InvNode(feat_1, 5, 1e-50, 1e50))
+    feats.append(SqNode(feat_2, 6, 1e-50, 1e50))
+    feats.append(CbNode(feat_1, 7, 1e-50, 1e50))
+    feats.append(SixPowNode(feat_2, 8, 1e-50, 1e50))
+
+    try:
+        feats.append(SqrtNode(feats[0], 13, 1e-50, 1e50))
+        raise InvalidFeatureMade("Taking the sqrt of a CbrtNode")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(SqrtNode(feats[1], 13, 1e-50, 1e50))
+        raise InvalidFeatureMade("Taking the sqrt of a InvNode")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(SqrtNode(feats[2], 13, 1e-50, 1e50))
+        raise InvalidFeatureMade("Taking the sqrt of an SqNode")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(SqrtNode(feats[3], 13, 1e-50, 1e50))
+        raise InvalidFeatureMade("Taking the sqrt of a CbNode")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(SqrtNode(feats[4], 13, 1e-50, 1e50))
+        raise InvalidFeatureMade("Taking the sqrt of a SixPowNode")
+    except RuntimeError:
+        pass
+
+
+if __name__ == "__main__":
+    test_sqrt_node()
diff --git a/tests/test_feat_generation/test_sub_node.py b/tests/test_feat_generation/test_sub_node.py
new file mode 100644
index 00000000..35886d08
--- /dev/null
+++ b/tests/test_feat_generation/test_sub_node.py
@@ -0,0 +1,74 @@
+from cpp_sisso import FeatureNode, AddNode, SubNode, Unit
+
+import numpy as np
+
+
+class InvalidFeatureMade(Exception):
+    pass
+
+
+def test_sub_node():
+    data_1 = np.random.random(90) * 1e4 + 1e-10
+    test_data_1 = np.random.random(10) * 1e4 + 1e-10
+
+    data_2 = np.random.random(90) * 2e4 - 1e4
+    test_data_2 = np.random.random(10) * 2e4 - 1e4
+
+    data_3 = np.random.random(90) * 1e10 + 1e-10
+    test_data_3 = np.random.random(10) * 1e10 + 1e-10
+
+    feat_1 = FeatureNode(0, "t_a", data_1, test_data_1, Unit("s"))
+    feat_2 = FeatureNode(1, "x_a", data_2, test_data_2, Unit("m"))
+    feat_3 = FeatureNode(2, "t_b", data_3, test_data_3, Unit("s"))
+    feat_4 = FeatureNode(3, "t_c", data_3, test_data_3, Unit("s"))
+
+    feats = []
+    try:
+        feats.append(SubNode(feat_1, feat_2, 3, 1e-50, 1e50))
+        raise InvalidFeatureMade("Subtraction with mismatching units")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(SubNode(feat_3, feat_4, 4, 1e-50, 1e50))
+        raise InvalidFeatureMade("Subtraction leading to a constant feature")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(SubNode(feat_1, feat_2, 4, 1e-50, 1e0))
+        raise InvalidFeatureMade("Subtraction outside of user specified bounds")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(SubNode(feat_1, feat_2, 4, 1e-1, 1e50))
+        raise InvalidFeatureMade("Subtraction outside of user specified bounds")
+    except RuntimeError:
+        pass
+
+    feats.append(AddNode(feat_1, feat_3, 4, 1e-50, 1e50))
+    feats.append(SubNode(feat_1, feat_3, 5, 1e-50, 1e50))
+    feats.append(SubNode(feat_3, feat_1, 6, 1e-50, 1e50))
+
+    try:
+        feats.append(SubNode(feats[0], feats[1], 7, 1e-50, 1e50))
+        raise InvalidFeatureMade("Subtraction with canceled out terms")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(SubNode(feat_1, feat_1, 7, 1e-50, 1e50))
+        raise InvalidFeatureMade("Subtracting the same feature")
+    except RuntimeError:
+        pass
+
+    try:
+        feats.append(SubNode(feats[-2], feats[-1], 7, 1e-50, 1e50))
+        raise InvalidFeatureMade("Subtraction with non-one prefactor")
+    except RuntimeError:
+        pass
+
+
+if __name__ == "__main__":
+    test_sub_node()
diff --git a/tests/test_sisso.py b/tests/test_sisso.py
index 72cd6469..5efc2a7a 100644
--- a/tests/test_sisso.py
+++ b/tests/test_sisso.py
@@ -16,7 +16,7 @@ def test_sisso():
         max_dim=2,
         n_residuals=1,
         task_key="Task",
-        leave_out_frac=0.05,
+        leave_out_frac=0.1,
     )
 
     sisso.fit()
-- 
GitLab