# Path: crm_solver/ode.py
# class Ode:
#     def __init__(self, coeff_matrix, init_condition):
#         self.coeff_matrix = coeff_matrix
#         self.init_condition = init_condition
#         self.__validate_parameters()
# 
#     def __validate_parameters(self):
#         self.__validate(self.coeff_matrix)
#         self.__validate(self.init_condition)
#         self.__validate_matrix(self.coeff_matrix)
# 
#     @staticmethod
#     def __validate(parameter):
#         if parameter is None:
#             raise ValueError('Try to define Ode class with null matrix.')
# 
#     def __validate_matrix(self, parameter):
#         if self.__is_not_square(parameter):
#             raise ValueError("The matrix must be squared")
# 
#     @staticmethod
#     def __is_not_square(matrix):
#         matrix_temp1 = matrix.shape[0]
#         if matrix.ndim > 1:
#             matrix_temp2 = matrix.shape[1]
#         else:
#             matrix_temp2 = 0
#         return matrix_temp1 != matrix_temp2
# 
#     def calculate_numerical_solution(self, steps):
#         return odeint(func=self.set_derivative_vector, y0=self.init_condition, t=steps, args=(self.coeff_matrix, steps))
# 
#     def calculate_analytical_solution(self, steps):
#         eigenvalues, eigenvectors = numpy.linalg.eig(self.coeff_matrix)
#         if self.init_condition.size == 1:
#             return self.__calculate_analytical_solution_1d(steps, eigenvalues)
#         else:
#             return self.__calculate_analytical_solution_else(steps, eigenvalues, eigenvectors)
# 
#     def __calculate_analytical_solution_else(self, steps, eigenvalues, eigenvectors):
#         analytical_solution = numpy.zeros((steps.size, self.init_condition.size))
#         temporal_constant = numpy.dot(numpy.dot(numpy.linalg.inv(eigenvectors), self.init_condition), eigenvectors)
#         for step in range(steps.size):
#             analytical_solution[step, :] = self.calculate_exp_solution(temporal_constant, eigenvalues, steps[step])
#         return analytical_solution
# 
#     def __calculate_analytical_solution_1d(self, steps, eigenvalues):
#         analytical_solution = numpy.zeros(steps.size)
#         for step in range(steps.size):
#             analytical_solution[step] = self.calculate_exp_solution(self.init_condition, eigenvalues, steps[step])
#         return analytical_solution
# 
#     @staticmethod
#     def set_derivative_vector(variable_vector, actual_position, coeff_matrix, steps):
#         if coeff_matrix.ndim == 3:
#             interp_coeff_matrix = interp1d(steps, coeff_matrix, axis=2, fill_value='extrapolate')
#             return numpy.dot(variable_vector, interp_coeff_matrix(actual_position))
#         elif coeff_matrix.ndim == 2:
#             return numpy.dot(variable_vector, coeff_matrix)
#         else:
#             raise ValueError('Rate Coefficient Matrix of dimensions: ' + str(coeff_matrix.ndim) + ' is not supported')
# 
#     @staticmethod
#     def calculate_exp_solution(init, coefficient, variable):
#         return init * numpy.exp(coefficient * variable)


