#################################################################################
# The Institute for the Design of Advanced Energy Systems Integrated Platform
# Framework (IDAES IP) was produced under the DOE Institute for the
# Design of Advanced Energy Systems (IDAES).
#
# Copyright (c) 2018-2024 by the software owners: The Regents of the
# University of California, through Lawrence Berkeley National Laboratory,
# National Technology & Engineering Solutions of Sandia, LLC, Carnegie Mellon
# University, West Virginia University Research Corporation, et al.
# All rights reserved.  Please see the files COPYRIGHT.md and LICENSE.md
# for full copyright and license information.
#################################################################################
import sys, os
from unittest.mock import patch

from idaes.core.surrogate.pysmo.polynomial_regression import (
    PolynomialRegression,
    FeatureScaling,
)
import numpy as np
import pandas as pd
import pytest
import idaes.logger as idaeslog


class TestFeatureScaling:
    test_data_1d = [[x] for x in range(10)]
    test_data_2d = [[x, (x + 1) ** 2] for x in range(10)]
    test_data_3d = [[x, x + 10, (x + 1) ** 2 + x + 10] for x in range(10)]
    test_data_3d_constant = [[x, 10, (x + 1) ** 2 + 10] for x in range(10)]

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type", [np.array, pd.DataFrame])
    def test_data_scaling_01(self, array_type):
        input_array = array_type(self.test_data_1d)

        output_1, output_2, output_3 = FeatureScaling.data_scaling(input_array)
        expected_output_3 = np.array([[9]])
        expected_output_2 = np.array([[0]])
        expected_output_1 = (input_array - expected_output_2) / (
            expected_output_3 - expected_output_2
        )
        np.testing.assert_array_equal(output_3, expected_output_3)
        np.testing.assert_array_equal(output_2, expected_output_2)
        np.testing.assert_array_equal(
            output_1, np.array(expected_output_1).reshape(10, 1)
        )

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type", [np.array, pd.DataFrame])
    def test_data_scaling_02(self, array_type):
        input_array = array_type(self.test_data_2d)
        output_1, output_2, output_3 = FeatureScaling.data_scaling(input_array)
        expected_output_3 = np.array([[9, 100]])
        expected_output_2 = np.array([[0, 1]])
        expected_output_1 = (input_array - expected_output_2) / (
            expected_output_3 - expected_output_2
        )
        np.testing.assert_array_equal(output_3, expected_output_3)
        np.testing.assert_array_equal(output_2, expected_output_2)
        np.testing.assert_array_equal(output_1, expected_output_1)

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type", [np.array, pd.DataFrame])
    def test_data_scaling_03(self, array_type):
        input_array = array_type(self.test_data_3d)
        output_1, output_2, output_3 = FeatureScaling.data_scaling(input_array)
        expected_output_3 = np.array([[9, 19, 119]])
        expected_output_2 = np.array([[0, 10, 11]])
        expected_output_1 = (input_array - expected_output_2) / (
            expected_output_3 - expected_output_2
        )
        np.testing.assert_array_equal(output_3, expected_output_3)
        np.testing.assert_array_equal(output_2, expected_output_2)
        np.testing.assert_array_equal(output_1, expected_output_1)

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type", [np.array, pd.DataFrame])
    def test_data_scaling_04(self, array_type):
        input_array = array_type(self.test_data_3d_constant)
        output_1, output_2, output_3 = FeatureScaling.data_scaling(input_array)
        expected_output_3 = np.array([[9, 10, 110]])
        expected_output_2 = np.array([[0, 10, 11]])
        scale = expected_output_3 - expected_output_2
        scale[scale == 0.0] = 1.0
        expected_output_1 = (input_array - expected_output_2) / scale
        np.testing.assert_array_equal(output_3, expected_output_3)
        np.testing.assert_array_equal(output_2, expected_output_2)
        np.testing.assert_array_equal(output_1, expected_output_1)

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type", [list])
    def test_data_scaling_05(self, array_type):
        input_array = array_type(self.test_data_2d)
        with pytest.raises(TypeError):
            FeatureScaling.data_scaling(input_array)

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type", [np.array])
    def test_data_unscaling_01(self, array_type):
        input_array = array_type(self.test_data_1d)
        output_1, output_2, output_3 = FeatureScaling.data_scaling(input_array)
        output_1 = output_1.reshape(
            output_1.shape[0],
        )
        un_output_1 = FeatureScaling.data_unscaling(output_1, output_2, output_3)
        np.testing.assert_array_equal(un_output_1, input_array.reshape(10, 1))

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type", [np.array])
    def test_data_unscaling_02(self, array_type):
        input_array = array_type(self.test_data_2d)
        output_1, output_2, output_3 = FeatureScaling.data_scaling(input_array)
        un_output_1 = FeatureScaling.data_unscaling(output_1, output_2, output_3)
        np.testing.assert_array_equal(un_output_1, input_array)

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type", [np.array])
    def test_data_unscaling_03(self, array_type):
        input_array = array_type(self.test_data_3d)
        output_1, output_2, output_3 = FeatureScaling.data_scaling(input_array)
        un_output_1 = FeatureScaling.data_unscaling(output_1, output_2, output_3)
        np.testing.assert_array_equal(un_output_1, input_array)

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type", [np.array])
    def test_data_unscaling_04(self, array_type):
        input_array = array_type(self.test_data_3d_constant)
        output_1, output_2, output_3 = FeatureScaling.data_scaling(input_array)
        un_output_1 = FeatureScaling.data_unscaling(output_1, output_2, output_3)
        np.testing.assert_array_equal(un_output_1, input_array)

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type", [np.array])
    def test_data_unscaling_05(self, array_type):
        input_array = array_type(self.test_data_2d)
        output_1, output_2, output_3 = FeatureScaling.data_scaling(input_array)

        min_array = np.array([[1]])
        max_array = np.array([[5]])
        with pytest.raises(IndexError):
            FeatureScaling.data_unscaling(output_1, min_array, max_array)

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type", [np.array])
    def test_data_unscaling_06(self, array_type):
        input_array = array_type(self.test_data_2d)
        output_1, output_2, output_3 = FeatureScaling.data_scaling(input_array)

        min_array = np.array([[1, 2, 3]])
        max_array = np.array([[5, 6, 7]])
        with pytest.raises(IndexError):
            FeatureScaling.data_unscaling(output_1, min_array, max_array)


