[SYSTEMDS-2648] Multi return for algorithms in python

This commit change the python interface to allow multi return builtin
algorithms. Such that when we have many returns, it will parse out each
of these into a list.
diff --git a/src/main/python/systemds/operator/algorithm.py b/src/main/python/systemds/operator/algorithm.py
index a2a8e83..ccae749 100644
--- a/src/main/python/systemds/operator/algorithm.py
+++ b/src/main/python/systemds/operator/algorithm.py
@@ -22,7 +22,7 @@
 from typing import Dict
 
 from systemds.operator import OperationNode
-from systemds.script_building.dag import DAGNode
+from systemds.script_building.dag import DAGNode, OutputType
 from systemds.utils.consts import VALID_INPUT_TYPES
 
 __all__ = ['l2svm', 'lm', 'kmeans', 'pca']
@@ -77,6 +77,7 @@
     :param eps: Tolerance for the algorithm to declare convergence using WCSS change ratio.
     :param is_verbose: Boolean flag if the algorithm should be run in a verbose manner.
     :param avg_sample_size_per_centroid: The average number of records per centroid in the data samples.
+    :return: `OperationNode` List containing two outputs 1. the clusters, 2 the cluster ID associated with each row in x.
     """
 
     x._check_matrix_op()
@@ -90,7 +91,7 @@
 
     params_dict = {'X': x}
     params_dict.update(kwargs)
-    return OperationNode(x.sds_context, 'kmeans', named_input_nodes=params_dict)
+    return OperationNode(x.sds_context, 'kmeans', named_input_nodes=params_dict, output_type=OutputType.LIST, number_of_outputs=2)
 
 
 def pca(x: DAGNode, **kwargs: Dict[str, VALID_INPUT_TYPES]) -> OperationNode:
@@ -101,6 +102,7 @@
     :param K: The number of reduced dimensions.
     :param center: Boolean specifying if the input values should be centered.
     :param scale: Boolean specifying if the input values should be scaled.
+     :return: `OperationNode` List containing two outputs 1. The dimensionality reduced X input, 2. A matrix to reduce dimensionality similarly on unseen data.
     """
 
     x._check_matrix_op()
@@ -126,7 +128,7 @@
 
     params_dict = {'X': x}
     params_dict.update(kwargs)
-    return OperationNode(x.sds_context, 'pca', named_input_nodes=params_dict)
+    return OperationNode(x.sds_context, 'pca', named_input_nodes=params_dict,  output_type=OutputType.LIST, number_of_outputs=2)
 
 
 def multiLogReg(x: DAGNode, y: DAGNode, **kwargs: Dict[str, VALID_INPUT_TYPES]) -> OperationNode:
@@ -146,8 +148,9 @@
     :param reg: Regularization parameter (lambda = 1/C); intercept settings are not regularized.
     :param maxi: Maximum outer iterations of the algorithm
     :param maxii: Maximum inner iterations of the algorithm
