Skip to content
Snippets Groups Projects
Commit cbf35d5d authored by Tiago de Freitas Pereira's avatar Tiago de Freitas Pereira
Browse files

Binded the Generalized SVD from LAPACK (issue #6) swaping properly its output (issue #7)

parent 5010cd86
No related branches found
No related tags found
1 merge request!7Binded the Generalized SVD from LAPACK (issue #6) swaping properly its output (issue #7)
Pipeline #
/**
* @date Tue Jan 10 21:14 2016 +0100
* @author Tiago de Freitas Pereira <tiago.pereira@idiap.ch>
*
* Copyright (C) Idiap Research Institute, Martigny, Switzerland
*/
#include <stdexcept>
#include <boost/shared_array.hpp>
#include <bob.math/gsvd.h>
#include <bob.core/assert.h>
#include <bob.core/check.h>
#include <bob.core/array_copy.h>
#include <bob.math/linear.h>
// Declaration of the external LAPACK function (Divide and conquer SVD)
extern "C" void dggsvd3_(const char *jobu,
const char *jobv,
const char *jobq,
const int *M,
const int *N,
const int *P,
const int *K,
const int *L,
double *A,
const int *lda,
double *B,
const int *ldb,
double *alpha,
double *beta,
double *U,
const int *ldu,
double *V,
const int *ldv,
double *Q,
const int *ldq,
double *work,
const int *lwork,
int *iwork,
int *info);
void bob::math::gsvd( blitz::Array<double,2>& A,
blitz::Array<double,2>& B,
blitz::Array<double,2>& U,
blitz::Array<double,2>& V,
blitz::Array<double,2>& zeroR,
blitz::Array<double,2>& Q,
blitz::Array<double,2>& X,
blitz::Array<double,2>& C,
blitz::Array<double,2>& S)
{
const char jobu = 'U';
const char jobv = 'V';
const char jobq = 'Q';
// Size variables
const int M = A.extent(0);
const int N = A.extent(1);
const int P = B.extent(0);
const int lda = std::max(1,M);
const int ldb = std::max(1,P);
const int ldu = std::max(1,M);
const int ldv = std::max(1,P);
const int ldq = std::max(1,N);
int K = 0; //out
int L = 0; //out
// Prepares to call LAPACK function:
// We will decompose A^T rather than A and B^T rather than B to reduce the required number of copy
// We recall that FORTRAN/LAPACK is column-major order whereas blitz arrays
// are row-major order by default.
//A_lapack = A^T
blitz::Array<double,2> A_blitz_lapack(bob::core::array::ccopy(const_cast<blitz::Array<double,2>&>(A).transpose(1,0)));
double* A_lapack = A_blitz_lapack.data();
//B_lapack = B^T
blitz::Array<double,2> B_blitz_lapack(bob::core::array::ccopy(const_cast<blitz::Array<double,2>&>(B).transpose(1,0)));
double* B_lapack = B_blitz_lapack.data();
// U. We will trainpose this one in the end
double *U_lapack = U.data();
// V. We will trainpose this one in the end
double *V_lapack = V.data();
// For Q, we will return the transpose
double *Q_lapack = Q.data();
// In LAPACK C and S is 1-d. Our code makes it diagonal
blitz::Array<double,1> C_1d(N); C_1d = 0;
double *C_lapack = C_1d.data();
blitz::Array<double,1> S_1d(N); S_1d = 0;
double *S_lapack = S_1d.data();
const int lwork_query = -1;
double work_query;
boost::shared_array<int> iwork(new int[N]);
int info = 0;
// A/ Queries the optimal size of the working array
dggsvd3_(&jobu,
&jobv,
&jobq,
&M,
&N,
&P,
&K,
&L,
A_lapack,
&lda,
B_lapack,
&ldb,
C_lapack,
S_lapack,
U_lapack,
&ldu,
V_lapack,
&ldv,
Q_lapack,
&ldq,
&work_query,
&lwork_query,
iwork.get(),
&info);
if (info != 0)
throw std::runtime_error("The LAPACK dggsvd3 function returned a non-zero value during the checking");
// B/ Computes
const int lwork = static_cast<int>(work_query);
boost::shared_array<double> work(new double[lwork]);
dggsvd3_(&jobu,
&jobv,
&jobq,
&M,
&N,
&P,
&K,
&L,
A_lapack,
&lda,
B_lapack,
&ldb,
C_lapack,
S_lapack,
U_lapack,
&ldu,
V_lapack,
&ldv,
Q_lapack,
&ldq,
work.get(),
&lwork,
iwork.get(),
&info);
if (info != 0)
throw std::runtime_error("The LAPACK dggsvd3 function returned a non-zero value during the computation.");
/*
According to the website http://www.netlib.org/lapack/explore-html/d1/d7e/group__double_g_esing_ga4a187519e5c71da3b3f67c85e9baf0f2.html#ga4a187519e5c71da3b3f67c85e9baf0f2
if (M-K-L >= 0)
N-K-L K L
( 0 R ) = K ( 0 R11 R12 )
L ( 0 0 R22 )
Where R11, R12, R22, = A(1:K+L,N-K-L+1:N)
else
N-K-L K M-K K+L-M
( 0 R ) = K ( 0 R11 R12 R13 )
M-K ( 0 0 R22 R23 )
K+L-M ( 0 0 0 R33 )
where R11, R12, R13, R22, R23 = A(1:M, N-K-L+1:N)
where R33 = B(M-K+1:L,N+M-K-L+1:N)
*/
int r = K + L; //Dimension of R
C.resize(M, r);
S.resize(P, r);
S = 0;
C = 0;
zeroR.resize(r, N);
zeroR = 0;
X.resize(r,N);
if (M-K-L >= 0){
//1. First we need the [0 R], which has the shape (N-K-L + K + L, K+L)
zeroR(blitz::Range(0, r-1), blitz::Range(N-r, N-1)) = A_blitz_lapack.transpose(1,0)(blitz::Range(0,r-1), blitz::Range(N-r,N-1));
// 2. Now we have to deal with C and S according to http://www.netlib.org/lapack/lug/node36.html
// In the end C is m-by-r and is p-by-r, both are real, nonnegative and diagonal and C'C + S'S = I ,
// They have the following structure
// COPY AND PASTE
//2.1 Preparing C
// K L
// C = K ( I 0 )
// L ( 0 C )
// M-K-L ( 0 0 )
// A - Identity part
if (K>0){
blitz::Array<double,2> I (K,K); I= 0;
bob::math::eye_(I);
C(blitz::Range(0, K-1), blitz::Range(0, K-1)) = I;
}
// B - diag(C) part. Here the C is LxL
// Swaping
bob::math::swap_(C_1d, iwork.get(), K, std::min(M,r));
blitz::Array<double,2> C_diag (L,L); C_diag = 0;
bob::math::diag(C_1d(blitz::Range(0,L-1)), C_diag);
C(blitz::Range(K, M-1), blitz::Range(K, K+L-1)) = C_diag;
//2.2 Preparing S
// K L
//D2 = L ( 0 S )
// P-L ( 0 0 )
// A - diag(S) part
// Swap
bob::math::swap_(S_1d, iwork.get(), K, std::min(M,r));
blitz::Array<double,2> S_diag (L,L); S_diag = 0;
bob::math::diag(S_1d(blitz::Range(0,L-1)), S_diag);
S(blitz::Range(0, L-1), blitz::Range(K, K+L-1)) = S_diag;
}
else{
//1. First we need the [0 R], which has the shape (N-K-L K M-K K+L-M , k + M-K K+L-M )
//A. First part of R is in A(1:M, N-K-L+1:N)
zeroR(blitz::Range(0,M-1), blitz::Range(N-K-L, N-1)) = A_blitz_lapack.transpose(1,0)(blitz::Range(0,M-1) , blitz::Range(N-K-L,N-1));
//B. Second part of R is in B(M-K+1:L,N+M-K-L+1:N)
zeroR(blitz::Range(M, r - 1), blitz::Range(N-L+M-K,N-1)) = B_blitz_lapack.transpose(1,0)(blitz::Range(M-K, L-1) , blitz::Range(N+M-K-L, N-1));
//2. Now we have to deal with C and S according to http://www.netlib.org/lapack/lug/node36.html
// In the end C is m-by-r and is p-by-r, both are real, nonnegative and diagonal and C'C + S'S = I ,
// They have the following structure
// COPY AND PASTE
//2.1 Preparing C, where C=diag( ALPHA(K+1), ... , ALPHA(M) ),
// K M-K K+L-M
// D1 = K ( I 0 0 )
// M-K ( 0 C 0 )
// A - Identity part
if (K>0){
blitz::Array<double,2> I (K,K); I = 0;
bob::math::eye_(I);
C(blitz::Range(0, K-1), blitz::Range(0, K-1)) = I;
}
// B - diag(C) part
// Swaping
blitz::Array<double,1> C_1d_cropped(M-K); C_1d_cropped = 0;
C_1d_cropped = C_1d(blitz::Range(K,K+M-1));
bob::math::swap_(C_1d_cropped, iwork.get(), K, std::min(M,r));
blitz::Array<double,2> C_diag (M,M); C_diag = 0;
bob::math::diag(C_1d_cropped, C_diag);
C(blitz::Range(K,M-1), blitz::Range(K,M-1)) = C_diag;
//2.2 Preparing S
// K M-K K+L-M
// D2 = M-K ( 0 S 0 )
// K+L-M ( 0 0 I )
// P-L ( 0 0 0 )
// A - Identity part
if (K+L-M>0){
blitz::Array<double,2> I (K+L-M,K+L-M); I= 0;
bob::math::eye_(I);
S(blitz::Range(M-K,L-1), blitz::Range(M,K+L-1)) = I;
}
// B - diag(S) part
// Swaping
blitz::Array<double,1> S_1d_cropped(M-K); S_1d_cropped = 0;
S_1d_cropped = S_1d(blitz::Range(K,K+M-1));
bob::math::swap_(S_1d_cropped, iwork.get(), K, std::min(M,r));
blitz::Array<double,2> S_diag (M,M); S_diag = 0;
bob::math::diag(S_1d_cropped, S_diag);
S(blitz::Range(0,M-K-1), blitz::Range(K,M-1)) = S_diag;
}
// Transposing U and V
blitz::Array<double,2> Ut(
bob::core::array::ccopy(const_cast<blitz::Array<double,2>&>(U).transpose(1,0)));
U = Ut;
blitz::Array<double,2> Vt(
bob::core::array::ccopy(const_cast<blitz::Array<double,2>&>(V).transpose(1,0)));
V = Vt;
// Swaping U
bob::math::swap_(U, iwork.get(), K, std::min(M,r));
// Swaping V
bob::math::swap_(V, iwork.get(), K, std::min(M,r));
//Computing X
bob::math::prod_(zeroR, Q, X);
blitz::Array<double,2> Xt(
bob::core::array::ccopy(const_cast<blitz::Array<double,2>&>(X).transpose(1,0)));
X = Xt;
bob::math::swap_(X, iwork.get(), K, std::min(M,r));
}
/**
* @date Tue Jan 10 21:14 2016 +0100
* @author Tiago de Freitas Pereira <tiago.pereira@idiap.ch>
*
* @brief Binds the Generalized SVD
*/
#include "gsvd.h"
#include <bob.blitz/cppapi.h>
#include <bob.blitz/cleanup.h>
#include <bob.math/gsvd.h>
PyObject* py_gsvd (PyObject*, PyObject* args, PyObject* kwds) {
/* Parses input arguments in a single shot */
static const char* const_kwlist[] = { "A", "B", 0 /* Sentinel */ };
static char** kwlist = const_cast<char**>(const_kwlist);
PyBlitzArrayObject* A = 0;
PyBlitzArrayObject* B = 0;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&O&", kwlist,
&PyBlitzArray_Converter, &A,
&PyBlitzArray_Converter, &B
))
return 0;
auto A_ = make_safe(A);
auto B_ = make_safe(B);
if (A->ndim != 2 || A->type_num != NPY_FLOAT64) {
PyErr_Format(PyExc_TypeError, "`A` matrix only supports 2D 64-bit float array");
return 0;
}
if (B->ndim != 2 || B->type_num != NPY_FLOAT64) {
PyErr_Format(PyExc_TypeError, "`B` matrix only supports 2D 64-bit float array");
return 0;
}
auto A_bz = PyBlitzArrayCxx_AsBlitz<double,2>(A);
auto B_bz = PyBlitzArrayCxx_AsBlitz<double,2>(B);
const int M = A_bz->extent(0);
const int N = A_bz->extent(1);
const int P = B_bz->extent(0);
// Creating the output matrices
blitz::Array<double,2> U(M, M); U=0;
blitz::Array<double,2> V(P, P); V=0;
blitz::Array<double,2> Q(N, N); Q=0;
blitz::Array<double,2> zeroR(N, N); zeroR=0;
blitz::Array<double,2> X(N, N); X=0;
blitz::Array<double,2> C(N, N); C=0;
blitz::Array<double,2> S(N, N); S=0;
try {
bob::math::gsvd(*A_bz,*B_bz,U,V,zeroR,Q,X,C,S);
return Py_BuildValue("NNNNN",
PyBlitzArrayCxx_AsConstNumpy(U),
PyBlitzArrayCxx_AsConstNumpy(V),
PyBlitzArrayCxx_AsConstNumpy(X),
PyBlitzArrayCxx_AsConstNumpy(C),
PyBlitzArrayCxx_AsConstNumpy(S));
}
catch (std::exception& e) {
PyErr_SetString(PyExc_RuntimeError, e.what());
}
catch (...) {
PyErr_SetString(PyExc_RuntimeError, "norminv failed: unknown exception caught");
}
return 0;
}
/**
* @author Andre Anjos <andre.anjos@idiap.ch>
* @date Thu 5 Dec 12:01:57 2013
*
* @brief Declaration of components relevant for main.cpp
*/
#include <Python.h>
PyObject* py_gsvd(PyObject*, PyObject* args, PyObject* kwds);
/**
* @date Tue Jan 10 21:14 2016 +0100
* @author Tiago de Freitas Pereira <tiago.pereira@idiap.ch>
*
* @brief This file defines a function to determine the SVD decomposition
* a 2D blitz array using LAPACK.
*
* Copyright (C) Idiap Research Institute, Martigny, Switzerland
*/
#ifndef BOB_MATH_GSVD_H
#define BOB_MATH_GSVD_H
#include <blitz/array.h>
namespace bob { namespace math {
/**
* @ingroup MATH
* @{
*/
/**
* @brief Function which performs the Generalized Singular Value Decomposition
* using the 'simple' driver routine dggsvd3 of LAPACK.
* Just remembering that given A and B as input we have:
*
* A = U C X and B = V S X
* where X = [0,R]Q^T
*
*
*
* @warning The output blitz::array sigma should have the correct
* size, with zero base index. Checks are performed.
* @param A The A matrix to decompose (size MxP)
* @param B The B matrix to decompose (size NxP)
* @warning A and B must have the same number of columns, but may have different numbers of rows. If A is m-by-p and B is n-by-p, then U is m-by-m, V is n-by-n, X is p-by-q, C is m-by-q
* and S is n-by-q, where q = min(m+n,p).
* @param U The U matrix (MxM)
* @param V The V matrix (NxN)
* @param zeroR The X matrix (rxN)
* @param Q The Q matrix (Q)
* @param C The C matrix (MxQ)
* @param S The S matrix (NxQ)
*/
void gsvd(blitz::Array<double,2>& A,
blitz::Array<double,2>& B,
blitz::Array<double,2>& U,
blitz::Array<double,2>& V,
blitz::Array<double,2>& zeroR,
blitz::Array<double,2>& Q,
blitz::Array<double,2>& X,
blitz::Array<double,2>& C,
blitz::Array<double,2>& S
);
/**
* @brief Swaping using the LAPACK variable iWork
* http://www.netlib.org/lapack/explore-html/d1/d7e/group__double_g_esing_ga4a187519e5c71da3b3f67c85e9baf0f2.html#ga4a187519e5c71da3b3f67c85e9baf0f2
*
* @param A 1D Matrix
* @param indexes Sorting information
* @param n number of operations
*/
template<typename T>
void swap_(blitz::Array<T,1>& A, int* indexes, int begin, int end) {
T aux = 0;
int fortran_index = 0;
for (int i=0; i<A.extent(0); i++){
fortran_index = indexes[i]-1;
aux = A(i);
A(i) = A(fortran_index);
A(fortran_index) = aux;
}
}
/**
* @brief Swaping using the LAPACK variable iWork
* http://www.netlib.org/lapack/explore-html/d1/d7e/group__double_g_esing_ga4a187519e5c71da3b3f67c85e9baf0f2.html#ga4a187519e5c71da3b3f67c85e9baf0f2
*
* @param A D Matrix
* @param indexes Sorting information
* @param n number of operations
*/
template<typename T>
void swap_(blitz::Array<T,2>& A, int* indexes, int begin, int end) {
blitz::Array<T,1> aux(A.extent(1)); aux = 0;
int fortran_index = 0;
for (int i=begin; i<end; i++){
fortran_index = indexes[i]-1;
aux = A(blitz::Range::all(), i);
A(blitz::Range::all(), i) = A(blitz::Range::all(), fortran_index);
A(blitz::Range::all(), fortran_index) = aux;
}
}
/**
* @}
*/
}}
#endif /* BOB_MATH_GSVD_H */
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
#include "norminv.h" #include "norminv.h"
#include "scatter.h" #include "scatter.h"
#include "lp_interior_point.h" #include "lp_interior_point.h"
#include "gsvd.h"
static bob::extension::FunctionDoc s_histogram_intersection = bob::extension::FunctionDoc( static bob::extension::FunctionDoc s_histogram_intersection = bob::extension::FunctionDoc(
"histogram_intersection", "histogram_intersection",
...@@ -355,6 +356,26 @@ static bob::extension::FunctionDoc s_scatters_nocheck = bob::extension::Function ...@@ -355,6 +356,26 @@ static bob::extension::FunctionDoc s_scatters_nocheck = bob::extension::Function
; ;
static bob::extension::FunctionDoc s_gsvd = bob::extension::FunctionDoc(
"gsvd",
"Computes the Generalized SVD",
"Computes the Generalized SVD. The output of this function is similar with the one found in Matlab\n"
"[U,V,X,C,S] = gsvd(A,B) returns unitary matrices :math:`U` and :math:`V`, the square matrix :math:`X` (which is :math:`[0 R] Q^{T}`), and nonnegative diagonal matrices :math:`C` and :math:`S` such that:\n\n"
".. math:: C^{T}C + S^{T}S = I \n"
".. math:: A = (XC^{T}U^{T})^{T}\n"
".. math:: B = (XS^{T}V^{T})^{T}\n"
)
.add_prototype("A, B", "")
.add_parameter("A", "[array_like (float, 2D)]", "Must be :math:`m \\times n`")
.add_parameter("B", "[array_like (float, 2D)]", "Must be :math:`p \\times n`")
.add_return("U", "[array_like (float, 2D)]", "Contains a :math:`m \\times m` orthogonal matrix.")
.add_return("V", "[array_like (float, 2D)]", "Contains a :math:`n \\times n` orthogonal matrix.")
.add_return("X", "[array_like (float, 2D)]", "Contains a :math:`p \\times q` matrix, where :math:`p=\\min(m+n,p)` and :math:`X=[0, R] Q^{T}` (Check LAPACK documentation).")
.add_return("C", "[array_like (float, 2D)]", "Contains a :math:`p \\times q` matrix.")
.add_return("S", "[array_like (float, 2D)]", "Contains a :math:`n \\times q` matrix.")
;
static PyMethodDef module_methods[] = { static PyMethodDef module_methods[] = {
{ {
s_histogram_intersection.name(), s_histogram_intersection.name(),
...@@ -470,6 +491,13 @@ static PyMethodDef module_methods[] = { ...@@ -470,6 +491,13 @@ static PyMethodDef module_methods[] = {
METH_VARARGS|METH_KEYWORDS, METH_VARARGS|METH_KEYWORDS,
s_scatters_nocheck.doc() s_scatters_nocheck.doc()
}, },
{
s_gsvd.name(),
(PyCFunction)py_gsvd,
METH_VARARGS|METH_KEYWORDS,
s_gsvd.doc()
},
{0} /* Sentinel */ {0} /* Sentinel */
}; };
......
#!/usr/bin/env python
# Tiago de Freitas Pereira <tiago.pereira@idiap.ch>
# Sun Jan 15 19:12:43 CET 2017
#
"""
Tests GSVD
Basically these tests test the GSVD relation.
Given 2 matrices A and B GSVD(A,B) = [U,V,X,C,S] where,
A= (X * C.T * U^T)^T and
B= (X * S.T * V^T)^T and
C**2 + S**2 = 1
"""
import bob.math
import numpy
import nose.tools
numpy.random.seed(10)
def gsvd_relations(A,B):
[U,V,X,C,S] = bob.math.gsvd(A, B)
# Cheking the relation C**2 + S**2 = 1
I = numpy.eye(A.shape[1])
I_check = numpy.dot(C.T, C) + numpy.dot(S.T, S)
nose.tools.eq_( (abs(I-I_check) < 1e-10).all(), True )
# Cheking the relation A= (X * C.T * U^T)^T
A_check = numpy.dot(numpy.dot(X,C.T),U.T).T
nose.tools.eq_( (abs(A-A_check) < 1e-10).all(), True )
# Cheking the relation B= (X * S.T * V^T)^T
B_check = numpy.dot(numpy.dot(X,S.T),V.T).T
nose.tools.eq_( (abs(B-B_check) < 1e-10).all(), True )
del U,V,X,C,S
def test_first_case():
"""
Testing the first scenario:
M-K-L >= 0 (check http://www.netlib.org/lapack/explore-html/d1/d7e/group__double_g_esing_ga4a187519e5c71da3b3f67c85e9baf0f2.html#ga4a187519e5c71da3b3f67c85e9baf0f2)
"""
A = numpy.random.rand(10,10)
B = numpy.random.rand(790,10)
gsvd_relations(A, B)
def test_second_case():
"""
Testing the second scenario:
M-K-L < 0 (check http://www.netlib.org/lapack/explore-html/d1/d7e/group__double_g_esing_ga4a187519e5c71da3b3f67c85e9baf0f2.html#ga4a187519e5c71da3b3f67c85e9baf0f2)
"""
A = numpy.random.rand(4,5)
B = numpy.random.rand(11,5)
gsvd_relations(A, B)
...@@ -15,7 +15,19 @@ Summary ...@@ -15,7 +15,19 @@ Summary
bob.math.LPInteriorPoint bob.math.LPInteriorPoint
bob.math.LPInteriorPointShortstep bob.math.LPInteriorPointShortstep
bob.math.LPInteriorPointLongstep bob.math.LPInteriorPointLongstep
bob.math.chi_square
bob.math.gsvd
bob.math.histogram_intersection
bob.math.kullback_leibler
bob.math.linsolve
bob.math.linsolve_cg_sympos
bob.math.linsolve_sympos
bob.math.norminv
bob.math.pavx
bob.math.pavxWidth
bob.math.pavxWidthHeight
bob.math.scatter
bob.math.scatters
Details Details
....... .......
......
...@@ -179,6 +179,7 @@ setup( ...@@ -179,6 +179,7 @@ setup(
"bob/math/cpp/pavx.cpp", "bob/math/cpp/pavx.cpp",
"bob/math/cpp/pinv.cpp", "bob/math/cpp/pinv.cpp",
"bob/math/cpp/svd.cpp", "bob/math/cpp/svd.cpp",
"bob/math/cpp/gsvd.cpp",
"bob/math/cpp/sqrtm.cpp", "bob/math/cpp/sqrtm.cpp",
], ],
version = version, version = version,
...@@ -195,6 +196,7 @@ setup( ...@@ -195,6 +196,7 @@ setup(
"bob/math/linsolve.cpp", "bob/math/linsolve.cpp",
"bob/math/pavx.cpp", "bob/math/pavx.cpp",
"bob/math/norminv.cpp", "bob/math/norminv.cpp",
"bob/math/gsvd.cpp",
"bob/math/scatter.cpp", "bob/math/scatter.cpp",
"bob/math/lp_interior_point.cpp", "bob/math/lp_interior_point.cpp",
"bob/math/main.cpp", "bob/math/main.cpp",
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment