```// Copyright (C) 2009  Davis E. King (davis@dlib.net)
#undef DLIB_MATRIx_LA_FUNCTS_ABSTRACT_
#ifdef DLIB_MATRIx_LA_FUNCTS_ABSTRACT_

#include "matrix_abstract.h"
#include <complex>

namespace dlib
{

// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
//                             Global linear algebra functions
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------

const matrix_exp::matrix_type inv (
const matrix_exp& m
);
/*!
requires
- m is a square matrix
ensures
- returns the inverse of m
(Note that if m is singular or so close to being singular that there
is a lot of numerical error then the returned matrix will be bogus.
You can check by seeing if m*inv(m) is an identity matrix)
!*/

// ----------------------------------------------------------------------------------------

const matrix pinv (
const matrix_exp& m,
double tol = 0
);
/*!
requires
- tol >= 0
ensures
- returns the Moore-Penrose pseudoinverse of m.
- The returned matrix has m.nc() rows and m.nr() columns.
- if (tol == 0) then
- singular values less than max(m.nr(),m.nc()) times the machine epsilon
times the largest singular value are ignored.
- else
- singular values less than tol*max(singular value in m) are ignored.
!*/

// ----------------------------------------------------------------------------------------

void svd (
const matrix_exp& m,
matrix<matrix_exp::type>& u,
matrix<matrix_exp::type>& w,
matrix<matrix_exp::type>& v
);
/*!
ensures
- computes the singular value decomposition of m
- m == #u*#w*trans(#v)
- trans(#u)*#u == identity matrix
- trans(#v)*#v == identity matrix
- diag(#w) == the singular values of the matrix m in no
particular order.  All non-diagonal elements of #w are
set to 0.
- #u.nr() == m.nr()
- #u.nc() == m.nc()
- #w.nr() == m.nc()
- #w.nc() == m.nc()
- #v.nr() == m.nc()
- #v.nc() == m.nc()
- if DLIB_USE_LAPACK is #defined then the xGESVD routine
from LAPACK is used to compute the SVD.
!*/

// ----------------------------------------------------------------------------------------

long svd2 (
bool withu,
bool withv,
const matrix_exp& m,
matrix<matrix_exp::type>& u,
matrix<matrix_exp::type>& w,
matrix<matrix_exp::type>& v
);
/*!
requires
- m.nr() >= m.nc()
ensures
- computes the singular value decomposition of matrix m
- m == subm(#u,get_rect(m))*diagm(#w)*trans(#v)
- trans(#u)*#u == identity matrix
- trans(#v)*#v == identity matrix
- #w == the singular values of the matrix m in no
particular order.
- #u.nr() == m.nr()
- #u.nc() == m.nr()
- #w.nr() == m.nc()
- #w.nc() == 1
- #v.nr() == m.nc()
- #v.nc() == m.nc()
- if (widthu == false) then
- ignore the above regarding #u, it isn't computed and its
output state is undefined.
- if (widthv == false) then
- ignore the above regarding #v, it isn't computed and its
output state is undefined.
- returns an error code of 0, if no errors and 'k' if we fail to
converge at the 'kth' singular value.
- if (DLIB_USE_LAPACK is #defined) then
- if (withu == withv) then
- the xGESDD routine from LAPACK is used to compute the SVD.
- else
- the xGESVD routine from LAPACK is used to compute the SVD.
!*/

// ----------------------------------------------------------------------------------------

void svd3 (
const matrix_exp& m,
matrix<matrix_exp::type>& u,
matrix<matrix_exp::type>& w,
matrix<matrix_exp::type>& v
);
/*!
ensures
- computes the singular value decomposition of m
- m == #u*diagm(#w)*trans(#v)
- trans(#u)*#u == identity matrix
- trans(#v)*#v == identity matrix
- #w == the singular values of the matrix m in no
particular order.
- #u.nr() == m.nr()
- #u.nc() == m.nc()
- #w.nr() == m.nc()
- #w.nc() == 1
- #v.nr() == m.nc()
- #v.nc() == m.nc()
- if DLIB_USE_LAPACK is #defined then the xGESVD routine
from LAPACK is used to compute the SVD.
!*/

// ----------------------------------------------------------------------------------------

template <
typename T
>
void svd_fast (
const matrix<T>& A,
matrix<T>& u,
matrix<T>& w,
matrix<T>& v,
unsigned long l,
unsigned long q = 1
);
/*!
requires
- l > 0
- A.size() > 0
(i.e. A can't be an empty matrix)
ensures
- computes the singular value decomposition of A.
- Lets define some constants we use to document the behavior of svd_fast():
- Let m = A.nr()
- Let n = A.nc()
- Let k = min(l, min(m,n))
- Therefore, A represents an m by n matrix and svd_fast() is designed
to find a rank-k representation of it.
- if (the rank of A is <= k) then
- A == #u*diagm(#w)*trans(#v)
- else
- A is approximated by #u*diagm(#w)*trans(#v)
(i.e. In this case A can't be represented with a rank-k matrix, so the
matrix you get by trying to reconstruct A from the output of the SVD is
not exactly the same.)
- trans(#u)*#u == identity matrix
- trans(#v)*#v == identity matrix
- #w == the top k singular values of the matrix A (in no particular order).
- #u.nr() == m
- #u.nc() == k
- #w.nr() == k
- #w.nc() == 1
- #v.nr() == n
- #v.nc() == k
- This function implements the randomized subspace iteration defined in the
algorithm 4.4 and 5.1 boxes of the paper:
Finding Structure with Randomness: Probabilistic Algorithms for
Constructing Approximate Matrix Decompositions by Halko et al.
Therefore, it is very fast and suitable for use with very large matrices.
Moreover, q is the number of subspace iterations performed.  Larger
values of q might increase the accuracy of the solution but the default
value should be good for many problems.
!*/

// ----------------------------------------------------------------------------------------

template <
typename sparse_vector_type,
typename T
>
void svd_fast (
const std::vector<sparse_vector_type>& A,
matrix<T>& u,
matrix<T>& w,
matrix<T>& v,
unsigned long l,
unsigned long q = 1
);
/*!
requires
- A contains a set of sparse vectors.  See dlib/svm/sparse_vector_abstract.h
for a definition of what constitutes a sparse vector.
- l > 0
- max_index_plus_one(A) > 0
(i.e. A can't be an empty matrix)
ensures
- computes the singular value decomposition of A.  In this case, we interpret A
as a matrix of A.size() rows, where each row is defined by a sparse vector.
- Lets define some constants we use to document the behavior of svd_fast():
- Let m = A.size()
- Let n = max_index_plus_one(A)
- Let k = min(l, min(m,n))
- Therefore, A represents an m by n matrix and svd_fast() is designed
to find a rank-k representation of it.
- if (the rank of A is <= k) then
- A == #u*diagm(#w)*trans(#v)
- else
- A is approximated by #u*diagm(#w)*trans(#v)
(i.e. In this case A can't be represented with a rank-k matrix, so the
matrix you get by trying to reconstruct A from the output of the SVD is
not exactly the same.)
- trans(#u)*#u == identity matrix
- trans(#v)*#v == identity matrix
- #w == the top k singular values of the matrix A (in no particular order).
- #u.nr() == m
- #u.nc() == k
- #w.nr() == k
- #w.nc() == 1
- #v.nr() == n
- #v.nc() == k
- This function implements the randomized subspace iteration defined in the
algorithm 4.4 and 5.1 boxes of the paper:
Finding Structure with Randomness: Probabilistic Algorithms for
Constructing Approximate Matrix Decompositions by Halko et al.
Therefore, it is very fast and suitable for use with very large matrices.
Moreover, q is the number of subspace iterations performed.  Larger
values of q might increase the accuracy of the solution but the default
value should be good for many problems.
!*/

template <
typename sparse_vector_type,
typename T
>
void svd_fast (
const std::vector<sparse_vector_type>& A,
matrix<T>& w,
matrix<T>& v,
unsigned long l,
unsigned long q = 1
);
/*!
This function is identical to the above svd_fast() except it doesn't compute u.
!*/

// ----------------------------------------------------------------------------------------

template <
typename T,
long NR,
long NC,
typename MM,
typename L
>
void orthogonalize (
matrix<T,NR,NC,MM,L>& m
);
/*!
requires
- m.nr() >= m.nc()
- m.size() > 0
ensures
- #m == an orthogonal matrix with the same dimensions as m.  In particular,
the columns of #m have the same span as the columns of m.
- trans(#m)*#m == identity matrix
- This function is just shorthand for computing the QR decomposition of m
and then storing the Q factor into #m.
!*/

// ----------------------------------------------------------------------------------------

const matrix real_eigenvalues (
const matrix_exp& m
);
/*!
requires
- m.nr() == m.nc()
- matrix_exp::type == float or double
ensures
- returns a matrix E such that:
- E.nr() == m.nr()
- E.nc() == 1
- E contains the real part of all eigenvalues of the matrix m.
(note that the eigenvalues are not sorted)
!*/

// ----------------------------------------------------------------------------------------

const matrix_exp::type det (
const matrix_exp& m
);
/*!
requires
- m is a square matrix
ensures
- returns the determinant of m
!*/

// ----------------------------------------------------------------------------------------

const matrix_exp::type trace (
const matrix_exp& m
);
/*!
requires
- m is a square matrix
ensures
- returns the trace of m
(i.e. returns sum(diag(m)))
!*/

// ----------------------------------------------------------------------------------------

const matrix_exp::matrix_type chol (
const matrix_exp& A
);
/*!
requires
- A is a square matrix
ensures
- if (A has a Cholesky Decomposition) then
- returns the decomposition of A.  That is, returns a matrix L
such that L*trans(L) == A.  L will also be lower triangular.
- else
- returns a matrix with the same dimensions as A but it
will have a bogus value.  I.e. it won't be a decomposition.
In this case the algorithm returns a partial decomposition.
- You can tell when chol fails by looking at the lower right
element of the returned matrix.  If it is 0 then it means
A does not have a cholesky decomposition.

- If DLIB_USE_LAPACK is defined then the LAPACK routine xPOTRF
is used to compute the cholesky decomposition.
!*/

// ----------------------------------------------------------------------------------------

const matrix_exp::matrix_type inv_lower_triangular (
const matrix_exp& A
);
/*!
requires
- A is a square matrix
ensures
- if (A is lower triangular) then
- returns the inverse of A.
- else
- returns a matrix with the same dimensions as A but it
will have a bogus value.  I.e. it won't be an inverse.
!*/

// ----------------------------------------------------------------------------------------

const matrix_exp::matrix_type inv_upper_triangular (
const matrix_exp& A
);
/*!
requires
- A is a square matrix
ensures
- if (A is upper triangular) then
- returns the inverse of A.
- else
- returns a matrix with the same dimensions as A but it
will have a bogus value.  I.e. it won't be an inverse.
!*/

// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
//                             Matrix decomposition classes
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------

template <
typename matrix_exp_type
>
class lu_decomposition
{
/*!
REQUIREMENTS ON matrix_exp_type
must be some kind of matrix expression as defined in the
dlib/matrix/matrix_abstract.h file.   (e.g. a dlib::matrix object)
The matrix type must also contain float or double values.

WHAT THIS OBJECT REPRESENTS
This object represents something that can compute an LU
decomposition of a real valued matrix.  That is, for any
matrix A it computes matrices L, U, and a pivot vector P such
that rowm(A,P) == L*U.

The LU decomposition with pivoting always exists, even if the matrix is
singular, so the constructor will never fail.  The primary use of the
LU decomposition is in the solution of square systems of simultaneous
linear equations.  This will fail if is_singular() returns true (or
if A is very nearly singular).

If DLIB_USE_LAPACK is defined then the LAPACK routine xGETRF
is used to compute the LU decomposition.
!*/

public:

const static long NR = matrix_exp_type::NR;
const static long NC = matrix_exp_type::NC;
typedef typename matrix_exp_type::type type;
typedef typename matrix_exp_type::mem_manager_type mem_manager_type;
typedef typename matrix_exp_type::layout_type layout_type;

typedef matrix<type,0,0,mem_manager_type,layout_type>  matrix_type;
typedef matrix<type,NR,1,mem_manager_type,layout_type> column_vector_type;
typedef matrix<long,NR,1,mem_manager_type,layout_type> pivot_column_vector_type;

template <typename EXP>
lu_decomposition (
const matrix_exp<EXP> &A
);
/*!
requires
- EXP::type == lu_decomposition::type
- A.size() > 0
ensures
- #nr() == A.nr()
- #nc() == A.nc()
- #is_square() == (A.nr() == A.nc())
- computes the LU factorization of the given A matrix.
!*/

bool is_square (
) const;
/*!
ensures
- if (the input A matrix was a square matrix) then
- returns true
- else
- returns false
!*/

bool is_singular (
) const;
/*!
requires
- is_square() == true
ensures
- if (the input A matrix is singular) then
- returns true
- else
- returns false
!*/

long nr(
) const;
/*!
ensures
- returns the number of rows in the input matrix
!*/

long nc(
) const;
/*!
ensures
- returns the number of columns in the input matrix
!*/

const matrix_type get_l (
) const;
/*!
ensures
- returns the lower triangular L factor of the LU factorization.
- L.nr() == nr()
- L.nc() == min(nr(),nc())
!*/

const matrix_type get_u (
) const;
/*!
ensures
- returns the upper triangular U factor of the LU factorization.
- U.nr() == min(nr(),nc())
- U.nc() == nc()
!*/

const pivot_column_vector_type& get_pivot (
) const;
/*!
ensures
- returns the pivot permutation vector.  That is,
if A is the input matrix then this function
returns a vector P such that:
- rowm(A,P) == get_l()*get_u()
- P.nr() == A.nr()
!*/

type det (
) const;
/*!
requires
- is_square() == true
ensures
- computes and returns the determinant of the input
matrix using LU factors.
!*/

template <typename EXP>
const matrix_type solve (
const matrix_exp<EXP> &B
) const;
/*!
requires
- EXP::type == lu_decomposition::type
- is_square() == true
- B.nr() == nr()
ensures
- Let A denote the input matrix to this class's constructor.
Then this function solves A*X == B for X and returns X.
- Note that if A is singular (or very close to singular) then
the X returned by this function won't fit A*X == B very well (if at all).
!*/

};

// ----------------------------------------------------------------------------------------

template <
typename matrix_exp_type
>
class cholesky_decomposition
{
/*!
REQUIREMENTS ON matrix_exp_type
must be some kind of matrix expression as defined in the
dlib/matrix/matrix_abstract.h file.   (e.g. a dlib::matrix object)
The matrix type must also contain float or double values.

WHAT THIS OBJECT REPRESENTS
This object represents something that can compute a cholesky
decomposition of a real valued matrix.  That is, for any
symmetric, positive definite matrix A, it computes a lower
triangular matrix L such that A == L*trans(L).

If the matrix is not symmetric or positive definite, the function
computes only a partial decomposition.  This can be tested with
the is_spd() flag.

If DLIB_USE_LAPACK is defined then the LAPACK routine xPOTRF
is used to compute the cholesky decomposition.
!*/

public:

const static long NR = matrix_exp_type::NR;
const static long NC = matrix_exp_type::NC;
typedef typename matrix_exp_type::type type;
typedef typename matrix_exp_type::mem_manager_type mem_manager_type;
typedef typename matrix_exp_type::layout_type layout_type;

typedef typename matrix_exp_type::matrix_type matrix_type;
typedef matrix<type,NR,1,mem_manager_type,layout_type> column_vector_type;

template <typename EXP>
cholesky_decomposition(
const matrix_exp<EXP>& A
);
/*!
requires
- EXP::type == cholesky_decomposition::type
- A.size() > 0
- A.nr() == A.nc()
(i.e. A must be a square matrix)
ensures
- if (A is symmetric positive-definite) then
- #is_spd() == true
- Constructs a lower triangular matrix L, such that L*trans(L) == A.
and #get_l() == L
- else
- #is_spd() == false
!*/

bool is_spd(
) const;
/*!
ensures
- if (the input matrix was symmetric positive-definite) then
- returns true
- else
- returns false
!*/

const matrix_type& get_l(
) const;
/*!
ensures
- returns the lower triangular factor, L, such that L*trans(L) == A
(where A is the input matrix to this class's constructor)
- Note that if A is not symmetric positive definite or positive semi-definite
then the equation L*trans(L) == A won't hold.
!*/

template <typename EXP>
const matrix solve (
const matrix_exp<EXP>& B
) const;
/*!
requires
- EXP::type == cholesky_decomposition::type
- B.nr() == get_l().nr()
(i.e. the number of rows in B must match the number of rows in the
input matrix A)
ensures
- Let A denote the input matrix to this class's constructor.  Then
this function solves A*X = B for X and returns X.
- Note that if is_spd() == false or A was really close to being
non-SPD then the solver will fail to find an accurate solution.
!*/

};

// ----------------------------------------------------------------------------------------

template <
typename matrix_exp_type
>
class qr_decomposition
{
/*!
REQUIREMENTS ON matrix_exp_type
must be some kind of matrix expression as defined in the
dlib/matrix/matrix_abstract.h file.   (e.g. a dlib::matrix object)
The matrix type must also contain float or double values.

WHAT THIS OBJECT REPRESENTS
This object represents something that can compute a classical
QR decomposition of an m-by-n real valued matrix A with m >= n.

The QR decomposition is an m-by-n orthogonal matrix Q and an
n-by-n upper triangular matrix R so that A == Q*R. The QR decomposition
always exists, even if the matrix does not have full rank, so the
constructor will never fail.  The primary use of the QR decomposition
is in the least squares solution of non-square systems of simultaneous
linear equations.  This will fail if is_full_rank() returns false or
A is very nearly not full rank.

The Q and R factors can be retrieved via the get_q() and get_r()
methods. Furthermore, a solve() method is provided to find the
least squares solution of Ax=b using the QR factors.

If DLIB_USE_LAPACK is #defined then the xGEQRF routine
from LAPACK is used to compute the QR decomposition.
!*/

public:

const static long NR = matrix_exp_type::NR;
const static long NC = matrix_exp_type::NC;
typedef typename matrix_exp_type::type type;
typedef typename matrix_exp_type::mem_manager_type mem_manager_type;
typedef typename matrix_exp_type::layout_type layout_type;

typedef matrix<type,0,0,mem_manager_type,layout_type> matrix_type;

template <typename EXP>
qr_decomposition(
const matrix_exp<EXP>& A
);
/*!
requires
- EXP::type == qr_decomposition::type
- A.nr() >= A.nc()
- A.size() > 0
ensures
- #nr() == A.nr()
- #nc() == A.nc()
- computes the QR decomposition of the given A matrix.
!*/

bool is_full_rank(
) const;
/*!
ensures
- if (the input A matrix had full rank) then
- returns true
- else
- returns false
!*/

long nr(
) const;
/*!
ensures
- returns the number of rows in the input matrix
!*/

long nc(
) const;
/*!
ensures
- returns the number of columns in the input matrix
!*/

const matrix_type get_r (
) const;
/*!
ensures
- returns a matrix R such that:
- R is the upper triangular factor, R, of the QR factorization
- get_q()*R == input matrix A
- R.nr() == nc()
- R.nc() == nc()
!*/

const matrix_type get_q (
) const;
/*!
ensures
- returns a matrix Q such that:
- Q is the economy-sized orthogonal factor Q from the QR
factorization.
- trans(Q)*Q == identity matrix
- Q*get_r() == input matrix A
- Q.nr() == nr()
- Q.nc() == nc()
!*/

void get_q (
matrix_type& Q
) const;
/*!
ensures
- #Q == get_q()
- This function exists to allow a user to get the Q matrix without the
overhead of returning a matrix by value.
!*/

template <typename EXP>
const matrix_type solve (
const matrix_exp<EXP>& B
) const;
/*!
requires
- EXP::type == qr_decomposition::type
- B.nr() == nr()
ensures
- Let A denote the input matrix to this class's constructor.
Then this function finds the least squares solution to the equation A*X = B
and returns X.  X has the following properties:
- X is the matrix that minimizes the two norm of A*X-B.  That is, it
minimizes sum(squared(A*X - B)).
- X.nr() == nc()
- X.nc() == B.nc()
- Note that this function will fail to output a good solution if is_full_rank() == false
or the A matrix is close to not being full rank.
!*/

};

// ----------------------------------------------------------------------------------------

template <
typename matrix_exp_type
>
class eigenvalue_decomposition
{
/*!
REQUIREMENTS ON matrix_exp_type
must be some kind of matrix expression as defined in the
dlib/matrix/matrix_abstract.h file.   (e.g. a dlib::matrix object)
The matrix type must also contain float or double values.

WHAT THIS OBJECT REPRESENTS
This object represents something that can compute an eigenvalue
decomposition of a real valued matrix.   So it gives
you the set of eigenvalues and eigenvectors for a matrix.

Let A denote the input matrix to this object's constructor.  Then
what this object does is it finds two matrices, D and V, such that
- A*V == V*D
Where V is a square matrix that contains all the eigenvectors
of the A matrix (each column of V is an eigenvector) and
D is a diagonal matrix containing the eigenvalues of A.

It is important to note that if A is symmetric or non-symmetric you
get somewhat different results.  If A is a symmetric matrix (i.e. A == trans(A))
then:
- All the eigenvalues and eigenvectors of A are real numbers.
- Because of this there isn't really any point in using the
part of this class's interface that returns complex matrices.
All you need are the get_real_eigenvalues() and
get_pseudo_v() functions.
- V*trans(V) should be equal to the identity matrix.  That is, all the
eigenvectors in V should be orthonormal.
- So A == V*D*trans(V)
- If DLIB_USE_LAPACK is #defined then this object uses the xSYEVR LAPACK
routine.

On the other hand, if A is not symmetric then:
- Some of the eigenvalues and eigenvectors might be complex numbers.
- An eigenvalue is complex if and only if its corresponding eigenvector
is complex.  So you can check for this case by just checking
get_imag_eigenvalues() to see if any values are non-zero.  You don't
have to check the V matrix as well.
- V*trans(V) won't be equal to the identity matrix but it is usually
invertible.  So A == V*D*inv(V) is usually a valid statement but
A == V*D*trans(V) won't be.
- If DLIB_USE_LAPACK is #defined then this object uses the xGEEV LAPACK
routine.
!*/

public:

const static long NR = matrix_exp_type::NR;
const static long NC = matrix_exp_type::NC;
typedef typename matrix_exp_type::type type;
typedef typename matrix_exp_type::mem_manager_type mem_manager_type;
typedef typename matrix_exp_type::layout_type layout_type;

typedef typename matrix_exp_type::matrix_type matrix_type;
typedef matrix<type,NR,1,mem_manager_type,layout_type> column_vector_type;

typedef matrix<std::complex<type>,0,0,mem_manager_type,layout_type> complex_matrix_type;
typedef matrix<std::complex<type>,NR,1,mem_manager_type,layout_type> complex_column_vector_type;

template <typename EXP>
eigenvalue_decomposition(
const matrix_exp<EXP>& A
);
/*!
requires
- A.nr() == A.nc()
- A.size() > 0
- EXP::type == eigenvalue_decomposition::type
ensures
- #dim() == A.nr()
- computes the eigenvalue decomposition of A.
- #get_eigenvalues() == the eigenvalues of A
- #get_v() == all the eigenvectors of A
!*/

template <typename EXP>
eigenvalue_decomposition(
const matrix_op<op_make_symmetric<EXP> >& A
);
/*!
requires
- A.nr() == A.nc()
- A.size() > 0
- EXP::type == eigenvalue_decomposition::type
ensures
- #dim() == A.nr()
- computes the eigenvalue decomposition of the symmetric matrix A.  Does so
using a method optimized for symmetric matrices.
- #get_eigenvalues() == the eigenvalues of A
- #get_v() == all the eigenvectors of A
- moreover, since A is symmetric there won't be any imaginary eigenvalues. So
we will have:
- #get_imag_eigenvalues() == 0
- #get_real_eigenvalues() == the eigenvalues of A
- #get_pseudo_v() == all the eigenvectors of A
- diagm(#get_real_eigenvalues()) == #get_pseudo_d()

Note that the symmetric matrix operator is created by the
dlib::make_symmetric() function.  This function simply reflects
the lower triangular part of a square matrix into the upper triangular
part to create a symmetric matrix.  It can also be used to denote that a
matrix is already symmetric using the C++ type system.
!*/

long dim (
) const;
/*!
ensures
- dim() == the number of rows/columns in the input matrix A
!*/

const complex_column_vector_type get_eigenvalues (
) const;
/*!
ensures
- returns diag(get_d()).  That is, returns a
vector that contains the eigenvalues of the input
matrix.
- the returned vector has dim() rows
- the eigenvalues are not sorted in any particular way
!*/

const column_vector_type& get_real_eigenvalues (
) const;
/*!
ensures
- returns the real parts of the eigenvalues.  That is,
returns real(get_eigenvalues())
- the returned vector has dim() rows
- the eigenvalues are not sorted in any particular way
!*/

const column_vector_type& get_imag_eigenvalues (
) const;
/*!
ensures
- returns the imaginary parts of the eigenvalues.  That is,
returns imag(get_eigenvalues())
- the returned vector has dim() rows
- the eigenvalues are not sorted in any particular way
!*/

const complex_matrix_type get_v (
) const;
/*!
ensures
- returns the eigenvector matrix V that is
dim() rows by dim() columns
- Each column in V is one of the eigenvectors of the input
matrix
!*/

const complex_matrix_type get_d (
) const;
/*!
ensures
- returns a matrix D such that:
- D.nr() == dim()
- D.nc() == dim()
- diag(D) == get_eigenvalues()
(i.e. the diagonal of D contains all the eigenvalues in the input matrix)
- all off diagonal elements of D are set to 0
!*/

const matrix_type& get_pseudo_v (
) const;
/*!
ensures
- returns a matrix that is dim() rows by dim() columns
- Let A denote the input matrix given to this object's constructor.
- if (A has any imaginary eigenvalues) then
- returns the pseudo-eigenvector matrix V
- The matrix V returned by this function is structured such that:
- A*V == V*get_pseudo_d()
- else
- returns the eigenvector matrix V with A's eigenvectors as
the columns of V
- A*V == V*diagm(get_real_eigenvalues())
!*/

const matrix_type get_pseudo_d (
) const;
/*!
ensures
- The returned matrix is dim() rows by dim() columns
- Computes and returns the block diagonal eigenvalue matrix.
If the original matrix A is not symmetric, then the eigenvalue
matrix D is block diagonal with the real eigenvalues in 1-by-1
blocks and any complex eigenvalues,
a + i*b, in 2-by-2 blocks, (a, b; -b, a).  That is, if the complex
eigenvalues look like

u + iv     .        .          .      .    .
.      u - iv     .          .      .    .
.        .      a + ib       .      .    .
.        .        .        a - ib   .    .
.        .        .          .      x    .
.        .        .          .      .    y

Then D looks like

u        v        .          .      .    .
-v        u        .          .      .    .
.        .        a          b      .    .
.        .       -b          a      .    .
.        .        .          .      x    .
.        .        .          .      .    y

This keeps V (The V you get from get_pseudo_v()) a real matrix in both
symmetric and non-symmetric cases, and A*V = V*D.
- the eigenvalues are not sorted in any particular way
!*/

};

// ----------------------------------------------------------------------------------------

}

#endif // DLIB_MATRIx_LA_FUNCTS_ABSTRACT_

```