+     :return: `OperationNode` of a matrix containing the regression parameters trained.
     """
-    
+
     x._check_matrix_op()
     if x._np_array.size == 0:
         raise ValueError("Found array with 0 feature(s) (shape={s}) while a minimum of 1 is required."
@@ -159,3 +162,35 @@
     params_dict = {'X': x, 'Y': y}
     params_dict.update(kwargs)
     return OperationNode(x.sds_context, 'multiLogReg', named_input_nodes=params_dict)
+
+
+def multiLogRegPredict(x: DAGNode, b: DAGNode, y: DAGNode, **kwargs: Dict[str, VALID_INPUT_TYPES]) -> OperationNode:
+    """
+    Performs prediction on input data x using the model trained, b.
+
+    :param x: The data to perform classification on.
+    :param b: The regression parameters trained from multiLogReg.
+    :param y: The Labels expected to be contained in the X dataset, to calculate accuracy.
+    :param verbose: Boolean specifying if the prediction should be verbose.
+    :return: `OperationNode` List containing three outputs. 
+        1. The predicted means / probabilities
+        2. The predicted response vector
+        3. The scalar value of accuracy
+    """
+
+    x._check_matrix_op()
+    b._check_matrix_op()
+    y._check_matrix_op()
+    if x._np_array.size == 0:
+        raise ValueError("Found array with 0 feature(s) (shape={s}) while a minimum of 1 is required."
+                         .format(s=x._np_array.shape))
+    if b._np_array.size == 0:
+        raise ValueError("Found array with 0 feature(s) (shape={s}) while a minimum of 1 is required."
+                         .format(s=y._np_array.shape))
+    if y._np_array.size == 0:
+        raise ValueError("Found array with 0 feature(s) (shape={s}) while a minimum of 1 is required."
+                         .format(s=y._np_array.shape))
+
+    params_dict = {'X': x, 'B': b, 'Y': y}
+    params_dict.update(kwargs)
+    return OperationNode(x.sds_context, 'multiLogRegPredict', named_input_nodes=params_dict,  output_type=OutputType.LIST, number_of_outputs=3, output_types=[OutputType.MATRIX,OutputType.MATRIX,OutputType.DOUBLE])
diff --git a/src/main/python/systemds/operator/operation_node.py b/src/main/python/systemds/operator/operation_node.py
index fbe8d19..665c294 100644
--- a/src/main/python/systemds/operator/operation_node.py
+++ b/src/main/python/systemds/operator/operation_node.py
@@ -1,4 +1,4 @@
-#-------------------------------------------------------------
+# -------------------------------------------------------------
 #
 # Licensed to the Apache Software Foundation (ASF) under one
 # or more contributor license agreements.  See the NOTICE file
@@ -17,7 +17,7 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-#-------------------------------------------------------------
+# -------------------------------------------------------------
 
 from typing import Union, Optional, Iterable, Dict, Sequence, Tuple, TYPE_CHECKING
 from multiprocessing import Process
@@ -32,21 +32,25 @@
 from systemds.script_building.dag import OutputType, DAGNode
 
 
-
 if TYPE_CHECKING:
     # to avoid cyclic dependencies during runtime
     from systemds.context import SystemDSContext
 
+
 class OperationNode(DAGNode):
     """A Node representing an operation in SystemDS"""
     _result_var: Optional[Union[float, np.array]]
     _lineage_trace: Optional[str]
     _script: Optional[DMLScript]
+    _output_types: Optional[Iterable[VALID_INPUT_TYPES]]
 
     def __init__(self, sds_context: 'SystemDSContext', operation: str,
                  unnamed_input_nodes: Iterable[VALID_INPUT_TYPES] = None,
                  named_input_nodes: Dict[str, VALID_INPUT_TYPES] = None,
-                 output_type: OutputType = OutputType.MATRIX, is_python_local_data: bool = False):
+                 output_type: OutputType = OutputType.MATRIX,
+                 is_python_local_data: bool = False,
+                 number_of_outputs=1,
+                 output_types: Iterable[OutputType] = None):
         """
         Create general `OperationNode`
 
@@ -55,7 +59,11 @@
         :param unnamed_input_nodes: inputs identified by their position, not name
         :param named_input_nodes: inputs with their respective parameter name
         :param output_type: type of the output in DML (double, matrix etc.)
-        :param is_python_local_data: if the data is local in python e.g. numpy arrays
+        :param is_python_local_data: if the data is local in python e.g. Numpy arrays
+        :param number_of_outputs: If set to other value than 1 then it is expected
+            that this operation node returns multiple values. If set remember to set the output_types value as well.
+        :param output_types: The types of output in a multi output scenario.
+            Default is None, and means every multi output is a matrix.
         """
         self.sds_context = sds_context
         if unnamed_input_nodes is None:
@@ -70,30 +78,43 @@
         self._result_var = None
         self._lineage_trace = None
         self._script = None
+        self._number_of_outputs = number_of_outputs
+        self._output_types = output_types
 
     def compute(self, verbose: bool = False, lineage: bool = False) -> \
             Union[float, np.array, Tuple[Union[float, np.array], str]]:
 
-
-
-
         if self._result_var is None or self._lineage_trace is None:
             self._script = DMLScript(self.sds_context)
             self._script.build_code(self)
-            if lineage:
-                result_variables, self._lineage_trace = self._script.execute(lineage)
-            else:
-                result_variables = self._script.execute(lineage)
-
             if verbose:
                 print("SCRIPT:")
                 print(self._script.dml_script)
 
+            if lineage:
+                result_variables, self._lineage_trace = self._script.execute(
+                    lineage)
+            else:
+                result_variables = self._script.execute(lineage)
+
             if self.output_type == OutputType.DOUBLE:
-                self._result_var = result_variables.getDouble(self._script.out_var_name)
+                self._result_var = result_variables.getDouble(
+                    self._script.out_var_name[0])
             elif self.output_type == OutputType.MATRIX:
                 self._result_var = matrix_block_to_numpy(self.sds_context.java_gateway.jvm,
-                                                         result_variables.getMatrixBlock(self._script.out_var_name))
+                                                         result_variables.getMatrixBlock(self._script.out_var_name[0]))
+            elif self.output_type == OutputType.LIST:
+                self._result_var = []
+                for idx, v in enumerate(self._script.out_var_name):
+                    if(self._output_types == None):
+                        self._result_var.append(matrix_block_to_numpy(self.sds_context.java_gateway.jvm,
+                                                                      result_variables.getMatrixBlock(v)))
+                    elif(self._output_types[idx] == OutputType.MATRIX):
+                        self._result_var.append(matrix_block_to_numpy(self.sds_context.java_gateway.jvm,
+                                                                      result_variables.getMatrixBlock(v)))
+                    else:
+                        self._result_var.append(result_variables.getDouble(
+                            self._script.out_var_name[idx]))
         if verbose:
             for x in self.sds_context.get_stdout():
                 print(x)
@@ -120,15 +141,27 @@
     def code_line(self, var_name: str, unnamed_input_vars: Sequence[str],
                   named_input_vars: Dict[str, str]) -> str:
         if self.operation in BINARY_OPERATIONS:
-            assert len(named_input_vars) == 0, 'Named parameters can not be used with binary operations'
-            assert len(unnamed_input_vars) == 2, 'Binary Operations need exactly two input variables'
+            assert len(
+                named_input_vars) == 0, 'Named parameters can not be used with binary operations'
+            assert len(
+                unnamed_input_vars) == 2, 'Binary Operations need exactly two input variables'
             return f'{var_name}={unnamed_input_vars[0]}{self.operation}{unnamed_input_vars[1]}'
+
+        inputs_comma_sep = create_params_string(
+            unnamed_input_vars, named_input_vars)
+
+        if self.output_type == OutputType.LIST:
+            output = "["
+            for idx in range(self._number_of_outputs):
+                output += f'{var_name}_{idx},'
+            output = output[:-1] + "]"
+            return f'{output}={self.operation}({inputs_comma_sep});'
         else:
-            inputs_comma_sep = create_params_string(unnamed_input_vars, named_input_vars)
             return f'{var_name}={self.operation}({inputs_comma_sep});'
 
     def pass_python_data_to_prepared_script(self, jvm: JVMView, var_name: str, prepared_script: JavaObject) -> None:
-        raise NotImplementedError('Operation node has no python local data. Missing implementation in derived class?')
+        raise NotImplementedError(
+            'Operation node has no python local data. Missing implementation in derived class?')
 
     def _check_matrix_op(self):
         """Perform checks to assure operation is allowed to be performed on data type of this `OperationNode`
@@ -137,40 +170,40 @@
         """
         assert self.output_type == OutputType.MATRIX, f'{self.operation} only supported for matrices'
 
-    def __add__(self, other: VALID_ARITHMETIC_TYPES):
+    def __add__(self, other: VALID_ARITHMETIC_TYPES) -> 'OperationNode':
         return OperationNode(self.sds_context, '+', [self, other])
 
-    def __sub__(self, other: VALID_ARITHMETIC_TYPES):
+    def __sub__(self, other: VALID_ARITHMETIC_TYPES) -> 'OperationNode':
         return OperationNode(self.sds_context, '-', [self, other])
 
-    def __mul__(self, other: VALID_ARITHMETIC_TYPES):
+    def __mul__(self, other: VALID_ARITHMETIC_TYPES) -> 'OperationNode':
         return OperationNode(self.sds_context, '*', [self, other])
 
-    def __truediv__(self, other: VALID_ARITHMETIC_TYPES):
+    def __truediv__(self, other: VALID_ARITHMETIC_TYPES) -> 'OperationNode':
         return OperationNode(self.sds_context, '/', [self, other])
 
-    def __floordiv__(self, other: VALID_ARITHMETIC_TYPES):
+    def __floordiv__(self, other: VALID_ARITHMETIC_TYPES) -> 'OperationNode':
         return OperationNode(self.sds_context, '//', [self, other])
 
     def __lt__(self, other) -> 'OperationNode':
         return OperationNode(self.sds_context, '<', [self, other])
 
-    def __le__(self, other):
+    def __le__(self, other) -> 'OperationNode':
         return OperationNode(self.sds_context, '<=', [self, other])
 
-    def __gt__(self, other):
+    def __gt__(self, other) -> 'OperationNode':
         return OperationNode(self.sds_context, '>', [self, other])
 
-    def __ge__(self, other):
+    def __ge__(self, other) -> 'OperationNode':
         return OperationNode(self.sds_context, '>=', [self, other])
 
-    def __eq__(self, other):
+    def __eq__(self, other) -> 'OperationNode':
         return OperationNode(self.sds_context, '==', [self, other])
 
-    def __ne__(self, other):
+    def __ne__(self, other) -> 'OperationNode':
         return OperationNode(self.sds_context, '!=', [self, other])
 
-    def __matmul__(self, other: VALID_ARITHMETIC_TYPES):
+    def __matmul__(self, other: VALID_ARITHMETIC_TYPES) -> 'OperationNode':
         return OperationNode(self.sds_context, '%*%', [self, other])
 
     def sum(self, axis: int = None) -> 'OperationNode':
@@ -186,7 +219,8 @@
             return OperationNode(self.sds_context, 'rowSums', [self])
         elif axis is None:
             return OperationNode(self.sds_context, 'sum', [self], output_type=OutputType.DOUBLE)
-        raise ValueError(f"Axis has to be either 0, 1 or None, for column, row or complete {self.operation}")
+        raise ValueError(
+            f"Axis has to be either 0, 1 or None, for column, row or complete {self.operation}")
 
     def mean(self, axis: int = None) -> 'OperationNode':
         """Calculate mean of matrix.
