"""
Solution of equations using dense matrices.

The dense matrix is stored as a list of lists.

"""

from sympy.matrices.densetools import col, eye, augment
from sympy.matrices.densetools import rowadd, rowmul, conjugate_transpose
from sympy import sqrt, var
from sympy.core.compatibility import range
import copy

def row_echelon(matlist, K):
    """
    Returns the row echelon form of a matrix with diagonal elements
    reduced to 1.

    Examples
    ========

    >>> from sympy.matrices.densesolve import row_echelon
    >>> from sympy import QQ
    >>> a = [
    ... [QQ(3), QQ(7), QQ(4)],
    ... [QQ(2), QQ(4), QQ(5)],
    ... [QQ(6), QQ(2), QQ(3)]]
    >>> row_echelon(a, QQ)
    [[1, 7/3, 4/3], [0, 1, -7/2], [0, 0, 1]]

    See Also
    ========

    rref
    """
    result_matlist = copy.deepcopy(matlist)
    nrow = len(result_matlist)
    for i in range(nrow):
        if (result_matlist[i][i] != 1 and result_matlist[i][i] != 0):
            rowmul(result_matlist, i, 1/result_matlist[i][i], K)
        for j in range(i + 1, nrow):
            if (result_matlist[j][i] != 0):
                rowadd(result_matlist, j, i, -result_matlist[j][i], K)
    return result_matlist


def rref(matlist, K):
    """
    Returns the reduced row echelon form of a Matrix.

    Examples
    ========

    >>> from sympy.matrices.densesolve import rref
    >>> from sympy import QQ
    >>> a = [
    ... [QQ(1), QQ(2), QQ(1)],
    ... [QQ(-2), QQ(-3), QQ(1)],
    ... [QQ(3), QQ(5), QQ(0)]]
    >>> rref(a, QQ)
    [[1, 0, -5], [0, 1, 3], [0, 0, 0]]

    See Also
    ========

    row_echelon
    """
    result_matlist = copy.deepcopy(matlist)
    result_matlist = row_echelon(result_matlist, K)
    nrow = len(result_matlist)
    for i in range(nrow):
        if result_matlist[i][i] == 1:
            for j in range(i):
                rowadd(result_matlist, j, i, -result_matlist[j][i], K)
    return result_matlist


def LU(matlist, K, reverse = 0):
    """
    It computes the LU decomposition of a matrix and returns L and U
    matrices.

    Examples
    ========

    >>> from sympy.matrices.densesolve import LU
    >>> from sympy import QQ
    >>> a = [
    ... [QQ(1), QQ(2), QQ(3)],
    ... [QQ(2), QQ(-4), QQ(6)],
    ... [QQ(3), QQ(-9), QQ(-3)]]
    >>> LU(a, QQ)
    ([[1, 0, 0], [2, 1, 0], [3, 15/8, 1]], [[1, 2, 3], [0, -8, 0], [0, 0, -12]])

    See Also
    ========

    upper_triangle
    lower_triangle
    """
    nrow = len(matlist)
    new_matlist1, new_matlist2 = eye(nrow, K), copy.deepcopy(matlist)
    for i in range(nrow):
        for j in range(i + 1, nrow):
            if (new_matlist2[j][i] != 0):
                new_matlist1[j][i] = new_matlist2[j][i]/new_matlist2[i][i]
                rowadd(new_matlist2, j, i, -new_matlist2[j][i]/new_matlist2[i][i], K)
    return new_matlist1, new_matlist2


def cholesky(matlist, K):
    """
    Performs the cholesky decomposition of a Hermitian matrix and
    returns L and it's conjugate transpose.

    Examples
    ========

    >>> from sympy.matrices.densesolve import cholesky
    >>> from sympy import QQ
    >>> cholesky([[QQ(25), QQ(15), QQ(-5)], [QQ(15), QQ(18), QQ(0)], [QQ(-5), QQ(0), QQ(11)]], QQ)
    ([[5, 0, 0], [3, 3, 0], [-1, 1, 3]], [[5, 3, -1], [0, 3, 1], [0, 0, 3]])

    See Also
    ========

    cholesky_solve
    """
    new_matlist = copy.deepcopy(matlist)
    nrow = len(new_matlist)
    L = eye(nrow, K)
    for i in range(nrow):
        for j in range(i + 1):
            a = K.zero
            for k in range(j):
                a += L[i][k]*L[j][k]
            if i == j:
                L[i][j] = int(sqrt(new_matlist[i][j] - a))
            else:
                L[i][j] = (new_matlist[i][j] - a)/L[j][j]
    return L, conjugate_transpose(L, K)