@pytest.mark.usefixtures("run_class_in_tmp_path")
class TestPolynomialRegression:
    y = np.array(
        [
            [i, j, ((i + 1) ** 2) + ((j + 1) ** 2)]
            for i in np.linspace(0, 10, 21)
            for j in np.linspace(0, 10, 21)
        ]
    )
    full_data = {"x1": y[:, 0], "x2": y[:, 1], "y": y[:, 2]}
    training_data = [
        [i, j, ((i + 1) ** 2) + ((j + 1) ** 2)]
        for i in np.linspace(0, 10, 5)
        for j in np.linspace(0, 10, 5)
    ]
    test_data = [[i, (i + 1) ** 2] for i in range(10)]
    test_data_large = [[i, (i + 1) ** 2] for i in range(200)]
    test_data_1d = [[(i + 1) ** 2] for i in range(10)]
    test_data_3d = [[i, (i + 1) ** 2, (i + 2) ** 2] for i in range(10)]
    sample_points = [[i, (i + 1) ** 2] for i in range(8)]
    sample_points_large = [[i, (i + 1) ** 2] for i in range(100)]
    sample_points_1d = [[(i + 1) ** 2] for i in range(8)]
    sample_points_3d = [[i, (i + 1) ** 2, (i + 2) ** 2] for i in range(8)]

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [np.array, pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array, pd.DataFrame])
    def test__init__01(self, array_type1, array_type2):
        original_data_input = array_type1(self.test_data)
        regression_data_input = array_type2(self.sample_points)

        PolyClass = PolynomialRegression(
            original_data_input, regression_data_input, maximum_polynomial_order=5
        )
        assert PolyClass.max_polynomial_order == 5
        assert (
            PolyClass.number_of_crossvalidations == 3
        )  # Default number of cross-validations
        assert PolyClass.no_adaptive_samples == 4  # Default number of adaptive samples
        assert PolyClass.fraction_training == 0.75  # Default training split
        assert (
            PolyClass.max_fraction_training_samples == 0.5
        )  # Default fraction for the maximum number of training samples
        assert PolyClass.max_iter == 10  # Default maximum number of iterations
        assert PolyClass.solution_method == "pyomo"  # Default solution_method
        assert PolyClass.multinomials == 1  # Default multinomials

    @pytest.mark.parametrize("array_type1", [np.array, pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array, pd.DataFrame])
    @pytest.mark.unit
    def test__init__02(self, array_type1, array_type2):
        original_data_input = array_type1(self.test_data)
        regression_data_input = array_type2(self.sample_points)
        PolyClass = PolynomialRegression(
            original_data_input,
            regression_data_input,
            maximum_polynomial_order=3,
            number_of_crossvalidations=5,
            no_adaptive_samples=6,
            training_split=0.5,
            max_fraction_training_samples=0.4,
            max_iter=20,
            solution_method="MLe",
            multinomials=0,
        )
        assert PolyClass.max_polynomial_order == 3
        assert (
            PolyClass.number_of_crossvalidations == 5
        )  # Default number of cross-validations
        assert PolyClass.no_adaptive_samples == 6  # Default number of adaptive samples
        assert PolyClass.fraction_training == 0.5  # Default training split
        assert (
            PolyClass.max_fraction_training_samples == 0.4
        )  # Default fraction for the maximum number of training samples
        assert PolyClass.max_iter == 20  # Default maximum number of iterations
        assert (
            PolyClass.solution_method == "mle"
        )  # Default solution_method, doesn't matter lower / upper characters
        assert PolyClass.multinomials == 0  # Default multinomials

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [list])
    @pytest.mark.parametrize("array_type2", [np.array, pd.DataFrame])
    def test__init__03(self, array_type1, array_type2):
        original_data_input = array_type1(self.test_data)
        regression_data_input = array_type2(self.sample_points)
        with pytest.raises(ValueError):
            PolyClass = PolynomialRegression(
                original_data_input, regression_data_input, maximum_polynomial_order=5
            )

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [np.array, pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [list])
    def test__init__04(self, array_type1, array_type2):
        original_data_input = array_type1(self.test_data)
        regression_data_input = array_type2(self.sample_points)
        with pytest.raises(ValueError):
            PolyClass = PolynomialRegression(
                original_data_input, regression_data_input, maximum_polynomial_order=5
            )

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [np.array, pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array, pd.DataFrame])
    def test__init__05(self, array_type1, array_type2):
        original_data_input = array_type1(self.test_data)
        regression_data_input = array_type2(self.sample_points_large)
        with pytest.raises(Exception):
            PolyClass = PolynomialRegression(
                original_data_input, regression_data_input, maximum_polynomial_order=5
            )

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [np.array, pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array, pd.DataFrame])
    def test__init__06(self, array_type1, array_type2):
        original_data_input = array_type1(self.test_data_3d)
        regression_data_input = array_type2(self.sample_points)
        with pytest.raises(Exception):
            PolyClass = PolynomialRegression(
                original_data_input, regression_data_input, maximum_polynomial_order=5
            )

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [np.array, pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array, pd.DataFrame])
    def test__init__07(self, array_type1, array_type2):
        original_data_input = array_type1(self.test_data)
        regression_data_input = array_type2(self.sample_points_3d)
        with pytest.raises(Exception):
            PolyClass = PolynomialRegression(
                original_data_input, regression_data_input, maximum_polynomial_order=5
            )

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [np.array, pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array, pd.DataFrame])
    def test__init__08(self, array_type1, array_type2):
        original_data_input = array_type1(self.test_data_1d)
        regression_data_input = array_type2(self.sample_points_1d)
        with pytest.raises(Exception):
            PolyClass = PolynomialRegression(
                original_data_input, regression_data_input, maximum_polynomial_order=5
            )

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [np.array, pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array, pd.DataFrame])
    def test__init__09(self, array_type1, array_type2, caplog):
        caplog.set_level(idaeslog.WARNING)
        warning_msg = "The number of cross-validations entered is large. The simulation may take a while to run"
        original_data_input = array_type1(self.test_data)
        regression_data_input = array_type2(self.sample_points)
        PolyClass = PolynomialRegression(
            original_data_input,
            regression_data_input,
            maximum_polynomial_order=5,
            number_of_crossvalidations=11,
        )
        assert warning_msg in caplog.text
        for record in caplog.records:
            assert record.levelno == idaeslog.WARNING
        assert (
            PolyClass.number_of_crossvalidations == 11
        )  # Default number of cross-validations

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [np.array, pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array, pd.DataFrame])
    def test__init__10(self, array_type1, array_type2):
        original_data_input = array_type1(self.test_data)
        regression_data_input = array_type2(self.sample_points)
        with pytest.raises(Exception):
            PolyClass = PolynomialRegression(
                original_data_input, regression_data_input, maximum_polynomial_order=1.2
            )

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [np.array, pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array, pd.DataFrame])
    def test__init__11(self, array_type1, array_type2, caplog):
        caplog.set_level(idaeslog.WARNING)
        warning_msg = (
            "The maximum allowed polynomial order is 10. Value has been adjusted to 10."
        )
        original_data_input = array_type1(self.test_data_large)
        regression_data_input = array_type2(self.sample_points_large)
        PolyClass = PolynomialRegression(
            original_data_input, regression_data_input, maximum_polynomial_order=11
        )
        warning_msg = (
            "The maximum allowed polynomial order is 10. Value has been adjusted to 10."
        )
        assert warning_msg in caplog.text
        for record in caplog.records:
            assert record.levelno == idaeslog.WARNING
        assert PolyClass.max_polynomial_order == 10

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [np.array, pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array, pd.DataFrame])
    def test__init__12(self, array_type1, array_type2):
        original_data_input = array_type1(self.test_data)
        regression_data_input = array_type2(self.sample_points)
        with pytest.raises(Exception):
            PolyClass = PolynomialRegression(
                original_data_input,
                regression_data_input,
                maximum_polynomial_order=5,
                training_split=1,
            )

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [np.array, pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array, pd.DataFrame])
    def test__init__13(self, array_type1, array_type2):
        original_data_input = array_type1(self.test_data)
        regression_data_input = array_type2(self.sample_points)
        with pytest.raises(Exception):
            PolyClass = PolynomialRegression(
                original_data_input,
                regression_data_input,
                maximum_polynomial_order=5,
                training_split=-1,
            )

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [np.array, pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array, pd.DataFrame])
    def test__init__14(self, array_type1, array_type2):
        original_data_input = array_type1(self.test_data)
        regression_data_input = array_type2(self.sample_points)
        with pytest.raises(Exception):
            PolyClass = PolynomialRegression(
                original_data_input,
                regression_data_input,
                maximum_polynomial_order=5,
                max_fraction_training_samples=1.2,
            )

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [np.array, pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array, pd.DataFrame])
    def test__init__15(self, array_type1, array_type2):
        original_data_input = array_type1(self.test_data)
        regression_data_input = array_type2(self.sample_points)
        with pytest.raises(Exception):
            PolyClass = PolynomialRegression(
                original_data_input,
                regression_data_input,
                maximum_polynomial_order=5,
                max_fraction_training_samples=-1.2,
            )

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [np.array, pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array, pd.DataFrame])
    def test__init__16(self, array_type1, array_type2):
        original_data_input = array_type1(self.test_data)
        regression_data_input = array_type2(self.sample_points)
        PolyClass = PolynomialRegression(
            regression_data_input,
            regression_data_input,
            maximum_polynomial_order=5,
            max_iter=100,
        )
        assert PolyClass.max_iter == 0

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [np.array, pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array, pd.DataFrame])
    def test__init__17(self, array_type1, array_type2):
        original_data_input = array_type1(self.test_data)
        regression_data_input = array_type2(self.sample_points)
        PolyClass = PolynomialRegression(
            original_data_input,
            regression_data_input,
            maximum_polynomial_order=5,
            no_adaptive_samples=0,
            max_iter=100,
        )
        assert PolyClass.max_iter == 0

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [np.array, pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array, pd.DataFrame])
    def test__init__18(self, array_type1, array_type2):
        original_data_input = array_type1(self.test_data)
        regression_data_input = array_type2(self.sample_points)
        with pytest.raises(Exception):
            PolyClass = PolynomialRegression(
                original_data_input,
                regression_data_input,
                maximum_polynomial_order=5,
                number_of_crossvalidations=1.2,
            )

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [np.array, pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array, pd.DataFrame])
    def test__init__19(self, array_type1, array_type2):
        original_data_input = array_type1(self.test_data)
        regression_data_input = array_type2(self.sample_points)
        with pytest.raises(Exception):
            PolyClass = PolynomialRegression(
                original_data_input,
                regression_data_input,
                maximum_polynomial_order=5,
                no_adaptive_samples=4.2,
            )

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [np.array, pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array, pd.DataFrame])
    def test__init__20(self, array_type1, array_type2):
        original_data_input = array_type1(self.test_data)
        regression_data_input = array_type2(self.sample_points)
        with pytest.raises(Exception):
            PolyClass = PolynomialRegression(
                original_data_input,
                regression_data_input,
                maximum_polynomial_order=5,
                max_iter=4.2,
            )

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [np.array, pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array, pd.DataFrame])
    def test__init__21(self, array_type1, array_type2):
        original_data_input = array_type1(self.test_data)
        regression_data_input = array_type2(self.sample_points)
        with pytest.raises(Exception):
            PolyClass = PolynomialRegression(
                original_data_input, regression_data_input, maximum_polynomial_order=15
            )

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [np.array, pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array, pd.DataFrame])
    def test__init__22(self, array_type1, array_type2):
        original_data_input = array_type1(self.test_data)
        regression_data_input = array_type2(self.sample_points)
        with pytest.raises(Exception):
            PolyClass = PolynomialRegression(
                original_data_input,
                regression_data_input,
                maximum_polynomial_order=5,
                solution_method=1,
            )

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [np.array, pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array, pd.DataFrame])
    def test__init__23(self, array_type1, array_type2):
        original_data_input = array_type1(self.test_data)
        regression_data_input = array_type2(self.sample_points)
        with pytest.raises(Exception):
            PolyClass = PolynomialRegression(
                original_data_input,
                regression_data_input,
                maximum_polynomial_order=5,
                solution_method="idaes",
            )

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [np.array, pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array, pd.DataFrame])
    def test__init__24(self, array_type1, array_type2):
        original_data_input = array_type1(self.test_data)
        regression_data_input = array_type2(self.sample_points)
        with pytest.raises(Exception):
            PolyClass = PolynomialRegression(
                original_data_input,
                regression_data_input,
                maximum_polynomial_order=5,
                multinomials=3,
            )

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [np.array, pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array, pd.DataFrame])
    def test__init__25(self, array_type1, array_type2):
        original_data_input = array_type1(self.test_data)
        regression_data_input = array_type2(self.sample_points)
        with pytest.raises(Exception):
            PolyClass = PolynomialRegression(
                original_data_input, regression_data_input, maximum_polynomial_order=-2
            )

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [np.array, pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array, pd.DataFrame])
    def test__init__26(self, array_type1, array_type2):
        original_data_input = array_type1(self.test_data)
        regression_data_input = array_type2(self.sample_points)
        with pytest.raises(Exception):
            PolyClass = PolynomialRegression(
                original_data_input,
                regression_data_input,
                maximum_polynomial_order=3,
                number_of_crossvalidations=-3,
            )

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [np.array, pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array, pd.DataFrame])
    def test__init__27(self, array_type1, array_type2):
        original_data_input = array_type1(self.test_data)
        regression_data_input = array_type2(self.sample_points)
        with pytest.raises(Exception):
            PolyClass = PolynomialRegression(
                original_data_input,
                regression_data_input,
                maximum_polynomial_order=3,
                no_adaptive_samples=-3,
            )

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [np.array, pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array, pd.DataFrame])
    def test__init__28(self, array_type1, array_type2):
        original_data_input = array_type1(self.test_data)
        regression_data_input = array_type2(self.sample_points)
        with pytest.raises(Exception):
            PolyClass = PolynomialRegression(
                original_data_input,
                regression_data_input,
                maximum_polynomial_order=3,
                max_iter=-3,
            )

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [np.array, pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array, pd.DataFrame])
    def test__init__29(self, array_type1, array_type2):
        original_data_input = array_type1(self.test_data)
        regression_data_input = array_type2(self.sample_points)
        with pytest.raises(Exception):
            PolyClass = PolynomialRegression(
                original_data_input,
                regression_data_input,
                maximum_polynomial_order=3,
                overwrite=1,
            )

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [np.array, pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array, pd.DataFrame])
    def test__init__30(self, array_type1, array_type2):
        original_data_input = array_type1(self.test_data)
        regression_data_input = array_type2(self.sample_points)
        with pytest.raises(Exception):
            PolyClass = PolynomialRegression(
                original_data_input,
                regression_data_input,
                maximum_polynomial_order=3,
                fname="solution.pkl",
            )

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [np.array, pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array, pd.DataFrame])
    def test__init__31(self, array_type1, array_type2):
        original_data_input = array_type1(self.test_data)
        regression_data_input = array_type2(self.sample_points)
        with pytest.raises(Exception):
            PolyClass = PolynomialRegression(
                original_data_input,
                regression_data_input,
                maximum_polynomial_order=3,
                fname=1,
            )

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [np.array, pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array, pd.DataFrame])
    def test__init__32(self, array_type1, array_type2):
        file_name = "sol_check.pickle"
        original_data_input = array_type1(self.test_data)
        regression_data_input = array_type2(self.sample_points)

        PolyClass1 = PolynomialRegression(
            original_data_input,
            regression_data_input,
            maximum_polynomial_order=3,
            fname=file_name,
            overwrite=True,
        )
        PolyClass1.get_feature_vector()
        results = PolyClass1.polynomial_regression_fitting()
        PolyClass2 = PolynomialRegression(
            original_data_input,
            regression_data_input,
            maximum_polynomial_order=3,
            fname=file_name,
            overwrite=True,
        )
        assert PolyClass1.filename == PolyClass2.filename

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [np.array, pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array, pd.DataFrame])
    def test__init__33(self, array_type1, array_type2):
        file_name1 = "sol_check1.pickle"
        file_name2 = "sol_check2.pickle"
        original_data_input = array_type1(self.test_data)
        regression_data_input = array_type2(self.sample_points)

        PolyClass1 = PolynomialRegression(
            original_data_input,
            regression_data_input,
            maximum_polynomial_order=3,
            fname=file_name1,
            overwrite=True,
        )
        PolyClass1.get_feature_vector()
        results = PolyClass1.polynomial_regression_fitting()
        PolyClass2 = PolynomialRegression(
            original_data_input,
            regression_data_input,
            maximum_polynomial_order=3,
            fname=file_name2,
            overwrite=True,
        )
        assert PolyClass1.filename == file_name1
        assert PolyClass2.filename == file_name2

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [np.array, pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array, pd.DataFrame])
    def test__init__34(self, array_type1, array_type2):
        file_name = "sol_check.pickle"
        original_data_input = array_type1(self.test_data)
        regression_data_input = array_type2(self.sample_points)
        PolyClass1 = PolynomialRegression(
            original_data_input,
            regression_data_input,
            fname=file_name,
            maximum_polynomial_order=3,
            overwrite=True,
        )
        PolyClass1.get_feature_vector()
        results = PolyClass1.polynomial_regression_fitting()
        PolygClass2 = PolynomialRegression(
            original_data_input,
            regression_data_input,
            fname=file_name,
            maximum_polynomial_order=3,
            overwrite=True,
        )
        assert PolyClass1.filename == PolygClass2.filename

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [np.array, pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array, pd.DataFrame])
    def test_training_test_data_creation_01(self, array_type1, array_type2):
        original_data_input = array_type1(self.test_data)
        regression_data_input = array_type2(self.sample_points)

        PolyClass = PolynomialRegression(
            original_data_input,
            regression_data_input,
            maximum_polynomial_order=5,
            training_split=0.01,
        )
        with pytest.raises(Exception):
            training_data, cross_val_data = PolyClass.training_test_data_creation()

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [np.array, pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array, pd.DataFrame])
    def test_training_test_data_creation_02(self, array_type1, array_type2):
        original_data_input = array_type1(self.test_data)
        regression_data_input = array_type2(self.sample_points)

        PolyClass = PolynomialRegression(
            original_data_input,
            regression_data_input,
            maximum_polynomial_order=5,
            training_split=0.99,
        )
        with pytest.raises(Exception):
            training_data, cross_val_data = PolyClass.training_test_data_creation()

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array])
    def test_training_test_data_creation_03(self, array_type1, array_type2):
        original_data_input = array_type1(self.full_data)
        regression_data_input = array_type2(self.training_data)

        PolyClass = PolynomialRegression(
            original_data_input, regression_data_input, maximum_polynomial_order=5
        )

        training_data, cross_val_data = PolyClass.training_test_data_creation()

        expected_training_size = int(
            np.around(PolyClass.number_of_samples * PolyClass.fraction_training)
        )
        expected_test_size = PolyClass.regression_data.shape[0] - expected_training_size

        assert len(training_data) == PolyClass.number_of_crossvalidations
        assert len(cross_val_data) == PolyClass.number_of_crossvalidations

        for i in range(1, PolyClass.number_of_crossvalidations + 1):
            assert (
                training_data["training_set_" + str(i)].shape[0]
                == expected_training_size
            )
            assert cross_val_data["test_set_" + str(i)].shape[0] == expected_test_size

            concat_01 = np.concatenate(
                (
                    training_data["training_set_" + str(i)],
                    cross_val_data["test_set_" + str(i)],
                ),
                axis=0,
            )
            sample_data_sorted = regression_data_input[
                np.lexsort(
                    (
                        regression_data_input[:, 2],
                        regression_data_input[:, 1],
                        regression_data_input[:, 0],
                    )
                )
            ]
            concat_01_sorted = concat_01[
                np.lexsort((concat_01[:, 2], concat_01[:, 1], concat_01[:, 0]))
            ]
            np.testing.assert_equal(sample_data_sorted, concat_01_sorted)

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array])
    def test_training_test_data_creation_04(self, array_type1, array_type2):
        original_data_input = array_type1(self.full_data)
        regression_data_input = array_type2(self.training_data)
        PolyClass = PolynomialRegression(
            original_data_input,
            regression_data_input,
            maximum_polynomial_order=3,
            number_of_crossvalidations=5,
            no_adaptive_samples=6,
            training_split=0.5,
            max_fraction_training_samples=0.4,
            max_iter=20,
            solution_method="MLe",
            multinomials=0,
        )
        training_data, cross_val_data = PolyClass.training_test_data_creation()
        expected_training_size = int(
            np.around(PolyClass.number_of_samples * PolyClass.fraction_training)
        )
        expected_test_size = PolyClass.regression_data.shape[0] - expected_training_size
        assert len(training_data) == PolyClass.number_of_crossvalidations
        assert len(cross_val_data) == PolyClass.number_of_crossvalidations
        for i in range(1, PolyClass.number_of_crossvalidations + 1):
            assert (
                training_data["training_set_" + str(i)].shape[0]
                == expected_training_size
            )
            assert cross_val_data["test_set_" + str(i)].shape[0] == expected_test_size
            concat_01 = np.concatenate(
                (
                    training_data["training_set_" + str(i)],
                    cross_val_data["test_set_" + str(i)],
                ),
                axis=0,
            )
            sample_data_sorted = regression_data_input[
                np.lexsort(
                    (
                        regression_data_input[:, 2],
                        regression_data_input[:, 1],
                        regression_data_input[:, 0],
                    )
                )
            ]
            concat_01_sorted = concat_01[
                np.lexsort((concat_01[:, 2], concat_01[:, 1], concat_01[:, 0]))
            ]
            np.testing.assert_equal(sample_data_sorted, concat_01_sorted)

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array])
    def test_training_test_data_creation_05(self, array_type1, array_type2):
        original_data_input = array_type1(self.full_data)
        regression_data_input = array_type2(self.training_data)
        PolyClass = PolynomialRegression(
            original_data_input, regression_data_input, maximum_polynomial_order=5
        )
        additional_data_input = np.array(
            [
                [
                    i**2,
                    ((i + 1) * 2) + ((j + 1) * 2),
                    j**4,
                    ((i + 1) * 2) + ((j + 1) ** 2),
                ]
                for i in range(5)
                for j in range(5)
            ]
        )
        training_data, cross_val_data = PolyClass.training_test_data_creation(
            additional_features=additional_data_input
        )
        expected_training_size = int(
            np.around(PolyClass.number_of_samples * PolyClass.fraction_training)
        )
        expected_test_size = PolyClass.regression_data.shape[0] - expected_training_size
        assert len(training_data) == PolyClass.number_of_crossvalidations * 2
        assert len(cross_val_data) == PolyClass.number_of_crossvalidations * 2
        for i in range(1, PolyClass.number_of_crossvalidations + 1):
            assert (
                training_data["training_set_" + str(i)].shape[0]
                == expected_training_size
            )
            assert (
                training_data["training_extras_" + str(i)].shape[0]
                == expected_training_size
            )
            assert cross_val_data["test_set_" + str(i)].shape[0] == expected_test_size
            assert (
                cross_val_data["test_extras_" + str(i)].shape[0] == expected_test_size
            )
            concat_01 = np.concatenate(
                (
                    training_data["training_set_" + str(i)],
                    cross_val_data["test_set_" + str(i)],
                ),
                axis=0,
            )
            sample_data_sorted = regression_data_input[
                np.lexsort(
                    (
                        regression_data_input[:, 2],
                        regression_data_input[:, 1],
                        regression_data_input[:, 0],
                    )
                )
            ]
            concat_01_sorted = concat_01[
                np.lexsort((concat_01[:, 2], concat_01[:, 1], concat_01[:, 0]))
            ]
            np.testing.assert_equal(sample_data_sorted, concat_01_sorted)
            concat_02 = np.concatenate(
                (
                    training_data["training_extras_" + str(i)],
                    cross_val_data["test_extras_" + str(i)],
                ),
                axis=0,
            )
            additional_data_sorted = additional_data_input[
                np.lexsort(
                    (
                        additional_data_input[:, 3],
                        additional_data_input[:, 2],
                        additional_data_input[:, 1],
                        additional_data_input[:, 0],
                    )
                )
            ]
            concat_02_sorted = concat_02[
                np.lexsort(
                    (concat_02[:, 3], concat_02[:, 2], concat_02[:, 1], concat_02[:, 0])
                )
            ]
            np.testing.assert_equal(additional_data_sorted, concat_02_sorted)

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array])
    def test_polygeneration_01(self, array_type1, array_type2):
        original_data_input = array_type1(self.full_data)
        regression_data_input = array_type2(self.training_data)
        x_input_train_data = regression_data_input[:, :-1]
        data_feed = PolynomialRegression(
            original_data_input, regression_data_input, maximum_polynomial_order=1
        )
        poly_degree = 1
        output_1 = data_feed.polygeneration(
            poly_degree, data_feed.multinomials, x_input_train_data
        )
        expected_output_nr = x_input_train_data.shape[0]
        expected_output_nc = 4  # New number of features should be = 2 * max_polynomial_order + 2 for two input features
        expected_output = np.zeros((expected_output_nr, expected_output_nc))
        expected_output[:, 0] = 1
        expected_output[:, 1] = x_input_train_data[:, 0]
        expected_output[:, 2] = x_input_train_data[:, 1]
        expected_output[:, 3] = x_input_train_data[:, 0] * x_input_train_data[:, 1]
        np.testing.assert_equal(output_1, expected_output)

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array])
    def test_polygeneration_02(self, array_type1, array_type2):
        original_data_input = array_type1(self.full_data)
        regression_data_input = array_type2(self.training_data)
        x_input_train_data = regression_data_input[:, :-1]
        data_feed = PolynomialRegression(
            original_data_input, regression_data_input, maximum_polynomial_order=2
        )
        poly_degree = 2
        output_1 = data_feed.polygeneration(
            poly_degree, data_feed.multinomials, x_input_train_data
        )
        expected_output_nr = x_input_train_data.shape[0]
        expected_output_nc = 6  # New number of features should be = 2 * max_polynomial_order + 2 for two input features
        expected_output = np.zeros((expected_output_nr, expected_output_nc))
        expected_output[:, 0] = 1
        expected_output[:, 1] = x_input_train_data[:, 0]
        expected_output[:, 2] = x_input_train_data[:, 1]
        expected_output[:, 3] = x_input_train_data[:, 0] ** 2
        expected_output[:, 4] = x_input_train_data[:, 1] ** 2
        expected_output[:, 5] = x_input_train_data[:, 0] * x_input_train_data[:, 1]
        np.testing.assert_equal(output_1, expected_output)

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array])
    def test_polygeneration_03(self, array_type1, array_type2):
        original_data_input = array_type1(self.full_data)
        regression_data_input = array_type2(self.training_data)
        x_input_train_data = regression_data_input[:, :-1]
        data_feed = PolynomialRegression(
            original_data_input, regression_data_input, maximum_polynomial_order=10
        )
        poly_degree = 10
        output_1 = data_feed.polygeneration(
            poly_degree, data_feed.multinomials, x_input_train_data
        )
        expected_output_nr = x_input_train_data.shape[0]
        expected_output_nc = 22  # New number of features should be = 2 * max_polynomial_order + 2 for two input features
        expected_output = np.zeros((expected_output_nr, expected_output_nc))
        expected_output[:, 0] = 1
        expected_output[:, 1] = x_input_train_data[:, 0]
        expected_output[:, 2] = x_input_train_data[:, 1]
        expected_output[:, 3] = x_input_train_data[:, 0] ** 2
        expected_output[:, 4] = x_input_train_data[:, 1] ** 2
        expected_output[:, 5] = x_input_train_data[:, 0] ** 3
        expected_output[:, 6] = x_input_train_data[:, 1] ** 3
        expected_output[:, 7] = x_input_train_data[:, 0] ** 4
        expected_output[:, 8] = x_input_train_data[:, 1] ** 4
        expected_output[:, 9] = x_input_train_data[:, 0] ** 5
        expected_output[:, 10] = x_input_train_data[:, 1] ** 5
        expected_output[:, 11] = x_input_train_data[:, 0] ** 6
        expected_output[:, 12] = x_input_train_data[:, 1] ** 6
        expected_output[:, 13] = x_input_train_data[:, 0] ** 7
        expected_output[:, 14] = x_input_train_data[:, 1] ** 7
        expected_output[:, 15] = x_input_train_data[:, 0] ** 8
        expected_output[:, 16] = x_input_train_data[:, 1] ** 8
        expected_output[:, 17] = x_input_train_data[:, 0] ** 9
        expected_output[:, 18] = x_input_train_data[:, 1] ** 9
        expected_output[:, 19] = x_input_train_data[:, 0] ** 10
        expected_output[:, 20] = x_input_train_data[:, 1] ** 10
        expected_output[:, 21] = x_input_train_data[:, 0] * x_input_train_data[:, 1]
        np.testing.assert_equal(output_1, expected_output)

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array])
    def test_polygeneration_04(self, array_type1, array_type2):
        original_data_input = array_type1(self.full_data)
        regression_data_input = array_type2(self.training_data)
        x_input_train_data = regression_data_input[:, :-1]
        data_feed = PolynomialRegression(
            original_data_input,
            regression_data_input,
            maximum_polynomial_order=10,
            multinomials=0,
        )
        poly_degree = 10
        output_1 = data_feed.polygeneration(
            poly_degree, data_feed.multinomials, x_input_train_data
        )
        expected_output_nr = x_input_train_data.shape[0]
        expected_output_nc = 21  # New number of features should be = 2 * max_polynomial_order + 2 for two input features
        expected_output = np.zeros((expected_output_nr, expected_output_nc))
        expected_output[:, 0] = 1
        expected_output[:, 1] = x_input_train_data[:, 0]
        expected_output[:, 2] = x_input_train_data[:, 1]
        expected_output[:, 3] = x_input_train_data[:, 0] ** 2
        expected_output[:, 4] = x_input_train_data[:, 1] ** 2
        expected_output[:, 5] = x_input_train_data[:, 0] ** 3
        expected_output[:, 6] = x_input_train_data[:, 1] ** 3
        expected_output[:, 7] = x_input_train_data[:, 0] ** 4
        expected_output[:, 8] = x_input_train_data[:, 1] ** 4
        expected_output[:, 9] = x_input_train_data[:, 0] ** 5
        expected_output[:, 10] = x_input_train_data[:, 1] ** 5
        expected_output[:, 11] = x_input_train_data[:, 0] ** 6
        expected_output[:, 12] = x_input_train_data[:, 1] ** 6
        expected_output[:, 13] = x_input_train_data[:, 0] ** 7
        expected_output[:, 14] = x_input_train_data[:, 1] ** 7
        expected_output[:, 15] = x_input_train_data[:, 0] ** 8
        expected_output[:, 16] = x_input_train_data[:, 1] ** 8
        expected_output[:, 17] = x_input_train_data[:, 0] ** 9
        expected_output[:, 18] = x_input_train_data[:, 1] ** 9
        expected_output[:, 19] = x_input_train_data[:, 0] ** 10
        expected_output[:, 20] = x_input_train_data[:, 1] ** 10
        np.testing.assert_equal(output_1, expected_output)

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array])
    def test_polygeneration_05(self, array_type1, array_type2):
        original_data_input = array_type1(self.full_data)
        regression_data_input = array_type2(self.training_data)
        x_input_train_data = regression_data_input[:, :-1]
        data_feed = PolynomialRegression(
            original_data_input, regression_data_input, maximum_polynomial_order=1
        )
        poly_degree = 1
        additional_term = np.sqrt(x_input_train_data)
        output_1 = data_feed.polygeneration(
            poly_degree, data_feed.multinomials, x_input_train_data, additional_term
        )
        expected_output_nr = x_input_train_data.shape[0]
        expected_output_nc = (
            6  # New number of features should be = 2 * max_polynomial_order + 4
        )
        expected_output = np.zeros((expected_output_nr, expected_output_nc))
        expected_output[:, 0] = 1
        expected_output[:, 1] = x_input_train_data[:, 0]
        expected_output[:, 2] = x_input_train_data[:, 1]
        expected_output[:, 3] = x_input_train_data[:, 0] * x_input_train_data[:, 1]
        expected_output[:, 4] = additional_term[:, 0]
        expected_output[:, 5] = additional_term[:, 1]
        np.testing.assert_equal(output_1, expected_output)

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type", [np.array])
    def test_cost_function_01(self, array_type):
        regression_data_input = array_type(self.training_data)
        x = regression_data_input[:, :-1]
        y = regression_data_input[:, -1]
        x_data_nr = x.shape[0]
        x_data_nc = 6
        x_vector = np.zeros((x_data_nr, x_data_nc))
        x_vector[:, 0] = 1
        x_vector[:, 1] = x[:, 0]
        x_vector[:, 2] = x[:, 1]
        x_vector[:, 3] = x[:, 0] ** 2
        x_vector[:, 4] = x[:, 1] ** 2
        x_vector[:, 5] = x[:, 0] * x[:, 1]
        theta = np.zeros((x_data_nc, 1))
        expected_value = 6613.875  # Calculated externally as sum(y^2) / 2m
        output_1 = PolynomialRegression.cost_function(
            theta, x_vector, y, reg_parameter=0
        )
        assert output_1 == expected_value

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type", [np.array])
    def test_cost_function_02(self, array_type):
        regression_data_input = array_type(self.training_data)
        x = regression_data_input[:, :-1]
        y = regression_data_input[:, -1]
        x_data_nr = x.shape[0]
        x_data_nc = 6
        x_vector = np.zeros((x_data_nr, x_data_nc))
        x_vector[:, 0] = 1
        x_vector[:, 1] = x[:, 0]
        x_vector[:, 2] = x[:, 1]
        x_vector[:, 3] = x[:, 0] ** 2
        x_vector[:, 4] = x[:, 1] ** 2
        x_vector[:, 5] = x[:, 0] * x[:, 1]
        theta = np.array(
            [[4.5], [3], [3], [1], [1], [0]]
        )  # coefficients in (x1 + 1.5)^2 + (x2 + 1.5) ^ 2
        expected_value = 90.625  # Calculated externally as sum(dy^2) / 2m
        output_1 = PolynomialRegression.cost_function(
            theta, x_vector, y, reg_parameter=0
        )
        assert output_1 == expected_value

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type", [np.array])
    def test_cost_function_03(self, array_type):
        regression_data_input = array_type(self.training_data)
        x = regression_data_input[:, :-1]
        y = regression_data_input[:, -1]
        x_data_nr = x.shape[0]
        x_data_nc = 6
        x_vector = np.zeros((x_data_nr, x_data_nc))
        x_vector[:, 0] = 1
        x_vector[:, 1] = x[:, 0]
        x_vector[:, 2] = x[:, 1]
        x_vector[:, 3] = x[:, 0] ** 2
        x_vector[:, 4] = x[:, 1] ** 2
        x_vector[:, 5] = x[:, 0] * x[:, 1]
        theta = np.array(
            [[2], [2], [2], [1], [1], [0]]
        )  # Actual coefficients in (x1 + 1)^2 + (x2 + 1) ^ 2
        expected_value = 0  # Value should return zero for exact solution
        output_1 = PolynomialRegression.cost_function(
            theta, x_vector, y, reg_parameter=0
        )
        assert output_1 == expected_value

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type", [np.array])
    def test_gradient_function_01(self, array_type):
        regression_data_input = array_type(self.training_data)
        x = regression_data_input[:, :-1]
        y = regression_data_input[:, -1]
        x_data_nr = x.shape[0]
        x_data_nc = 6
        x_vector = np.zeros((x_data_nr, x_data_nc))
        x_vector[:, 0] = 1
        x_vector[:, 1] = x[:, 0]
        x_vector[:, 2] = x[:, 1]
        x_vector[:, 3] = x[:, 0] ** 2
        x_vector[:, 4] = x[:, 1] ** 2
        x_vector[:, 5] = x[:, 0] * x[:, 1]
        theta = np.zeros((x_data_nc,))
        expected_value = np.array(
            [[-97], [-635], [-635], [-5246.875], [-5246.875], [-3925]]
        )  # Calculated externally: see Excel sheet
        expected_value = expected_value.reshape(
            expected_value.shape[0],
        )
        output_1 = PolynomialRegression.gradient_function(
            theta, x_vector, y, reg_parameter=0
        )
        np.testing.assert_equal(output_1, expected_value)

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type", [np.array])
    def test_gradient_function_02(self, array_type):
        regression_data_input = array_type(self.training_data)
        x = regression_data_input[:, :-1]
        y = regression_data_input[:, -1]
        x_data_nr = x.shape[0]
        x_data_nc = 6
        x_vector = np.zeros((x_data_nr, x_data_nc))
        x_vector[:, 0] = 1
        x_vector[:, 1] = x[:, 0]
        x_vector[:, 2] = x[:, 1]
        x_vector[:, 3] = x[:, 0] ** 2
        x_vector[:, 4] = x[:, 1] ** 2
        x_vector[:, 5] = x[:, 0] * x[:, 1]
        theta = np.array(
            [[4.5], [3], [3], [1], [1], [0]]
        )  # coefficients in (x1 + 1.5)^2 + (x2 + 1.5) ^ 2
        theta = theta.reshape(
            theta.shape[0],
        )
        expected_value = np.array(
            [[12.5], [75], [75], [593.75], [593.75], [437.5]]
        )  # Calculated externally: see Excel sheet
        expected_value = expected_value.reshape(
            expected_value.shape[0],
        )
        output_1 = PolynomialRegression.gradient_function(
            theta, x_vector, y, reg_parameter=0
        )
        np.testing.assert_equal(output_1, expected_value)

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type", [np.array])
    def test_gradient_function_03(self, array_type):
        regression_data_input = array_type(self.training_data)
        x = regression_data_input[:, :-1]
        y = regression_data_input[:, -1]
        x_data_nr = x.shape[0]
        x_data_nc = 6
        x_vector = np.zeros((x_data_nr, x_data_nc))
        x_vector[:, 0] = 1
        x_vector[:, 1] = x[:, 0]
        x_vector[:, 2] = x[:, 1]
        x_vector[:, 3] = x[:, 0] ** 2
        x_vector[:, 4] = x[:, 1] ** 2
        x_vector[:, 5] = x[:, 0] * x[:, 1]
        theta = np.array(
            [[2], [2], [2], [1], [1], [0]]
        )  # Actual coefficients in (x1 + 1)^2 + (x2 + 1) ^ 2
        theta = theta.reshape(
            theta.shape[0],
        )
        expected_value = np.array(
            [[0], [0], [0], [0], [0], [0]]
        )  # Calculated externally: see Excel sheet
        expected_value = expected_value.reshape(
            expected_value.shape[0],
        )
        output_1 = PolynomialRegression.gradient_function(
            theta, x_vector, y, reg_parameter=0
        )
        np.testing.assert_equal(output_1, expected_value)

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type", [np.array, pd.DataFrame])
    def test_bfgs_parameter_optimization_01(self, array_type):
        original_data_input = array_type(self.test_data)
        # Create x vector for ax2 + bx + c: x data supplied in x_vector
        input_array = np.array(
            [
                [0, 1],
                [1, 4],
                [2, 9],
                [3, 16],
                [4, 25],
                [5, 36],
                [6, 49],
                [7, 64],
                [8, 81],
                [9, 100],
            ]
        )
        x = input_array[:, 0]
        y = input_array[:, 1]
        x_vector = np.zeros((x.shape[0], 3))
        x_vector[:, 0] = x[:,] ** 2
        x_vector[:, 1] = x[:,]
        x_vector[:, 2] = 1
        expected_value = np.array([[1.0], [2.0], [1.0]]).reshape(
            3,
        )
        data_feed = PolynomialRegression(
            original_data_input,
            input_array,
            maximum_polynomial_order=5,
            solution_method="bfgs",
        )
        output_1 = data_feed.bfgs_parameter_optimization(x_vector, y)
        assert data_feed.solution_method == "bfgs"
        np.testing.assert_array_equal(expected_value, np.round(output_1, 4))

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array])
    def test_bfgs_parameter_optimization_02(self, array_type1, array_type2):
        original_data_input = array_type1(self.full_data)
        regression_data_input = array_type2(self.training_data)
        # Create x vector for ax2 + bx + c: x data supplied in x_vector
        x = regression_data_input[:, :-1]
        y = regression_data_input[:, -1]
        x_vector = np.zeros((x.shape[0], 6))
        x_vector[:, 0] = x[:, 0] ** 2
        x_vector[:, 1] = x[:, 1] ** 2
        x_vector[:, 2] = x[:, 0]
        x_vector[:, 3] = x[:, 1]
        x_vector[:, 4] = x[:, 1] * x[:, 0]
        x_vector[:, 5] = 1
        expected_value = np.array([[1.0], [1.0], [2.0], [2.0], [0.0], [2.0]]).reshape(
            6,
        )
        data_feed = PolynomialRegression(
            original_data_input,
            regression_data_input,
            maximum_polynomial_order=4,
            solution_method="bfgs",
        )
        output_1 = data_feed.bfgs_parameter_optimization(x_vector, y)
        assert data_feed.solution_method == "bfgs"
        np.testing.assert_array_equal(expected_value, np.round(output_1, 4))

    @pytest.mark.unit
    def test_mle_estimate_01(self):
        # Create x vector for ax2 + bx + c: x data supplied in x_vector
        input_array = np.array(
            [
                [0, 1],
                [1, 4],
                [2, 9],
                [3, 16],
                [4, 25],
                [5, 36],
                [6, 49],
                [7, 64],
                [8, 81],
                [9, 100],
            ]
        )
        x = input_array[:, 0]
        y = input_array[:, 1]
        x_vector = np.zeros((x.shape[0], 3))
        x_vector[:, 0] = x[:,] ** 2
        x_vector[:, 1] = x[:,]
        x_vector[:, 2] = 1
        expected_value = np.array([[1.0], [2.0], [1.0]]).reshape(
            3,
        )
        output_1 = PolynomialRegression.MLE_estimate(x_vector, y)
        np.testing.assert_array_equal(expected_value, np.round(output_1, 4))

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type", [np.array])
    def test_mle_estimate_02(self, array_type):
        regression_data_input = array_type(self.training_data)
        x = regression_data_input[:, :-1]
        y = regression_data_input[:, -1]
        x_vector = np.zeros((x.shape[0], 6))
        x_vector[:, 0] = x[:, 0] ** 2
        x_vector[:, 1] = x[:, 1] ** 2
        x_vector[:, 2] = x[:, 0]
        x_vector[:, 3] = x[:, 1]
        x_vector[:, 4] = x[:, 1] * x[:, 0]
        x_vector[:, 5] = 1
        expected_value = np.array([[1.0], [1.0], [2.0], [2.0], [0.0], [2.0]]).reshape(
            6,
        )
        output_1 = PolynomialRegression.MLE_estimate(x_vector, y)
        np.testing.assert_array_equal(expected_value, np.round(output_1, 4))

    @pytest.mark.unit
    def test_pyomo_optimization_01(self):
        x_vector = np.array([[i**2, i, 1] for i in range(10)])
        y = np.array([[i**2] for i in range(1, 11)])
        expected_value = np.array([[1.0], [2.0], [1.0]])
        output_1 = PolynomialRegression.pyomo_optimization(x_vector, y)
        np.testing.assert_array_equal(expected_value, np.round(output_1, 4))

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type", [np.array])
    def test_pyomo_optimization_02(self, array_type):
        regression_data_input = array_type(self.training_data)
        x = regression_data_input[:, :-1]
        y = regression_data_input[:, -1]
        x_vector = np.zeros((x.shape[0], 6))
        x_vector[:, 0] = x[:, 0] ** 2
        x_vector[:, 1] = x[:, 1] ** 2
        x_vector[:, 2] = x[:, 0]
        x_vector[:, 3] = x[:, 1]
        x_vector[:, 4] = x[:, 1] * x[:, 0]
        x_vector[:, 5] = 1
        expected_value = np.array([[1.0], [1.0], [2.0], [2.0], [0.0], [2.0]])
        output_1 = PolynomialRegression.pyomo_optimization(x_vector, y)
        np.testing.assert_array_equal(expected_value, np.round(output_1, 4))

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type", [np.array])
    def test_cross_validation_error_calculation_01(self, array_type):
        regression_data_input = array_type(self.training_data)
        x = regression_data_input[:, :-1]
        y = regression_data_input[:, -1].reshape(regression_data_input.shape[0], 1)
        x_data_nr = x.shape[0]
        x_data_nc = 6
        x_vector = np.zeros((x_data_nr, x_data_nc))
        x_vector[:, 0] = 1
        x_vector[:, 1] = x[:, 0]
        x_vector[:, 2] = x[:, 1]
        x_vector[:, 3] = x[:, 0] ** 2
        x_vector[:, 4] = x[:, 1] ** 2
        x_vector[:, 5] = x[:, 0] * x[:, 1]
        theta = np.zeros((x_data_nc, 1))
        expected_value = 2 * 6613.875  # Calculated externally as sum(y^2) / m
        output_1 = PolynomialRegression.cross_validation_error_calculation(
            theta, x_vector, y
        )
        assert output_1 == expected_value

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type", [np.array])
    def test_cross_validation_error_calculation_02(self, array_type):
        regression_data_input = array_type(self.training_data)
        x = regression_data_input[:, :-1]
        y = regression_data_input[:, -1].reshape(regression_data_input.shape[0], 1)
        x_data_nr = x.shape[0]
        x_data_nc = 6
        x_vector = np.zeros((x_data_nr, x_data_nc))
        x_vector[:, 0] = 1
        x_vector[:, 1] = x[:, 0]
        x_vector[:, 2] = x[:, 1]
        x_vector[:, 3] = x[:, 0] ** 2
        x_vector[:, 4] = x[:, 1] ** 2
        x_vector[:, 5] = x[:, 0] * x[:, 1]
        theta = np.array(
            [[4.5], [3], [3], [1], [1], [0]]
        )  # coefficients in (x1 + 1.5)^2 + (x2 + 1.5) ^ 2
        expected_value = 2 * 90.625  # Calculated externally as sum(dy^2) / 2m
        output_1 = PolynomialRegression.cross_validation_error_calculation(
            theta, x_vector, y
        )
        assert output_1 == expected_value

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type", [np.array])
    def test_cross_validation_error_calculation_02(self, array_type):
        regression_data_input = array_type(self.training_data)
        x = regression_data_input[:, :-1]
        y = regression_data_input[:, -1].reshape(regression_data_input.shape[0], 1)
        x_data_nr = x.shape[0]
        x_data_nc = 6
        x_vector = np.zeros((x_data_nr, x_data_nc))
        x_vector[:, 0] = 1
        x_vector[:, 1] = x[:, 0]
        x_vector[:, 2] = x[:, 1]
        x_vector[:, 3] = x[:, 0] ** 2
        x_vector[:, 4] = x[:, 1] ** 2
        x_vector[:, 5] = x[:, 0] * x[:, 1]
        theta = np.array(
            [[2], [2], [2], [1], [1], [0]]
        )  # Actual coefficients in (x1 + 1)^2 + (x2 + 1) ^ 2
        expected_value = 2 * 0  # Value should return zero for exact solution
        output_1 = PolynomialRegression.cross_validation_error_calculation(
            theta, x_vector, y
        )
        assert output_1 == expected_value

    def mock_optimization(self, x, y):
        return 10 * np.ones((x.shape[1], 1))

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array])
    @patch.object(PolynomialRegression, "MLE_estimate", mock_optimization)
    def test_polyregression_01(self, array_type1, array_type2):
        original_data_input = array_type1(self.full_data)
        regression_data_input = array_type2(self.training_data)
        data_feed = PolynomialRegression(
            original_data_input,
            regression_data_input,
            maximum_polynomial_order=5,
            solution_method="mle",
        )
        poly_order = 2
        training_data = regression_data_input[0:20, :]
        test_data = regression_data_input[20:, :]
        expected_output = 10 * np.ones((6, 1))
        output_1, _, _ = data_feed.polyregression(poly_order, training_data, test_data)
        np.testing.assert_array_equal(expected_output, output_1)

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array])
    @patch.object(
        PolynomialRegression, "bfgs_parameter_optimization", mock_optimization
    )
    def test_polyregression_02(self, array_type1, array_type2):
        original_data_input = array_type1(self.full_data)
        regression_data_input = array_type2(self.training_data)
        data_feed = PolynomialRegression(
            original_data_input,
            regression_data_input,
            maximum_polynomial_order=5,
            solution_method="bfgs",
        )
        poly_order = 2
        training_data = regression_data_input[0:20, :]
        test_data = regression_data_input[20:, :]
        expected_output = 10 * np.ones((6, 1))
        output_1, _, _ = data_feed.polyregression(poly_order, training_data, test_data)
        np.testing.assert_array_equal(expected_output, output_1)

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array])
    @patch.object(PolynomialRegression, "pyomo_optimization", mock_optimization)
    def test_polyregression_03(self, array_type1, array_type2):
        original_data_input = array_type1(self.full_data)
        regression_data_input = array_type2(self.training_data)
        data_feed = PolynomialRegression(
            original_data_input, regression_data_input, maximum_polynomial_order=5
        )
        poly_order = 2
        training_data = regression_data_input[0:20, :]
        test_data = regression_data_input[20:, :]
        expected_output = 10 * np.ones((6, 1))
        output_1, _, _ = data_feed.polyregression(poly_order, training_data, test_data)
        np.testing.assert_array_equal(expected_output, output_1)

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array])
    def test_polyregression_04(self, array_type1, array_type2):
        original_data_input = array_type1(self.full_data)
        regression_data_input = array_type2(self.training_data)
        data_feed = PolynomialRegression(
            original_data_input, regression_data_input, maximum_polynomial_order=5
        )
        poly_order = 10
        training_data = regression_data_input[0:20, :]
        test_data = regression_data_input[20:, :]
        expected_output = np.inf
        output_1, output_2, output_3 = data_feed.polyregression(
            poly_order, training_data, test_data
        )
        np.testing.assert_array_equal(expected_output, output_1)
        np.testing.assert_array_equal(expected_output, output_2)
        np.testing.assert_array_equal(expected_output, output_3)

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type", [np.array, pd.DataFrame])
    def test_surrogate_performance_01(self, array_type):
        original_data_input = array_type(self.test_data)
        input_array = np.array(
            [
                [0, 1],
                [1, 4],
                [2, 9],
                [3, 16],
                [4, 25],
                [5, 36],
                [6, 49],
                [7, 64],
                [8, 81],
                [9, 100],
            ]
        )
        order_best = 2
        phi_best = np.array([[0.0], [0.0], [0.0]])
        expected_value_1 = 38.5
        expected_value_2 = 2533.3
        expected_value_3 = -1.410256
        expected_value_4 = 0
        data_feed = PolynomialRegression(
            original_data_input, input_array, maximum_polynomial_order=5
        )
        _, output_1, output_2, output_3, output_4 = data_feed.surrogate_performance(
            phi_best, order_best
        )
        assert output_1 == expected_value_1
        assert output_2 == expected_value_2
        assert np.round(output_3, 4) == np.round(expected_value_3, 4)
        assert np.round(output_4, 4) == np.round(expected_value_4, 4)

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type", [np.array, pd.DataFrame])
    def test_surrogate_performance_02(self, array_type):
        original_data_input = array_type(self.test_data)
        input_array = np.array(
            [
                [0, 1],
                [1, 4],
                [2, 9],
                [3, 16],
                [4, 25],
                [5, 36],
                [6, 49],
                [7, 64],
                [8, 81],
                [9, 100],
            ]
        )
        order_best = 2
        phi_best = np.array([[1.0], [2.0], [1.0]])
        expected_value_1 = 0
        expected_value_2 = 0
        expected_value_3 = 1
        expected_value_4 = 1
        data_feed = PolynomialRegression(
            original_data_input, input_array, maximum_polynomial_order=5
        )
        _, output_1, output_2, output_3, output_4 = data_feed.surrogate_performance(
            phi_best, order_best
        )
        assert output_1 == expected_value_1
        assert output_2 == expected_value_2
        assert np.round(output_3, 4) == np.round(expected_value_3, 4)
        assert np.round(output_4, 4) == np.round(expected_value_4, 4)

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type", [np.array, pd.DataFrame])
    def test_surrogate_performance_03(self, array_type):
        original_data_input = array_type(self.test_data)
        input_array = np.array(
            [
                [0, 1],
                [1, 4],
                [2, 9],
                [3, 16],
                [4, 25],
                [5, 36],
                [6, 49],
                [7, 64],
                [8, 81],
                [9, 100],
            ]
        )
        order_best = 2
        phi_best = np.array([[1.0], [1.0], [1.0]])
        expected_value_1 = 4.5
        expected_value_2 = 28.5
        expected_value_3 = 0.972884259
        expected_value_4 = 0.9651369
        data_feed = PolynomialRegression(
            original_data_input, input_array, maximum_polynomial_order=5
        )
        _, output_1, output_2, output_3, output_4 = data_feed.surrogate_performance(
            phi_best, order_best
        )
        assert output_1 == expected_value_1
        assert output_2 == expected_value_2
        assert np.round(output_3, 4) == np.round(expected_value_3, 4)
        assert np.round(output_4, 4) == np.round(expected_value_4, 4)

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array])
    def test_results_generation_01(self, array_type1, array_type2):
        original_data_input = array_type1(self.full_data)
        regression_data_input = array_type2(self.training_data)
        data_feed = PolynomialRegression(
            original_data_input,
            regression_data_input,
            maximum_polynomial_order=3,
            multinomials=0,
        )
        order = 1
        beta = np.array([[0], [0], [0]])
        expected_df = pd.Series()
        row_list = np.array([["k"], ["(x_1)^1"], ["(x_2)^1"]])
        expected_df = pd.Series(
            {
                row_list[0, 0]: beta[0, 0],
                row_list[1, 0]: beta[1, 0],
                row_list[2, 0]: beta[2, 0],
            }
        )
        output_df = data_feed.results_generation(beta, order)
        assert output_df.index.to_list() == expected_df.index.to_list()
        assert expected_df.all() == output_df.all()

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array])
    def test_results_generation_02(self, array_type1, array_type2):
        original_data_input = array_type1(self.full_data)
        regression_data_input = array_type2(self.training_data)
        data_feed = PolynomialRegression(
            original_data_input,
            regression_data_input,
            maximum_polynomial_order=3,
            multinomials=0,
        )
        order = 3
        beta = np.array([[1], [0.3], [6], [500], [500000], [0.001], [50]])
        expected_df = pd.Series()
        row_list = np.array(
            [
                ["k"],
                ["(x_1)^1"],
                ["(x_2)^1"],
                ["(x_1)^2"],
                ["(x_2)^2"],
                ["(x_1)^3"],
                ["(x_2)^3"],
            ]
        )
        expected_df = pd.Series(
            {
                row_list[0, 0]: beta[0, 0],
                row_list[1, 0]: beta[1, 0],
                row_list[2, 0]: beta[2, 0],
                row_list[3, 0]: beta[3, 0],
                row_list[4, 0]: beta[4, 0],
                row_list[5, 0]: beta[5, 0],
                row_list[6, 0]: beta[6, 0],
            }
        )
        output_df = data_feed.results_generation(beta, order)
        assert output_df.index.to_list() == expected_df.index.to_list()
        assert expected_df.all() == output_df.all()

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array])
    def test_results_generation_03(self, array_type1, array_type2):
        original_data_input = array_type1(self.full_data)
        regression_data_input = array_type2(self.training_data)
        data_feed = PolynomialRegression(
            original_data_input,
            regression_data_input,
            maximum_polynomial_order=3,
            multinomials=1,
        )
        order = 2
        beta = np.array([[1], [0.3], [6], [500], [500000], [0.001]])
        expected_df = pd.Series()
        row_list = np.array(
            [["k"], ["(x_1)^1"], ["(x_2)^1"], ["(x_1)^2"], ["(x_2)^2"], ["(x_1).(x_2)"]]
        )
        expected_df = pd.Series(
            {
                row_list[0, 0]: beta[0, 0],
                row_list[1, 0]: beta[1, 0],
                row_list[2, 0]: beta[2, 0],
                row_list[3, 0]: beta[3, 0],
                row_list[4, 0]: beta[4, 0],
                row_list[5, 0]: beta[5, 0],
            }
        )
        output_df = data_feed.results_generation(beta, order)
        assert output_df.index.to_list() == expected_df.index.to_list()
        assert expected_df.all() == output_df.all()

    @pytest.mark.unit
    @patch("matplotlib.pyplot.show")
    def test_error_plotting(self, mock_show):
        mock_show.return_value = None
        # Generate typical data values for eaxch variable in order to test plot function
        plotting_data = np.array(
            [
                [1, 7, 23.1, 29.2, 0.01, 1300, -1.6, -1.1, 5],
                [2, 9, 0.0055, 0.0015, 0.006, 159, 0.34, 0.19, 10],
                [3, 6, 0.0007, 0.0009, 0.001, 2.3, 0.998, 0.994, 15],
                [4, 2, 0.0005, 0.0004, 0.0008, 0.02, 1.0, 1.0, 20],
            ]
        )
        ax1, ax2, ax3, ax4 = PolynomialRegression.error_plotting(plotting_data)
        expected_output_1 = np.array([[1, 23.1], [2, 0.0055], [3, 0.0007], [4, 0.0005]])
        expected_output_2 = np.array([[1, 29.2], [2, 0.0015], [3, 0.0009], [4, 0.0004]])
        expected_output_3 = np.array([[1, 0.01], [2, 0.006], [3, 0.001], [4, 0.0008]])
        expected_output_4 = np.array([[1, 1300], [2, 159], [3, 2.3], [4, 0.02]])
        expected_output_5 = np.array([[1, -1.6], [2, 0.34], [3, 0.998], [4, 1.0]])
        expected_output_6 = np.array([[1, -1.1], [2, 0.19], [3, 0.994], [4, 1.0]])
        # Compare the data input to the actual plotted data
        np.testing.assert_array_equal(ax1.lines[0].get_xydata(), expected_output_1)
        np.testing.assert_array_equal(ax1.lines[1].get_xydata(), expected_output_2)
        np.testing.assert_array_equal(ax2.lines[0].get_xydata(), expected_output_3)
        np.testing.assert_array_equal(ax3.lines[0].get_xydata(), expected_output_4)
        np.testing.assert_array_equal(ax4.lines[0].get_xydata(), expected_output_5)
        np.testing.assert_array_equal(ax4.lines[1].get_xydata(), expected_output_6)
        # Compare the expected subplot titles to the actual display
        assert (
            ax1.title.get_text() == "Training (green) vs Cross-validation error (red)"
        )
        assert ax2.title.get_text() == "MAE"
        assert ax3.title.get_text() == "MSE"
        assert ax4.title.get_text() == "R-squared (blue) and Adjusted R-squared (red)"
        # Compare the line/curve segments for each line plot to the actual input data
        np.testing.assert_array_equal(
            ax1.lines[0].get_path()._vertices, expected_output_1
        )
        np.testing.assert_array_equal(
            ax1.lines[1].get_path()._vertices, expected_output_2
        )
        np.testing.assert_array_equal(
            ax2.lines[0].get_path()._vertices, expected_output_3
        )
        np.testing.assert_array_equal(
            ax3.lines[0].get_path()._vertices, expected_output_4
        )
        np.testing.assert_array_equal(
            ax4.lines[0].get_path()._vertices, expected_output_5
        )
        np.testing.assert_array_equal(
            ax4.lines[1].get_path()._vertices, expected_output_6
        )

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array])
    def test_user_defined_terms_01(self, array_type1, array_type2):
        original_data_input = array_type1(self.full_data)
        regression_data_input = array_type2(self.training_data)
        num_cv = 3
        split = 0.75
        data_feed = PolynomialRegression(
            original_data_input,
            regression_data_input,
            maximum_polynomial_order=2,
            training_split=split,
            number_of_crossvalidations=num_cv,
        )
        additional_terms = [
            np.sin(regression_data_input[:, 0]),
            np.sin(regression_data_input[:, 1]),
        ]
        returned_features_array = data_feed.user_defined_terms(additional_terms)

        # Create two additional columns
        expected_array = np.zeros((regression_data_input.shape[0], 2))
        expected_array[:, 0] = np.sin(regression_data_input[:, 0])
        expected_array[:, 1] = np.sin(regression_data_input[:, 1])

        np.testing.assert_equal(expected_array, returned_features_array)

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type", [pd.DataFrame])
    def test_user_defined_terms_02(self, array_type):
        original_data_input = array_type(self.full_data)
        num_cv = 3
        split = 0.75
        data_feed = PolynomialRegression(
            original_data_input,
            original_data_input,
            maximum_polynomial_order=2,
            training_split=split,
            number_of_crossvalidations=num_cv,
        )
        additional_terms = [
            np.sin(original_data_input["x1"]),
            np.sin(original_data_input["x2"]),
        ]
        returned_features_array = data_feed.user_defined_terms(additional_terms)

        # Create two additional columns
        expected_array = np.zeros((original_data_input.values.shape[0], 2))
        expected_array[:, 0] = np.sin(original_data_input.values[:, 0])
        expected_array[:, 1] = np.sin(original_data_input.values[:, 1])

        np.testing.assert_equal(expected_array, returned_features_array)

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array])
    def test_user_defined_terms_03(self, array_type1, array_type2):
        original_data_input = array_type1(self.full_data)
        regression_data_input = array_type2(self.training_data)
        num_cv = 3
        split = 0.75
        data_feed = PolynomialRegression(
            original_data_input,
            regression_data_input,
            maximum_polynomial_order=2,
            training_split=split,
            number_of_crossvalidations=num_cv,
        )
        additional_terms = [
            np.sin(original_data_input["x1"]),
            np.sin(original_data_input["x2"]),
        ]
        with pytest.raises(Exception):
            data_feed.user_defined_terms(additional_terms)

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array])
    def test_user_defined_terms_04(self, array_type1, array_type2):
        original_data_input = array_type1(self.full_data)
        regression_data_input = array_type2(self.training_data)
        num_cv = 3
        split = 0.75
        data_feed = PolynomialRegression(
            original_data_input,
            regression_data_input,
            maximum_polynomial_order=2,
            training_split=split,
            number_of_crossvalidations=num_cv,
        )
        p = np.sin(regression_data_input[:, 0]).reshape(
            regression_data_input.shape[0], 1
        )  # 2-D array, should raise error
        additional_terms = [p]
        with pytest.raises(Exception):
            data_feed.user_defined_terms(additional_terms)

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array])
    def test_user_defined_terms_05(self, array_type1, array_type2):
        original_data_input = array_type1(self.full_data)
        regression_data_input = array_type2(self.training_data)
        num_cv = 3
        split = 0.75
        data_feed = PolynomialRegression(
            original_data_input,
            regression_data_input,
            maximum_polynomial_order=2,
            training_split=split,
            number_of_crossvalidations=num_cv,
        )
        p = np.sin(regression_data_input[:, 0]).reshape(
            regression_data_input.shape[0], 1
        )
        additional_terms = p  # Additional terms as array, not list
        with pytest.raises(ValueError):
            data_feed.user_defined_terms(additional_terms)

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array])
    def test_polynomial_regression_fitting_01(self, array_type1, array_type2):
        original_data_input = array_type1(self.full_data)
        regression_data_input = array_type2(self.training_data)
        data_feed = PolynomialRegression(
            original_data_input, regression_data_input, maximum_polynomial_order=2
        )
        data_feed.get_feature_vector()
        results = data_feed.polynomial_regression_fitting()
        assert results.fit_status == "ok"

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array])
    @patch("matplotlib.pyplot.show")
    def test_polynomial_regression_fitting_02(
        self, mock_show, array_type1, array_type2
    ):
        original_data_input = array_type1(self.full_data)
        regression_data_input = array_type2(self.training_data)
        mock_show.return_value = None
        data_feed = PolynomialRegression(
            original_data_input, regression_data_input[:5], maximum_polynomial_order=1
        )
        data_feed.get_feature_vector()
        with pytest.warns(Warning):
            results = data_feed.polynomial_regression_fitting()
            assert results.fit_status == "poor"

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array])
    def test_polynomial_regression_fitting_03(self, array_type1, array_type2):
        original_data_input = array_type1(self.full_data)
        regression_data_input = array_type2(self.training_data)
        data_feed = PolynomialRegression(
            original_data_input, regression_data_input, maximum_polynomial_order=2
        )
        data_feed.get_feature_vector()
        results = data_feed.polynomial_regression_fitting()
        x_input_train_data = regression_data_input[:, :-1]
        assert results.fit_status == "ok"

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array])
    def test_polynomial_regression_fitting_04(self, array_type1, array_type2):
        original_data_input = array_type1(self.full_data)
        regression_data_input = array_type2(self.training_data)
        data_feed = PolynomialRegression(
            original_data_input, regression_data_input, maximum_polynomial_order=2
        )
        data_feed.get_feature_vector()
        additional_regression_features = [
            np.sin(regression_data_input[:, 0]),
            np.sin(regression_data_input[:, 1]),
        ]
        results = data_feed.polynomial_regression_fitting(
            additional_regression_features
        )
        results = data_feed.polynomial_regression_fitting()
        assert results.fit_status == "ok"

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array])
    def test_get_feature_vector_01(self, array_type1, array_type2):
        original_data_input = array_type1(self.full_data)
        regression_data_input = array_type2(self.training_data)
        data_feed = PolynomialRegression(
            original_data_input,
            regression_data_input,
            maximum_polynomial_order=3,
            multinomials=0,
        )
        output = data_feed.get_feature_vector()
        expected_dict = {"x1": 0, "x2": 0}
        assert expected_dict == output.extract_values()

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type", [np.array])
    def test_get_feature_vector_02(self, array_type):
        regression_data_input = array_type(self.training_data)
        data_feed = PolynomialRegression(
            regression_data_input,
            regression_data_input,
            maximum_polynomial_order=3,
            multinomials=0,
        )
        output = data_feed.get_feature_vector()
        expected_dict = {0: 0, 1: 0}
        assert expected_dict == output.extract_values()

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array])
    def test_set_additional_terms_01(self, array_type1, array_type2):
        original_data_input = array_type1(self.full_data)
        regression_data_input = array_type2(self.training_data)
        data_feed = PolynomialRegression(
            original_data_input, regression_data_input, maximum_polynomial_order=3
        )
        data_feed.set_additional_terms("a")
        assert "a" == data_feed.additional_term_expressions
        data_feed.set_additional_terms(1)
        assert 1 == data_feed.additional_term_expressions
        data_feed.set_additional_terms([1, 2])
        assert [1, 2] == data_feed.additional_term_expressions
        data_feed.set_additional_terms(np.array([1, 2]))
        np.testing.assert_equal(np.array([1, 2]), data_feed.additional_term_expressions)

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [pd.DataFrame])
    @pytest.mark.parametrize("array_type2", [np.array])
    def test_poly_training_01(self, array_type1, array_type2):
        original_data_input = array_type1(self.full_data)
        regression_data_input = array_type2(self.training_data)
        data_feed = PolynomialRegression(
            original_data_input, regression_data_input, maximum_polynomial_order=2
        )
        data_feed.get_feature_vector()
        data_feed.training()
        assert data_feed.fit_status == "ok"

    # TODO: This does not actually assert anything, and fails to run
    # Leaving code in case someone has time to fix it in the future
    # @pytest.mark.unit
    # @pytest.mark.parametrize("array_type1", [pd.DataFrame])
    # @pytest.mark.parametrize("array_type2", [np.array])
    # def test_generate_expression(self, array_type1, array_type2):
    #     original_data_input = array_type1(self.full_data)
    #     regression_data_input = array_type2(self.training_data)
    #     data_feed = PolynomialRegression(
    #         original_data_input, regression_data_input, maximum_polynomial_order=2
    #     )
    #
    #     p = data_feed.get_feature_vector()
    #     data_feed.training()
    #
    #     poly_expr = data_feed.generate_expression((p.keys()))

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [np.array])
    @pytest.mark.parametrize("array_type2", [np.array])
    def test_pickle_load01(self, array_type1, array_type2):
        original_data_input = array_type1(self.test_data)
        regression_data_input = array_type2(self.sample_points)
        PolyClass = PolynomialRegression(
            original_data_input, regression_data_input, maximum_polynomial_order=3
        )
        PolyClass.get_feature_vector()
        PolyClass.training()
        PolyClass.pickle_load(PolyClass.filename)

    @pytest.mark.unit
    @pytest.mark.parametrize("array_type1", [np.array])
    @pytest.mark.parametrize("array_type2", [np.array])
    def test_pickle_load02(self, array_type1, array_type2):
        original_data_input = array_type1(self.test_data)
        regression_data_input = array_type2(self.sample_points)
        PolyClass = PolynomialRegression(
            original_data_input, regression_data_input, maximum_polynomial_order=3
        )
        with pytest.raises(Exception):
            PolyClass.pickle_load("file_not_existing.pickle")

    @pytest.mark.unit
    @patch("matplotlib.pyplot.show")
    @pytest.mark.parametrize("array_type1", [np.array])
    @pytest.mark.parametrize("array_type2", [np.array])
    def test_parity_residual_plots(self, mock_show, array_type1, array_type2):
        original_data_input = array_type1(self.test_data)
        regression_data_input = array_type2(self.sample_points)
        PolyClass = PolynomialRegression(
            original_data_input, regression_data_input, maximum_polynomial_order=3
        )
        PolyClass.get_feature_vector()
        PolyClass.training()

        PolyClass.parity_residual_plots()

    @pytest.mark.unit
    def test_confint_regression_01(self):
        # Create x vector for ax2 + bx + c: x data supplied in x_vector
        input_array = np.array(
            [
                [0, 1],
                [1, 4],
                [2, 9],
                [3, 16],
                [4, 25],
                [5, 36],
                [6, 49],
                [7, 64],
                [8, 81],
                [9, 100],
            ]
        )
        expected_stderror = np.array([4.541936, 0.850783])
        data_feed = PolynomialRegression(
            input_array, input_array, maximum_polynomial_order=1, overwrite=True
        )
        p = data_feed.get_feature_vector()
        res = data_feed.training()
        opt_wts = res.optimal_weights_array.reshape(expected_stderror.shape)
        output = data_feed.confint_regression(confidence=0.99)
        tval = 3.2498355440153697
        np.testing.assert_allclose(
            output["Std. error"].values, expected_stderror, atol=1e-3
        )
        np.testing.assert_allclose(
            output["Conf. int. lower"].values,
            opt_wts - tval * expected_stderror,
            atol=1e-3,
        )
        np.testing.assert_allclose(
            output["Conf. int. upper"].values,
            opt_wts + tval * expected_stderror,
            atol=1e-3,
        )

    @pytest.mark.unit
    def test_confint_regression_02(self):
        # Create x vector for ax2 + bx + c: x data supplied in x_vector
        input_array = np.array(
            [
                [0, 1],
                [1, 4],
                [2, 9],
                [3, 16],
                [4, 25],
                [5, 36],
                [6, 49],
                [7, 64],
                [8, 81],
                [9, 100],
            ]
        )
        expected_stderror = np.array([4.541936, 0.850783])
        data_feed = PolynomialRegression(
            input_array, input_array, maximum_polynomial_order=1, overwrite=True
        )
        p = data_feed.get_feature_vector()
        res = data_feed.training()
        opt_wts = res.optimal_weights_array.reshape(expected_stderror.shape)
        output = data_feed.confint_regression(confidence=0.9)
        tval = 1.8331129326536335
        np.testing.assert_allclose(
            output["Std. error"].values, expected_stderror, atol=1e-3
        )
        np.testing.assert_allclose(
            output["Conf. int. lower"].values,
            opt_wts - tval * expected_stderror,
            atol=1e-3,
        )
        np.testing.assert_allclose(
            output["Conf. int. upper"].values,
            opt_wts + tval * expected_stderror,
            atol=1e-3,
        )

    @pytest.mark.unit
    def test_confint_regression_03(self):
        # Create x vector for ax2 + bx + c: x data supplied in x_vector
        input_array = np.array(
            [
                [0, 1],
                [1, 4],
                [2, 9],
                [3, 16],
                [4, 25],
                [5, 36],
                [6, 49],
                [7, 64],
                [8, 81],
                [9, 100],
            ]
        )
        expected_stderror = np.array([0, 0, 0])
        data_feed = PolynomialRegression(
            input_array, input_array, maximum_polynomial_order=2, overwrite=True
        )
        p = data_feed.get_feature_vector()
        res = data_feed.training()
        opt_wts = res.optimal_weights_array.reshape(expected_stderror.shape)
        output = data_feed.confint_regression(confidence=0.99)
        tval = 3.2498355440153697
        np.testing.assert_allclose(
            output["Std. error"].values, expected_stderror, atol=1e-3
        )
        np.testing.assert_allclose(
            output["Conf. int. lower"].values,
            opt_wts - tval * expected_stderror,
            atol=1e-3,
        )
        np.testing.assert_allclose(
            output["Conf. int. upper"].values,
            opt_wts + tval * expected_stderror,
            atol=1e-3,
        )


if __name__ == "__main__":
    pytest.main()