@@ -201,7 +235,8 @@
             return OperationNode(self.sds_context, 'rowMeans', [self])
         elif axis is None:
             return OperationNode(self.sds_context, 'mean', [self], output_type=OutputType.DOUBLE)
-        raise ValueError(f"Axis has to be either 0, 1 or None, for column, row or complete {self.operation}")
+        raise ValueError(
+            f"Axis has to be either 0, 1 or None, for column, row or complete {self.operation}")
 
     def var(self, axis: int = None) -> 'OperationNode':
         """Calculate variance of matrix.
@@ -216,7 +251,8 @@
             return OperationNode(self.sds_context, 'rowVars', [self])
         elif axis is None:
             return OperationNode(self.sds_context, 'var', [self], output_type=OutputType.DOUBLE)
-        raise ValueError(f"Axis has to be either 0, 1 or None, for column, row or complete {self.operation}")
+        raise ValueError(
+            f"Axis has to be either 0, 1 or None, for column, row or complete {self.operation}")
 
     def abs(self) -> 'OperationNode':
         """Calculate absolute.
@@ -287,14 +323,6 @@
         :return: `OperationNode` representing operation
         """
         return OperationNode(self.sds_context, 'tanh', [self])
-    '''
-    def rev(self) -> 'OperationNode':
-        """Calculate tan.
-
-        :return: `OperationNode` representing operation
-        """
-        return OperationNode(self.sds_context, 'rev', [self])
-    '''
 
     def moment(self, moment, weights: DAGNode = None) -> 'OperationNode':
         # TODO write tests
@@ -304,19 +332,3 @@
             unnamed_inputs.append(weights)
         unnamed_inputs.append(moment)
         return OperationNode(self.sds_context, 'moment', unnamed_inputs, output_type=OutputType.DOUBLE)
-
-    def lm(self, y: DAGNode, **kwargs) -> 'OperationNode':
-        self._check_matrix_op()
-
-        if self._np_array.size == 0:
-            raise ValueError("Found array with 0 feature(s) (shape={s}) while a minimum of 1 is required."
-                             .format(s=self._np_array.shape))
-
-        if y._np_array.size == 0:
-            raise ValueError("Found array with 0 feature(s) (shape={s}) while a minimum of 1 is required."
-                             .format(s=y._np_array.shape))
-
-        params_dict = {'X': self, 'y': y}
-        params_dict.update(kwargs)
-
-        return OperationNode(self.sds_context, 'lm', named_input_nodes=params_dict)
\ No newline at end of file
diff --git a/src/main/python/systemds/script_building/dag.py b/src/main/python/systemds/script_building/dag.py
index 1cad8eb..fa6bab9 100644
--- a/src/main/python/systemds/script_building/dag.py
+++ b/src/main/python/systemds/script_building/dag.py
@@ -33,6 +33,7 @@
 class OutputType(Enum):
     MATRIX = auto()
     DOUBLE = auto()
+    LIST = auto()
 
 
 class DAGNode(ABC):
@@ -42,6 +43,7 @@
     _named_input_nodes: Dict[str, Union['DAGNode', str, int, float, bool]]
     _output_type: OutputType
     _is_python_local_data: bool
+    _number_of_outputs: int
 
     def compute(self, verbose: bool = False, lineage: bool = False) -> Any:
         """Get result of this operation. Builds the dml script and executes it in SystemDS, before this method is called