import unittest
import numpy
import numpy.testing as npt
from crm_solver.ode import Ode
                  init_condition=self.INIT_CONDITION_CONSTANT_NONDIAGONAL)
        actual = ode.set_derivative_vector(variable_vector=self.INIT_CONDITION_CONSTANT_NONDIAGONAL, actual_position=0,
                                           coeff_matrix=self.COEFF_MATRIX_CONSTANT_NONDIAGONAL, steps=self.STEPS)
        self.assertEqual(actual.size, self.EXPECTED_SIZE_3)
        npt.assert_almost_equal(actual, self.EXPECTED_DERIVATIVE_CONSTANT_NONDIAGONAL, self.DECIMALS_6)

    def test_calculate_numerical_solution_for_constant_nondiagonal_case(self):
        ode = Ode(coeff_matrix=self.COEFF_MATRIX_CONSTANT_NONDIAGONAL,
                  init_condition=self.INIT_CONDITION_CONSTANT_NONDIAGONAL)
        actual = ode.calculate_numerical_solution(self.STEPS)
        self.assertEqual(actual.size, self.STEP_NUMBER * self.INIT_CONDITION_CONSTANT_NONDIAGONAL.size)
        self.assertEqual(actual.shape, (self.STEP_NUMBER, self.INIT_CONDITION_CONSTANT_NONDIAGONAL.size))
        for index in range(self.INIT_CONDITION_CONSTANT_NONDIAGONAL.size):
            self.assertIn(type(actual[-1, index]), self.ACCEPTED_TYPES)
            self.assertAlmostEqual(actual[-1, index], self.EXPECTED_RESULT_CONSTANT_NONDIAGONAL[index], self.DECIMALS_6)

    @unittest.skip('Analytical solver for constant non-diagonal case.')
    def test_calculate_analytical_solution_for_constant_nondiagonal_case(self):
        ode = Ode(coeff_matrix=self.COEFF_MATRIX_CONSTANT_NONDIAGONAL,
                  init_condition=self.INIT_CONDITION_CONSTANT_NONDIAGONAL)
        actual = ode.calculate_analytical_solution(self.STEPS)
        self.assertEqual(type(actual), numpy.ndarray)
        self.assertEqual(actual.size, self.STEP_NUMBER * self.INIT_CONDITION_CONSTANT_NONDIAGONAL.size)
        self.assertEqual(actual.shape, (self.STEP_NUMBER, self.INIT_CONDITION_CONSTANT_NONDIAGONAL.size))
        for index in range(self.INIT_CONDITION_CONSTANT_NONDIAGONAL.size):
            self.assertIn(type(actual[-1, index]), self.ACCEPTED_TYPES)
            self.assertAlmostEqual(actual[-1, index], self.EXPECTED_RESULT_CONSTANT_NONDIAGONAL[index], self.DECIMALS_6)

    @unittest.skip('Constant Non-diagonal Solvers Benchmark.')
    def test_benchmark_solvers_for_constant_nondiagonal_case(self):

# Path: crm_solver/ode.py
# class Ode:
#     def __init__(self, coeff_matrix, init_condition):
#         self.coeff_matrix = coeff_matrix
#         self.init_condition = init_condition
#         self.__validate_parameters()
# 
#     def __validate_parameters(self):
#         self.__validate(self.coeff_matrix)
#         self.__validate(self.init_condition)
#         self.__validate_matrix(self.coeff_matrix)
# 
#     @staticmethod
#     def __validate(parameter):
#         if parameter is None:
#             raise ValueError('Try to define Ode class with null matrix.')
# 
#     def __validate_matrix(self, parameter):
#         if self.__is_not_square(parameter):
#             raise ValueError("The matrix must be squared")
# 
#     @staticmethod
#     def __is_not_square(matrix):
#         matrix_temp1 = matrix.shape[0]
#         if matrix.ndim > 1:
#             matrix_temp2 = matrix.shape[1]
#         else:
#             matrix_temp2 = 0
#         return matrix_temp1 != matrix_temp2
# 
#     def calculate_numerical_solution(self, steps):
#         return odeint(func=self.set_derivative_vector, y0=self.init_condition, t=steps, args=(self.coeff_matrix, steps))
# 
#     def calculate_analytical_solution(self, steps):
#         eigenvalues, eigenvectors = numpy.linalg.eig(self.coeff_matrix)
#         if self.init_condition.size == 1:
#             return self.__calculate_analytical_solution_1d(steps, eigenvalues)
#         else:
#             return self.__calculate_analytical_solution_else(steps, eigenvalues, eigenvectors)
# 
#     def __calculate_analytical_solution_else(self, steps, eigenvalues, eigenvectors):
#         analytical_solution = numpy.zeros((steps.size, self.init_condition.size))
#         temporal_constant = numpy.dot(numpy.dot(numpy.linalg.inv(eigenvectors), self.init_condition), eigenvectors)
#         for step in range(steps.size):
#             analytical_solution[step, :] = self.calculate_exp_solution(temporal_constant, eigenvalues, steps[step])
#         return analytical_solution
# 
#     def __calculate_analytical_solution_1d(self, steps, eigenvalues):
#         analytical_solution = numpy.zeros(steps.size)
#         for step in range(steps.size):
#             analytical_solution[step] = self.calculate_exp_solution(self.init_condition, eigenvalues, steps[step])
#         return analytical_solution
# 
#     @staticmethod
#     def set_derivative_vector(variable_vector, actual_position, coeff_matrix, steps):
#         if coeff_matrix.ndim == 3:
#             interp_coeff_matrix = interp1d(steps, coeff_matrix, axis=2, fill_value='extrapolate')
#             return numpy.dot(variable_vector, interp_coeff_matrix(actual_position))
#         elif coeff_matrix.ndim == 2:
#             return numpy.dot(variable_vector, coeff_matrix)
#         else:
#             raise ValueError('Rate Coefficient Matrix of dimensions: ' + str(coeff_matrix.ndim) + ' is not supported')
# 
#     @staticmethod
#     def calculate_exp_solution(init, coefficient, variable):
#         return init * numpy.exp(coefficient * variable)


