mirror of https://github.com/AxioDL/metaforce.git
500 lines
18 KiB
C++
500 lines
18 KiB
C++
/* -*- c++ -*- (enables emacs c++ mode) */
|
|
/*===========================================================================
|
|
|
|
Copyright (C) 2009-2017 Yves Renard
|
|
|
|
This file is a part of GetFEM++
|
|
|
|
GetFEM++ is free software; you can redistribute it and/or modify it
|
|
under the terms of the GNU Lesser General Public License as published
|
|
by the Free Software Foundation; either version 3 of the License, or
|
|
(at your option) any later version along with the GCC Runtime Library
|
|
Exception either version 3.1 or (at your option) any later version.
|
|
This program is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
|
License and GCC Runtime Library Exception for more details.
|
|
You should have received a copy of the GNU Lesser General Public License
|
|
along with this program; if not, write to the Free Software Foundation,
|
|
Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
As a special exception, you may use this file as it is a part of a free
|
|
software library without restriction. Specifically, if other files
|
|
instantiate templates or use macros or inline functions from this file,
|
|
or you compile this file and link it with other files to produce an
|
|
executable, this file does not by itself cause the resulting executable
|
|
to be covered by the GNU Lesser General Public License. This exception
|
|
does not however invalidate any other reasons why the executable file
|
|
might be covered by the GNU Lesser General Public License.
|
|
|
|
===========================================================================*/
|
|
|
|
/**@file gmm_range_basis.h
|
|
@author Yves Renard <Yves.Renard@insa-lyon.fr>
|
|
@date March 10, 2009.
|
|
@brief Extract a basis of the range of a (large sparse) matrix from the
|
|
columns of this matrix.
|
|
*/
|
|
#ifndef GMM_RANGE_BASIS_H
|
|
#define GMM_RANGE_BASIS_H
|
|
#include "gmm_dense_qr.h"
|
|
#include "gmm_dense_lu.h"
|
|
|
|
#include "gmm_kernel.h"
|
|
#include "gmm_iter.h"
|
|
#include <set>
|
|
#include <list>
|
|
|
|
|
|
namespace gmm {
|
|
|
|
|
|
template <typename T, typename VECT, typename MAT1>
|
|
void tridiag_qr_algorithm
|
|
(std::vector<typename number_traits<T>::magnitude_type> diag,
|
|
std::vector<T> sdiag, const VECT &eigval_, const MAT1 &eigvect_,
|
|
bool compvect, tol_type_for_qr tol = default_tol_for_qr) {
|
|
VECT &eigval = const_cast<VECT &>(eigval_);
|
|
MAT1 &eigvect = const_cast<MAT1 &>(eigvect_);
|
|
typedef typename number_traits<T>::magnitude_type R;
|
|
|
|
if (compvect) gmm::copy(identity_matrix(), eigvect);
|
|
|
|
size_type n = diag.size(), q = 0, p, ite = 0;
|
|
if (n == 0) return;
|
|
if (n == 1) { eigval[0] = gmm::real(diag[0]); return; }
|
|
|
|
symmetric_qr_stop_criterion(diag, sdiag, p, q, tol);
|
|
|
|
while (q < n) {
|
|
sub_interval SUBI(p, n-p-q), SUBJ(0, mat_ncols(eigvect)), SUBK(p, n-p-q);
|
|
if (!compvect) SUBK = sub_interval(0,0);
|
|
|
|
symmetric_Wilkinson_qr_step(sub_vector(diag, SUBI),
|
|
sub_vector(sdiag, SUBI),
|
|
sub_matrix(eigvect, SUBJ, SUBK), compvect);
|
|
|
|
symmetric_qr_stop_criterion(diag, sdiag, p, q, tol*R(3));
|
|
++ite;
|
|
GMM_ASSERT1(ite < n*100, "QR algorithm failed.");
|
|
}
|
|
|
|
gmm::copy(diag, eigval);
|
|
}
|
|
|
|
// Range basis with a restarted Lanczos method
|
|
template <typename Mat>
|
|
void range_basis_eff_Lanczos(const Mat &BB, std::set<size_type> &columns,
|
|
double EPS=1E-12) {
|
|
typedef std::set<size_type> TAB;
|
|
typedef typename linalg_traits<Mat>::value_type T;
|
|
typedef typename number_traits<T>::magnitude_type R;
|
|
|
|
size_type nc_r = columns.size(), k;
|
|
col_matrix< rsvector<T> > B(mat_nrows(BB), mat_ncols(BB));
|
|
|
|
k = 0;
|
|
for (TAB::iterator it = columns.begin(); it!=columns.end(); ++it, ++k){
|
|
gmm::copy(scaled(mat_col(BB, *it), T(1)/vect_norm2(mat_col(BB, *it))),
|
|
mat_col(B, *it));
|
|
}
|
|
std::vector<T> w(mat_nrows(B));
|
|
size_type restart = 120;
|
|
std::vector<T> sdiag(restart);
|
|
std::vector<R> eigval(restart), diag(restart);
|
|
dense_matrix<T> eigvect(restart, restart);
|
|
|
|
R rho = R(-1), rho2;
|
|
while (nc_r) {
|
|
|
|
std::vector<T> v(nc_r), v0(nc_r), wl(nc_r);
|
|
dense_matrix<T> lv(nc_r, restart);
|
|
|
|
if (rho < R(0)) { // Estimate of the spectral radius of B^* B
|
|
gmm::fill_random(v);
|
|
for (size_type i = 0; i < 100; ++i) {
|
|
gmm::scale(v, T(1)/vect_norm2(v));
|
|
gmm::copy(v, v0);
|
|
k = 0; gmm::clear(w);
|
|
for (TAB::iterator it=columns.begin(); it!=columns.end(); ++it, ++k)
|
|
add(scaled(mat_col(B, *it), v[k]), w);
|
|
k = 0;
|
|
for (TAB::iterator it=columns.begin(); it!=columns.end(); ++it, ++k)
|
|
v[k] = vect_hp(w, mat_col(B, *it));
|
|
rho = gmm::abs(vect_hp(v, v0) / vect_hp(v0, v0));
|
|
}
|
|
rho *= R(2);
|
|
}
|
|
|
|
// Computing vectors of the null space of de B^* B with restarted Lanczos
|
|
rho2 = 0;
|
|
gmm::fill_random(v);
|
|
size_type iter = 0;
|
|
for(;;++iter) {
|
|
R rho_old = rho2;
|
|
R beta = R(0), alpha;
|
|
gmm::scale(v, T(1)/vect_norm2(v));
|
|
size_type eff_restart = restart;
|
|
if (sdiag.size() != restart) {
|
|
sdiag.resize(restart); eigval.resize(restart); diag.resize(restart); gmm::resize(eigvect, restart, restart);
|
|
gmm::resize(lv, nc_r, restart);
|
|
}
|
|
|
|
for (size_type i = 0; i < restart; ++i) { // Lanczos iterations
|
|
gmm::copy(v, mat_col(lv, i));
|
|
gmm::clear(w);
|
|
k = 0;
|
|
for (TAB::iterator it=columns.begin(); it!=columns.end(); ++it, ++k)
|
|
add(scaled(mat_col(B, *it), v[k]), w);
|
|
|
|
k = 0;
|
|
for (TAB::iterator it=columns.begin(); it!=columns.end(); ++it, ++k)
|
|
wl[k] = v[k]*rho - vect_hp(w, mat_col(B, *it)) - beta*v0[k];
|
|
alpha = gmm::real(vect_hp(wl, v));
|
|
diag[i] = alpha;
|
|
gmm::add(gmm::scaled(v, -alpha), wl);
|
|
sdiag[i] = beta = vect_norm2(wl);
|
|
gmm::copy(v, v0);
|
|
if (beta < EPS) { eff_restart = i+1; break; }
|
|
gmm::copy(gmm::scaled(wl, T(1) / beta), v);
|
|
}
|
|
if (eff_restart != restart) {
|
|
sdiag.resize(eff_restart); eigval.resize(eff_restart); diag.resize(eff_restart);
|
|
gmm::resize(eigvect, eff_restart, eff_restart); gmm::resize(lv, nc_r, eff_restart);
|
|
}
|
|
tridiag_qr_algorithm(diag, sdiag, eigval, eigvect, true);
|
|
|
|
size_type num = size_type(-1);
|
|
rho2 = R(0);
|
|
for (size_type j = 0; j < eff_restart; ++j)
|
|
{ R nvp=gmm::abs(eigval[j]); if (nvp > rho2) { rho2=nvp; num=j; }}
|
|
|
|
GMM_ASSERT1(num != size_type(-1), "Internal error");
|
|
|
|
gmm::mult(lv, mat_col(eigvect, num), v);
|
|
|
|
if (gmm::abs(rho2-rho_old) < rho_old*R(EPS)) break;
|
|
// if (gmm::abs(rho-rho2) < rho*R(gmm::sqrt(EPS))) break;
|
|
if (gmm::abs(rho-rho2) < rho*R(EPS)*R(100)) break;
|
|
}
|
|
|
|
if (gmm::abs(rho-rho2) < rho*R(EPS*10.)) {
|
|
size_type j_max = size_type(-1), j = 0;
|
|
R val_max = R(0);
|
|
for (TAB::iterator it=columns.begin(); it!=columns.end(); ++it, ++j)
|
|
if (gmm::abs(v[j]) > val_max)
|
|
{ val_max = gmm::abs(v[j]); j_max = *it; }
|
|
columns.erase(j_max); nc_r = columns.size();
|
|
}
|
|
else break;
|
|
}
|
|
}
|
|
|
|
// Range basis with LU decomposition. Not stable from a numerical viewpoint.
|
|
// Complex version not verified
|
|
template <typename Mat>
|
|
void range_basis_eff_lu(const Mat &B, std::set<size_type> &columns,
|
|
std::vector<bool> &c_ortho, double EPS) {
|
|
|
|
typedef std::set<size_type> TAB;
|
|
typedef typename linalg_traits<Mat>::value_type T;
|
|
typedef typename number_traits<T>::magnitude_type R;
|
|
|
|
size_type nc_r = 0, nc_o = 0, nc = mat_ncols(B), nr = mat_nrows(B), i, j;
|
|
|
|
for (TAB::iterator it=columns.begin(); it!=columns.end(); ++it)
|
|
if (!(c_ortho[*it])) ++nc_r; else nc_o++;
|
|
|
|
if (nc_r > 0) {
|
|
|
|
gmm::row_matrix< gmm::rsvector<T> > Hr(nc, nc_r), Ho(nc, nc_o);
|
|
gmm::row_matrix< gmm::rsvector<T> > BBr(nr, nc_r), BBo(nr, nc_o);
|
|
|
|
i = j = 0;
|
|
for (TAB::iterator it=columns.begin(); it!=columns.end(); ++it)
|
|
if (!(c_ortho[*it]))
|
|
{ Hr(*it, i) = T(1)/ vect_norminf(mat_col(B, *it)); ++i; }
|
|
else
|
|
{ Ho(*it, j) = T(1)/ vect_norm2(mat_col(B, *it)); ++j; }
|
|
|
|
gmm::mult(B, Hr, BBr);
|
|
gmm::mult(B, Ho, BBo);
|
|
gmm::dense_matrix<T> M(nc_r, nc_r), BBB(nc_r, nc_o), MM(nc_r, nc_r);
|
|
gmm::mult(gmm::conjugated(BBr), BBr, M);
|
|
gmm::mult(gmm::conjugated(BBr), BBo, BBB);
|
|
gmm::mult(BBB, gmm::conjugated(BBB), MM);
|
|
gmm::add(gmm::scaled(MM, T(-1)), M);
|
|
|
|
std::vector<int> ipvt(nc_r);
|
|
gmm::lu_factor(M, ipvt);
|
|
|
|
R emax = R(0);
|
|
for (i = 0; i < nc_r; ++i) emax = std::max(emax, gmm::abs(M(i,i)));
|
|
|
|
i = 0;
|
|
std::set<size_type> c = columns;
|
|
for (TAB::iterator it = c.begin(); it != c.end(); ++it)
|
|
if (!(c_ortho[*it])) {
|
|
if (gmm::abs(M(i,i)) <= R(EPS)*emax) columns.erase(*it);
|
|
++i;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Range basis with Gram-Schmidt orthogonalization (sparse version)
|
|
// The sparse version is better when the sparsity is high and less efficient
|
|
// than the dense version for high degree elements (P3, P4 ...)
|
|
// Complex version not verified
|
|
template <typename Mat>
|
|
void range_basis_eff_Gram_Schmidt_sparse(const Mat &BB,
|
|
std::set<size_type> &columns,
|
|
std::vector<bool> &c_ortho,
|
|
double EPS) {
|
|
|
|
typedef std::set<size_type> TAB;
|
|
typedef typename linalg_traits<Mat>::value_type T;
|
|
typedef typename number_traits<T>::magnitude_type R;
|
|
|
|
size_type nc = mat_ncols(BB), nr = mat_nrows(BB);
|
|
std::set<size_type> c = columns, rc = columns;
|
|
|
|
gmm::col_matrix< rsvector<T> > B(nr, nc);
|
|
for (std::set<size_type>::iterator it = columns.begin();
|
|
it != columns.end(); ++it) {
|
|
gmm::copy(mat_col(BB, *it), mat_col(B, *it));
|
|
gmm::scale(mat_col(B, *it), T(1)/vect_norm2(mat_col(B, *it)));
|
|
}
|
|
|
|
for (std::set<size_type>::iterator it = c.begin(); it != c.end(); ++it)
|
|
if (c_ortho[*it]) {
|
|
for (std::set<size_type>::iterator it2 = rc.begin();
|
|
it2 != rc.end(); ++it2)
|
|
if (!(c_ortho[*it2])) {
|
|
T r = -vect_hp(mat_col(B, *it2), mat_col(B, *it));
|
|
if (r != T(0)) add(scaled(mat_col(B, *it), r), mat_col(B, *it2));
|
|
}
|
|
rc.erase(*it);
|
|
}
|
|
|
|
while (rc.size()) {
|
|
R nmax = R(0); size_type cmax = size_type(-1);
|
|
for (std::set<size_type>::iterator it=rc.begin(); it != rc.end();) {
|
|
TAB::iterator itnext = it; ++itnext;
|
|
R n = vect_norm2(mat_col(B, *it));
|
|
if (nmax < n) { nmax = n; cmax = *it; }
|
|
if (n < R(EPS)) { columns.erase(*it); rc.erase(*it); }
|
|
it = itnext;
|
|
}
|
|
|
|
if (nmax < R(EPS)) break;
|
|
|
|
gmm::scale(mat_col(B, cmax), T(1)/vect_norm2(mat_col(B, cmax)));
|
|
rc.erase(cmax);
|
|
for (std::set<size_type>::iterator it=rc.begin(); it!=rc.end(); ++it) {
|
|
T r = -vect_hp(mat_col(B, *it), mat_col(B, cmax));
|
|
if (r != T(0)) add(scaled(mat_col(B, cmax), r), mat_col(B, *it));
|
|
}
|
|
}
|
|
for (std::set<size_type>::iterator it=rc.begin(); it!=rc.end(); ++it)
|
|
columns.erase(*it);
|
|
}
|
|
|
|
|
|
// Range basis with Gram-Schmidt orthogonalization (dense version)
|
|
template <typename Mat>
|
|
void range_basis_eff_Gram_Schmidt_dense(const Mat &B,
|
|
std::set<size_type> &columns,
|
|
std::vector<bool> &c_ortho,
|
|
double EPS) {
|
|
|
|
typedef std::set<size_type> TAB;
|
|
typedef typename linalg_traits<Mat>::value_type T;
|
|
typedef typename number_traits<T>::magnitude_type R;
|
|
|
|
size_type nc_r = columns.size(), nc = mat_ncols(B), nr = mat_nrows(B), i;
|
|
std::set<size_type> rc;
|
|
|
|
row_matrix< gmm::rsvector<T> > H(nc, nc_r), BB(nr, nc_r);
|
|
std::vector<T> v(nc_r);
|
|
std::vector<size_type> ind(nc_r);
|
|
|
|
i = 0;
|
|
for (TAB::iterator it = columns.begin(); it != columns.end(); ++it, ++i)
|
|
H(*it, i) = T(1) / vect_norm2(mat_col(B, *it));
|
|
|
|
mult(B, H, BB);
|
|
dense_matrix<T> M(nc_r, nc_r);
|
|
mult(gmm::conjugated(BB), BB, M);
|
|
|
|
i = 0;
|
|
for (TAB::iterator it = columns.begin(); it != columns.end(); ++it, ++i)
|
|
if (c_ortho[*it]) {
|
|
gmm::copy(mat_row(M, i), v);
|
|
rank_one_update(M, scaled(v, T(-1)), v);
|
|
M(i, i) = T(1);
|
|
}
|
|
else { rc.insert(i); ind[i] = *it; }
|
|
|
|
while (rc.size() > 0) {
|
|
|
|
// Next pivot
|
|
R nmax = R(0); size_type imax = size_type(-1);
|
|
for (TAB::iterator it = rc.begin(); it != rc.end();) {
|
|
TAB::iterator itnext = it; ++itnext;
|
|
R a = gmm::abs(M(*it, *it));
|
|
if (a > nmax) { nmax = a; imax = *it; }
|
|
if (a < R(EPS)) { columns.erase(ind[*it]); rc.erase(*it); }
|
|
it = itnext;
|
|
}
|
|
|
|
if (nmax < R(EPS)) break;
|
|
|
|
// Normalization
|
|
gmm::scale(mat_row(M, imax), T(1) / sqrt(nmax));
|
|
gmm::scale(mat_col(M, imax), T(1) / sqrt(nmax));
|
|
|
|
// orthogonalization
|
|
copy(mat_row(M, imax), v);
|
|
rank_one_update(M, scaled(v, T(-1)), v);
|
|
M(imax, imax) = T(1);
|
|
|
|
rc.erase(imax);
|
|
}
|
|
for (std::set<size_type>::iterator it=rc.begin(); it!=rc.end(); ++it)
|
|
columns.erase(ind[*it]);
|
|
}
|
|
|
|
template <typename L> size_type nnz_eps(const L& l, double eps) {
|
|
typename linalg_traits<L>::const_iterator it = vect_const_begin(l),
|
|
ite = vect_const_end(l);
|
|
size_type res(0);
|
|
for (; it != ite; ++it) if (gmm::abs(*it) >= eps) ++res;
|
|
return res;
|
|
}
|
|
|
|
template <typename L>
|
|
bool reserve__rb(const L& l, std::vector<bool> &b, double eps) {
|
|
typename linalg_traits<L>::const_iterator it = vect_const_begin(l),
|
|
ite = vect_const_end(l);
|
|
bool ok = true;
|
|
for (; it != ite; ++it)
|
|
if (gmm::abs(*it) >= eps && b[it.index()]) ok = false;
|
|
if (ok) {
|
|
for (it = vect_const_begin(l); it != ite; ++it)
|
|
if (gmm::abs(*it) >= eps) b[it.index()] = true;
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
template <typename Mat>
|
|
void range_basis(const Mat &B, std::set<size_type> &columns,
|
|
double EPS, col_major, bool skip_init=false) {
|
|
|
|
typedef typename linalg_traits<Mat>::value_type T;
|
|
typedef typename number_traits<T>::magnitude_type R;
|
|
|
|
size_type nc = mat_ncols(B), nr = mat_nrows(B);
|
|
|
|
std::vector<R> norms(nc);
|
|
std::vector<bool> c_ortho(nc), booked(nr);
|
|
std::vector< std::set<size_type> > nnzs(mat_nrows(B));
|
|
|
|
if (!skip_init) {
|
|
|
|
R norm_max = R(0);
|
|
for (size_type i = 0; i < nc; ++i) {
|
|
norms[i] = vect_norminf(mat_col(B, i));
|
|
norm_max = std::max(norm_max, norms[i]);
|
|
}
|
|
|
|
columns.clear();
|
|
for (size_type i = 0; i < nc; ++i)
|
|
if (norms[i] > norm_max*R(EPS)) {
|
|
columns.insert(i);
|
|
nnzs[nnz_eps(mat_col(B, i), R(EPS) * norms[i])].insert(i);
|
|
}
|
|
|
|
for (size_type i = 1; i < nr; ++i)
|
|
for (std::set<size_type>::iterator it = nnzs[i].begin();
|
|
it != nnzs[i].end(); ++it)
|
|
if (reserve__rb(mat_col(B, *it), booked, R(EPS) * norms[*it]))
|
|
c_ortho[*it] = true;
|
|
}
|
|
|
|
size_type sizesm[7] = {125, 200, 350, 550, 800, 1100, 1500}, actsize;
|
|
for (int k = 0; k < 7; ++k) {
|
|
size_type nc_r = columns.size();
|
|
std::set<size_type> c1, cres;
|
|
actsize = sizesm[k];
|
|
for (std::set<size_type>::iterator it = columns.begin();
|
|
it != columns.end(); ++it) {
|
|
c1.insert(*it);
|
|
if (c1.size() >= actsize) {
|
|
range_basis_eff_Gram_Schmidt_dense(B, c1, c_ortho, EPS);
|
|
for (std::set<size_type>::iterator it2=c1.begin(); it2 != c1.end();
|
|
++it2) cres.insert(*it2);
|
|
c1.clear();
|
|
}
|
|
}
|
|
if (c1.size() > 1)
|
|
range_basis_eff_Gram_Schmidt_dense(B, c1, c_ortho, EPS);
|
|
for (std::set<size_type>::iterator it = c1.begin(); it != c1.end(); ++it)
|
|
cres.insert(*it);
|
|
columns = cres;
|
|
if (nc_r <= actsize) return;
|
|
if (columns.size() == nc_r) break;
|
|
if (sizesm[k] >= 350 && columns.size() > (nc_r*19)/20) break;
|
|
}
|
|
if (columns.size() > std::max(size_type(10), actsize))
|
|
range_basis_eff_Lanczos(B, columns, EPS);
|
|
else
|
|
range_basis_eff_Gram_Schmidt_dense(B, columns, c_ortho, EPS);
|
|
}
|
|
|
|
|
|
template <typename Mat>
|
|
void range_basis(const Mat &B, std::set<size_type> &columns,
|
|
double EPS, row_major) {
|
|
typedef typename linalg_traits<Mat>::value_type T;
|
|
gmm::col_matrix< rsvector<T> > BB(mat_nrows(B), mat_ncols(B));
|
|
GMM_WARNING3("A copy of a row matrix is done into a column matrix "
|
|
"for range basis algorithm.");
|
|
gmm::copy(B, BB);
|
|
range_basis(BB, columns, EPS);
|
|
}
|
|
|
|
/** Range Basis :
|
|
Extract a basis of the range of a (large sparse) matrix selecting some
|
|
column vectors of this matrix. This is in particular useful to select
|
|
an independent set of linear constraints.
|
|
|
|
The algorithm is optimized for two cases :
|
|
- when the (non trivial) kernel is small. An iterativ algorithm
|
|
based on Lanczos method is applied
|
|
- when the (non trivial) kernel is large and most of the dependencies
|
|
can be detected locally. A block Gram-Schmidt is applied first then
|
|
a restarted Lanczos method when the remaining kernel is greatly
|
|
smaller.
|
|
The restarted Lanczos method could be improved or replaced by a block
|
|
Lanczos method, a block Wiedelann method (in order to be parallelized for
|
|
instance) or simply could compute more than one vector of the null
|
|
space at each iteration.
|
|
The LU decomposition has been tested for local elimination but gives bad
|
|
results : the algorithm is unstable and do not permit to give the right
|
|
number of vector at the end of the process. Moreover, the number of final
|
|
vectors depends greatly on the number of vectors in a block of the local
|
|
analysis.
|
|
*/
|
|
template <typename Mat>
|
|
void range_basis(const Mat &B, std::set<size_type> &columns,
|
|
double EPS=1E-12) {
|
|
range_basis(B, columns, EPS,
|
|
typename principal_orientation_type
|
|
<typename linalg_traits<Mat>::sub_orientation>::potype());
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|