@@ -90,3 +92,7 @@
     @property
     def is_python_local_data(self):
         return self._is_python_local_data
+    
+    @property
+    def number_of_outputs(self):
+        return self._number_of_outputs
diff --git a/src/main/python/systemds/script_building/script.py b/src/main/python/systemds/script_building/script.py
index 3568d12..8eb22af 100644
--- a/src/main/python/systemds/script_building/script.py
+++ b/src/main/python/systemds/script_building/script.py
@@ -38,8 +38,6 @@
 
     TODO caching
 
-    TODO multiple outputs
-
     TODO rerun with different inputs without recompilation
     """
     sds_context: 'SystemDSContext'
@@ -54,7 +52,7 @@
         self.dml_script = ''
         self.inputs = {}
         self.prepared_script = None
-        self.out_var_name = ''
+        self.out_var_name = []
         self._variable_counter = 0
 
     def add_code(self, code: str) -> None:
@@ -88,7 +86,7 @@
             self.prepared_script = connection.prepareScript(
                 self.dml_script,
                 _list_to_java_array(gateway, input_names),
-                _list_to_java_array(gateway, [self.out_var_name]))
+                _list_to_java_array(gateway, self.out_var_name))
             for (name, input_node) in self.inputs.items():
                 input_node.pass_python_data_to_prepared_script(
                     gateway.jvm, name, self.prepared_script)
@@ -98,7 +96,13 @@
 
         ret = self.prepared_script.executeScript()
         if lineage:
-            return ret, self.prepared_script.getLineageTrace(self.out_var_name)
+            if len(self.out_var_name) == 1:
+                return ret, self.prepared_script.getLineageTrace(self.out_var_name[0])
+            else:
+                traces = []
+                for output in self.out_var_name:
+                    traces.append(self.prepared_script.getLineageTrace(output))
+                return ret, traces
 
         return ret
 
@@ -111,7 +115,7 @@
             self.prepared_script = connection.prepareScript(
                 self.dml_script,
                 _list_to_java_array(gateway, input_names),
-                _list_to_java_array(gateway, [self.out_var_name]))
+                _list_to_java_array(gateway, self.out_var_name))
             for (name, input_node) in self.inputs.items():
                 input_node.pass_python_data_to_prepared_script(
                     gateway.jvm, name, self.prepared_script)
@@ -119,16 +123,29 @@
             connection.setLineage(True)
 
         self.prepared_script.executeScript()
-        lineage = self.prepared_script.getLineageTrace(self.out_var_name)
-        return lineage
+        if len(self.out_var_name) == 1:
+            return self.prepared_script.getLineageTrace(self.out_var_name[0])
+        else:
+            traces = []
+            for output in self.out_var_name:
+                traces.append(self.prepared_script.getLineageTrace(output))
+            return traces
+        
 
     def build_code(self, dag_root: DAGNode) -> None:
         """Builds code from our DAG
 
         :param dag_root: the topmost operation of our DAG, result of operation will be output
         """
-        self.out_var_name = self._dfs_dag_nodes(dag_root)
-        self.add_code(f'write({self.out_var_name}, \'./tmp\');')
+        baseOutVarString = self._dfs_dag_nodes(dag_root)
+        if(dag_root.number_of_outputs > 1):
+            self.out_var_name = []
+            for idx in range(dag_root.number_of_outputs):
+                self.add_code(f'write({baseOutVarString}_{idx}, \'./tmp_{idx}\');')
+                self.out_var_name.append(f'{baseOutVarString}_{idx}')
+        else:
+            self.out_var_name.append(baseOutVarString)
+            self.add_code(f'write({baseOutVarString}, \'./tmp\');')
 
     def _dfs_dag_nodes(self, dag_node: VALID_INPUT_TYPES) -> str:
         """Uses Depth-First-Search to create code from DAG
diff --git a/src/main/python/tests/algorithms/test_kmeans.py b/src/main/python/tests/algorithms/test_kmeans.py
index 3eef08e..ebf2264 100644
--- a/src/main/python/tests/algorithms/test_kmeans.py
+++ b/src/main/python/tests/algorithms/test_kmeans.py
@@ -45,7 +45,7 @@
         and use 4 clusters then they will be located in each one corner.
         """
         features = self.generate_matrices_for_k_means((500, 2), seed=1304)
-        res = kmeans(features, k=4).compute()
+        [res, classifications] = kmeans(features, k=4).compute()
 
         corners = set()
         for x in res:
diff --git a/src/main/python/tests/algorithms/test_multiLogReg.py b/src/main/python/tests/algorithms/test_multiLogReg.py
index 6c7e297..4f35f68 100644
--- a/src/main/python/tests/algorithms/test_multiLogReg.py
+++ b/src/main/python/tests/algorithms/test_multiLogReg.py
@@ -24,7 +24,7 @@
 import numpy as np
 from systemds.context import SystemDSContext
 from systemds.matrix import Matrix
-from systemds.operator.algorithm import multiLogReg
+from systemds.operator.algorithm import multiLogReg, multiLogRegPredict
 
 
 class TestMultiLogReg(unittest.TestCase):
@@ -41,30 +41,48 @@
 
     def test_simple(self):
         """
-        Test simple, if the log reg splits a dataset where everything over 1 is label 1 and under 1 is 0.
+        Test simple, if the log reg splits a dataset where everything over 1 is label 2 and under 1 is 1.
+        With manual classification.
         """
-        # Generate data
-        mu, sigma = 1, 0.1
-        X = np.reshape(np.random.normal(mu, sigma,  500), (2,250))
-        # All over 1 is true
-        f = lambda x: x[0] > 1
-        labels = f(X)
-        # Y labels as double
-        Y = np.array(labels, dtype=np.double)
-        # Transpose X to fit input format.
-        X = X.transpose()
+        [X, labels, Y] = self.gen_data()
 
         # Call algorithm
         bias = multiLogReg(Matrix(self.sds,X),Matrix(self.sds,Y)).compute()
         
         # Calculate result.
         res = np.reshape(np.dot(X, bias[:len(X[0])]) + bias[len(X[0])], (250))
-
-        f2 = lambda x: x > 0
+        f2 = lambda x: (x < 0) + 1
         accuracy = np.sum(labels == f2(res)) / 250 * 100
-
+        
         self.assertTrue(accuracy > 98)
 
+    def test_using_predict(self):
+        """
+        Test the algorithm using the predict function.
+        With builtin classification
+        """
+        [X, labels, Y] = self.gen_data()
+        # Call algorithm
+        bias = multiLogReg(Matrix(self.sds,X),Matrix(self.sds,Y)).compute()
+
+        [m, y_pred, acc] = multiLogRegPredict(Matrix(self.sds,X),Matrix(self.sds,bias), Matrix(self.sds,Y)).compute()
+
+        self.assertTrue(acc > 98)
+
+    
+    def gen_data(self):
+        np.random.seed(13241)
+        # Generate data
+        mu, sigma = 1, 0.1
+        X = np.reshape(np.random.normal(mu, sigma,  500), (2,250))
+        # All over 1 is true
+        f = lambda x: (x[0] > 1) + 1
+        labels = f(X)
+        # Y labels as double
+        Y = np.array(labels, dtype=np.double)
+        # Transpose X to fit input format.
+        X = X.transpose()
+        return X, labels, Y
 
 if __name__ == "__main__":
     unittest.main(exit=False)
diff --git a/src/main/python/tests/algorithms/test_pca.py b/src/main/python/tests/algorithms/test_pca.py
index bab18af..9f432f0 100644
--- a/src/main/python/tests/algorithms/test_pca.py
+++ b/src/main/python/tests/algorithms/test_pca.py
@@ -49,7 +49,7 @@
         m1 = self.generate_matrices_for_pca(30, seed=1304)
         X = Matrix(self.sds, m1)
         # print(features)
-        res = pca(X, K=1, scale="FALSE", center="FALSE").compute(verbose=True)
+        [res, model] = pca(X, K=1, scale="FALSE", center="FALSE").compute()
         for (x, y) in zip(m1, res):
             self.assertTrue((x[0] > 0 and y > 0) or (x[0] < 0 and y < 0))
 
@@ -58,7 +58,7 @@
         line of numbers. Here the pca should return values that are double or close to double of the last value
         """
         m1 = np.array([[1, 1], [2, 2], [3, 3], [4, 4], [5, 5]])
-        res = pca(Matrix(self.sds, m1), K=1,
+        [res, model] = pca(Matrix(self.sds, m1), K=1,
                   scale=False, center=False).compute()
         for x in range(len(m1) - 1):
             self.assertTrue(abs(res[x + 1] - res[0] * (x + 2)) < 0.001)