def LDL(matlist, K):
    """
    Performs the LDL decomposition of a hermitian matrix and returns L, D and
    transpose of L. Only applicable to rational entries.

    Examples
    ========

    >>> from sympy.matrices.densesolve import LDL
    >>> from sympy import QQ

    >>> a = [
    ... [QQ(4), QQ(12), QQ(-16)],
    ... [QQ(12), QQ(37), QQ(-43)],
    ... [QQ(-16), QQ(-43), QQ(98)]]
    >>> LDL(a, QQ)
    ([[1, 0, 0], [3, 1, 0], [-4, 5, 1]], [[4, 0, 0], [0, 1, 0], [0, 0, 9]], [[1, 3, -4], [0, 1, 5], [0, 0, 1]])

    """
    new_matlist = copy.deepcopy(matlist)
    nrow = len(new_matlist)
    L, D = eye(nrow, K), eye(nrow, K)
    for i in range(nrow):
        for j in range(i + 1):
            a = K.zero
            for k in range(j):
                a += L[i][k]*L[j][k]*D[k][k]
            if i == j:
                D[j][j] = new_matlist[j][j] - a
            else:
                L[i][j] = (new_matlist[i][j] - a)/D[j][j]
    return L, D, conjugate_transpose(L, K)


def upper_triangle(matlist, K):
    """
    Transforms a given matrix to an upper triangle matrix by performing
    row operations on it.

    Examples
    ========

    >>> from sympy.matrices.densesolve import upper_triangle
    >>> from sympy import QQ
    >>> a = [
    ... [QQ(4,1), QQ(12,1), QQ(-16,1)],
    ... [QQ(12,1), QQ(37,1), QQ(-43,1)],
    ... [QQ(-16,1), QQ(-43,1), QQ(98,1)]]
    >>> upper_triangle(a, QQ)
    [[4, 12, -16], [0, 1, 5], [0, 0, 9]]

    See Also
    ========

    LU
    """
    copy_matlist = copy.deepcopy(matlist)
    lower_triangle, upper_triangle = LU(copy_matlist, K)
    return upper_triangle


def lower_triangle(matlist, K):
    """
    Transforms a given matrix to a lower triangle matrix by performing
    row operations on it.

    Examples
    ========

    >>> from sympy.matrices.densesolve import lower_triangle
    >>> from sympy import QQ
    >>> a = [
    ... [QQ(4,1), QQ(12,1), QQ(-16)],
    ... [QQ(12,1), QQ(37,1), QQ(-43,1)],
    ... [QQ(-16,1), QQ(-43,1), QQ(98,1)]]
    >>> lower_triangle(a, QQ)
    [[1, 0, 0], [3, 1, 0], [-4, 5, 1]]

    See Also
    ========

    LU
    """
    copy_matlist = copy.deepcopy(matlist)
    lower_triangle, upper_triangle = LU(copy_matlist, K, reverse = 1)
    return lower_triangle


def rref_solve(matlist, variable, constant, K):
    """
    Solves a system of equations using reduced row echelon form given
    a matrix of coefficients, a vector of variables and a vector of constants.

    Examples
    ========

    >>> from sympy.matrices.densesolve import rref_solve
    >>> from sympy import QQ
    >>> from sympy import Dummy
    >>> x, y, z = Dummy('x'), Dummy('y'), Dummy('z')
    >>> coefficients = [
    ... [QQ(25), QQ(15), QQ(-5)],
    ... [QQ(15), QQ(18), QQ(0)],
    ... [QQ(-5), QQ(0), QQ(11)]]
    >>> constants = [
    ... [QQ(2)],
    ... [QQ(3)],
    ... [QQ(1)]]
    >>> variables = [
    ... [x],
    ... [y],
    ... [z]]
    >>> rref_solve(coefficients, variables, constants, QQ)
    [[-1/225], [23/135], [4/45]]

    See Also
    ========

    row_echelon
    augment
    """
    new_matlist = copy.deepcopy(matlist)
    augmented = augment(new_matlist, constant, K)
    solution = rref(augmented, K)
    return col(solution, -1)


def LU_solve(matlist, variable, constant, K):
    """
    Solves a system of equations using  LU decomposition given a matrix
    of coefficients, a vector of variables and a vector of constants.

    Examples
    ========

    >>> from sympy.matrices.densesolve import LU_solve
    >>> from sympy import QQ
    >>> from sympy import Dummy
    >>> x, y, z = Dummy('x'), Dummy('y'), Dummy('z')
    >>> coefficients = [
    ... [QQ(2), QQ(-1), QQ(-2)],
    ... [QQ(-4), QQ(6), QQ(3)],
    ... [QQ(-4), QQ(-2), QQ(8)]]
    >>> variables = [
    ... [x],
    ... [y],
    ... [z]]
    >>> constants = [
    ... [QQ(-1)],
    ... [QQ(13)],
    ... [QQ(-6)]]
    >>> LU_solve(coefficients, variables, constants, QQ)
    [[2], [3], [1]]

    See Also
    ========

    LU
    forward_substitution
    backward_substitution
    """
    new_matlist = copy.deepcopy(matlist)
    nrow = len(new_matlist)
    y = []
    L, U = LU(new_matlist, K)
    for i in range(nrow):
        y.append([var('y' + str(i))])
    forward_substitution(L, y, constant, K)
    backward_substitution(U, variable, y, K)
    return variable