# Path: crm_solver/odetest.py
import unittest
import numpy
import numpy.testing as npt
from crm_solver.ode import Ode

                  init_condition=self.INIT_CONDITION_CONSTANT_NONDIAGONAL)
        actual = ode.set_derivative_vector(variable_vector=self.INIT_CONDITION_CONSTANT_NONDIAGONAL, actual_position=0,
                                           coeff_matrix=self.COEFF_MATRIX_CONSTANT_NONDIAGONAL, steps=self.STEPS)
        self.assertEqual(actual.size, self.EXPECTED_SIZE_3)
        npt.assert_almost_equal(actual, self.EXPECTED_DERIVATIVE_CONSTANT_NONDIAGONAL, self.DECIMALS_6)

    def test_calculate_numerical_solution_for_constant_nondiagonal_case(self):
        ode = Ode(coeff_matrix=self.COEFF_MATRIX_CONSTANT_NONDIAGONAL,
                  init_condition=self.INIT_CONDITION_CONSTANT_NONDIAGONAL)
        actual = ode.calculate_numerical_solution(self.STEPS)
        self.assertEqual(actual.size, self.STEP_NUMBER * self.INIT_CONDITION_CONSTANT_NONDIAGONAL.size)
        self.assertEqual(actual.shape, (self.STEP_NUMBER, self.INIT_CONDITION_CONSTANT_NONDIAGONAL.size))
        for index in range(self.INIT_CONDITION_CONSTANT_NONDIAGONAL.size):
            self.assertIn(type(actual[-1, index]), self.ACCEPTED_TYPES)
            self.assertAlmostEqual(actual[-1, index], self.EXPECTED_RESULT_CONSTANT_NONDIAGONAL[index], self.DECIMALS_6)

    @unittest.skip('Analytical solver for constant non-diagonal case.')
    def test_calculate_analytical_solution_for_constant_nondiagonal_case(self):
        ode = Ode(coeff_matrix=self.COEFF_MATRIX_CONSTANT_NONDIAGONAL,
                  init_condition=self.INIT_CONDITION_CONSTANT_NONDIAGONAL)
        actual = ode.calculate_analytical_solution(self.STEPS)
        self.assertEqual(type(actual), numpy.ndarray)
        self.assertEqual(actual.size, self.STEP_NUMBER * self.INIT_CONDITION_CONSTANT_NONDIAGONAL.size)
        self.assertEqual(actual.shape, (self.STEP_NUMBER, self.INIT_CONDITION_CONSTANT_NONDIAGONAL.size))
        for index in range(self.INIT_CONDITION_CONSTANT_NONDIAGONAL.size):
            self.assertIn(type(actual[-1, index]), self.ACCEPTED_TYPES)
            self.assertAlmostEqual(actual[-1, index], self.EXPECTED_RESULT_CONSTANT_NONDIAGONAL[index], self.DECIMALS_6)

    @unittest.skip('Constant Non-diagonal Solvers Benchmark.')
    def test_benchmark_solvers_for_constant_nondiagonal_case(self):

        ode = Ode(coeff_matrix=self.COEFF_MATRIX_CONSTANT_NONDIAGONAL,