def cholesky_solve(matlist, variable, constant, K):
    """
    Solves a system of equations using Cholesky decomposition given
    a matrix of coefficients, a vector of variables and a vector of constants.

    Examples
    ========

    >>> from sympy.matrices.densesolve import cholesky_solve
    >>> from sympy import QQ
    >>> from sympy import Dummy
    >>> x, y, z = Dummy('x'), Dummy('y'), Dummy('z')
    >>> coefficients = [
    ... [QQ(25), QQ(15), QQ(-5)],
    ... [QQ(15), QQ(18), QQ(0)],
    ... [QQ(-5), QQ(0), QQ(11)]]
    >>> variables = [
    ... [x],
    ... [y],
    ... [z]]
    >>> coefficients = [
    ... [QQ(2)],
    ... [QQ(3)],
    ... [QQ(1)]]
    >>> cholesky_solve([[QQ(25), QQ(15), QQ(-5)], [QQ(15), QQ(18), QQ(0)], [QQ(-5), QQ(0), QQ(11)]], [[x], [y], [z]], [[QQ(2)], [QQ(3)], [QQ(1)]], QQ)
    [[-1/225], [23/135], [4/45]]

    See Also
    ========

    cholesky
    forward_substitution
    backward_substitution
    """
    new_matlist = copy.deepcopy(matlist)
    nrow = len(new_matlist)
    y = []
    L, Lstar = cholesky(new_matlist, K)
    for i in range(nrow):
        y.append([var('y' + str(i))])
    forward_substitution(L, y, constant, K)
    backward_substitution(Lstar, variable, y, K)
    return variable


def forward_substitution(lower_triangle, variable, constant, K):
    """
    Performs forward substitution given a lower triangular matrix, a
    vector of variables and a vector of constants.

    Examples
    ========

    >>> from sympy.matrices.densesolve import forward_substitution
    >>> from sympy import QQ
    >>> from sympy import Dummy
    >>> x, y, z = Dummy('x'), Dummy('y'), Dummy('z')
    >>> a = [
    ... [QQ(1), QQ(0), QQ(0)],
    ... [QQ(-2), QQ(1), QQ(0)],
    ... [QQ(-2), QQ(-1), QQ(1)]]
    >>> variables = [
    ... [x],
    ... [y],
    ... [z]]
    >>> constants = [
    ... [QQ(-1)],
    ... [QQ(13)],
    ... [QQ(-6)]]
    >>> forward_substitution(a, variables, constants, QQ)
    [[-1], [11], [3]]

    See Also
    ========

    LU_solve
    cholesky_solve
    """
    copy_lower_triangle = copy.deepcopy(lower_triangle)
    nrow = len(copy_lower_triangle)
    result = []
    for i in range(nrow):
        a = K.zero
        for j in range(i):
            a += copy_lower_triangle[i][j]*variable[j][0]
        variable[i][0] = (constant[i][0] - a)/copy_lower_triangle[i][i]
    return variable


def backward_substitution(upper_triangle, variable, constant, K):
    """
    Performs forward substitution given a lower triangular matrix,
    a vector of variables and a vector constants.

    Examples
    ========

    >>> from sympy.matrices.densesolve import backward_substitution
    >>> from sympy import QQ
    >>> from sympy import Dummy
    >>> x, y, z = Dummy('x'), Dummy('y'), Dummy('z')
    >>> a = [
    ... [QQ(2), QQ(-1), QQ(-2)],
    ... [QQ(0), QQ(4), QQ(-1)],
    ... [QQ(0), QQ(0), QQ(3)]]
    >>> variables = [
    ... [x],
    ... [y],
    ... [z]]
    >>> constants = [
    ... [QQ(-1)],
    ... [QQ(11)],
    ... [QQ(3)]]
    >>> backward_substitution(a, variables, constants, QQ)
    [[2], [3], [1]]

    See Also
    ========

    LU_solve
    cholesky_solve
    """
    copy_upper_triangle = copy.deepcopy(upper_triangle)
    nrow = len(copy_upper_triangle)
    result = []
    for i in reversed(range(nrow)):
        a = K.zero
        for j in reversed(range(i + 1, nrow)):
            a += copy_upper_triangle[i][j]*variable[j][0]
        variable[i][0] = (constant[i][0] - a)/copy_upper_triangle[i][i]
    return variable
