Files
opennurbs/opennurbs_matrix.cpp
2024-10-09 06:42:31 -07:00

1997 lines
45 KiB
C++

//
// Copyright (c) 1993-2022 Robert McNeel & Associates. All rights reserved.
// OpenNURBS, Rhinoceros, and Rhino3D are registered trademarks of Robert
// McNeel & Associates.
//
// THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY.
// ALL IMPLIED WARRANTIES OF FITNESS FOR ANY PARTICULAR PURPOSE AND OF
// MERCHANTABILITY ARE HEREBY DISCLAIMED.
//
// For complete openNURBS copyright information see <http://www.opennurbs.org>.
//
////////////////////////////////////////////////////////////////
#include "opennurbs.h"
#if !defined(ON_COMPILING_OPENNURBS)
// This check is included in all opennurbs source .c and .cpp files to insure
// ON_COMPILING_OPENNURBS is defined when opennurbs source is compiled.
// When opennurbs source is being compiled, ON_COMPILING_OPENNURBS is defined
// and the opennurbs .h files alter what is declared and how it is declared.
#error ON_COMPILING_OPENNURBS must be defined when compiling opennurbs
#endif
// 8 July 2003 Dale Lear
// changed ON_Matrix to use multiple allocations
// for coefficient memory in large matrices.
// ON_Matrix.m_cmem points to a struct DBLBLK
struct DBLBLK
{
int count;
double* a;
struct DBLBLK *next;
};
double const * const * ON_Matrix::ThisM() const
{
// When the "expert" constructor Create(row_count,col_count,user_memory,...)
// is used, m_rowmem[] is empty and m = user_memory;
// When any other constructor is used, m_rowmem[] is the 0-based
// row memory.
return (m_row_count == m_rowmem.Count()) ? m_rowmem.Array() : m;
}
double * * ON_Matrix::ThisM()
{
// When the "expert" constructor Create(row_count,col_count,user_memory,...)
// is used, m_rowmem[] is empty and m = user_memory;
// When any other constructor is used, m_rowmem[] is the 0-based
// row memory.
return (m_row_count == m_rowmem.Count()) ? m_rowmem.Array() : m;
}
double* ON_Matrix::operator[](int i)
{
return m[i];
}
const double* ON_Matrix::operator[](int i) const
{
return m[i];
}
ON_Matrix::ON_Matrix()
: m(0)
, m_row_count(0)
, m_col_count(0)
, m_Mmem(0)
, m_row_offset(0)
, m_col_offset(0)
, m_cmem(0)
{
}
ON_Matrix::ON_Matrix( int row_size, int col_size )
: m(0)
, m_row_count(0)
, m_col_count(0)
, m_Mmem(0)
, m_row_offset(0)
, m_col_offset(0)
, m_cmem(0)
{
Create(row_size,col_size);
}
ON_Matrix::ON_Matrix( int row0, int row1, int col0, int col1 )
: m(0)
, m_row_count(0)
, m_col_count(0)
, m_Mmem(0)
, m_row_offset(0)
, m_col_offset(0)
, m_cmem(0)
{
Create(row0,row1,col0,col1);
}
ON_Matrix::ON_Matrix( const ON_Xform& x )
: m(0)
, m_row_count(0)
, m_col_count(0)
, m_Mmem(0)
, m_row_offset(0)
, m_col_offset(0)
, m_cmem(0)
{
*this = x;
}
ON_Matrix::ON_Matrix( const ON_Matrix& src )
: m(0)
, m_row_count(0)
, m_col_count(0)
, m_Mmem(0)
, m_row_offset(0)
, m_col_offset(0)
, m_cmem(0)
{
*this = src;
}
ON_Matrix::ON_Matrix(
int row_count,
int col_count,
double** M,
bool bDestructorFreeM
)
: m(0)
, m_row_count(0)
, m_col_count(0)
, m_Mmem(0)
, m_row_offset(0)
, m_col_offset(0)
, m_cmem(0)
{
Create(row_count,col_count,M,bDestructorFreeM);
}
ON_Matrix::~ON_Matrix()
{
if ( 0 != m_Mmem )
{
onfree(m_Mmem);
m_Mmem = 0;
}
m_row_offset = 0;
m_col_offset = 0;
struct DBLBLK* p = (struct DBLBLK*)m_cmem;
m_cmem = 0;
while(0 != p)
{
struct DBLBLK* next = p->next;
onfree(p);
p = next;
}
}
#if defined(ON_HAS_RVALUEREF)
ON_Matrix::ON_Matrix(ON_Matrix&& src) ON_NOEXCEPT
: m(src.m)
, m_row_count(src.m_row_count)
, m_col_count(src.m_col_count)
, m_rowmem(std::move(src.m_rowmem))
, m_Mmem(src.m_Mmem)
, m_row_offset(src.m_row_offset)
, m_cmem(src.m_cmem)
{
src.m = nullptr;
src.m_row_count = 0;
src.m_col_count = 0;
// src.m_rowmem - std::move insures src.m_rowmem is zeroed
src.m_Mmem = nullptr;
src.m_row_offset = 0;
src.m_cmem = nullptr;
}
ON_Matrix& ON_Matrix::operator=(ON_Matrix&& src)
{
if (this != &src)
{
m = src.m;
m_row_count = src.m_row_count;
m_col_count = src.m_col_count;
m_rowmem = std::move(src.m_rowmem);
m_Mmem = src.m_Mmem;
m_row_offset = src.m_row_offset;
m_cmem = src.m_cmem;
src.m = nullptr;
src.m_row_count = 0;
src.m_col_count = 0;
src.m_Mmem = nullptr;
src.m_row_offset = 0;
src.m_cmem = nullptr;
}
return *this;
}
#endif
double** ON_Matrix::Allocate(
unsigned int row_count,
unsigned int col_count
)
{
if (row_count < 1 || row_count >= ON_UNSET_UINT_INDEX / 2)
return nullptr;
if (col_count < 1 || col_count >= ON_UNSET_UINT_INDEX / 2)
return nullptr;
double** M;
size_t sizeof_element = sizeof(M[0][0]);
size_t sizeof_ptr = sizeof(M[0]);
size_t sz0 = row_count*sizeof_ptr;
if (0 != sz0 % sizeof_element)
sz0 += sizeof_ptr;
size_t sz1 = row_count*col_count*sizeof_element;
if (0 != sz1 % sizeof_ptr)
sz1 += sizeof_ptr;
M = new (std::nothrow) double*[(sz0 + sz1) / sizeof_ptr];
if (nullptr == M)
return nullptr;
double* row = (double*)(((char*)M) + sz0);
for (unsigned int i = 0; i < row_count; i++)
{
M[i] = row;
row += col_count;
}
return M;
}
void ON_Matrix::Deallocate(double** M)
{
if (nullptr != M)
{
delete[] M;
}
}
int ON_Matrix::RowCount() const
{
return m_row_count;
}
int ON_Matrix::ColCount() const
{
return m_col_count;
}
int ON_Matrix::MinCount() const
{
return (m_row_count <= m_col_count) ? m_row_count : m_col_count;
}
int ON_Matrix::MaxCount() const
{
return (m_row_count >= m_col_count) ? m_row_count : m_col_count;
}
unsigned int ON_Matrix::UnsignedRowCount() const
{
return (m_row_count>0) ? (unsigned int)m_row_count : 0;
}
unsigned int ON_Matrix::UnsignedColCount() const
{
return (m_col_count>0) ? (unsigned int)m_col_count : 0;
}
unsigned int ON_Matrix::UnsignedMinCount() const
{
const unsigned int rc[2] = { UnsignedRowCount(), UnsignedColCount() };
return (rc[0] < rc[1]) ? rc[0] : rc[1];
}
unsigned int ON_Matrix::UnsignedMaxCount() const
{
const unsigned int rc[2] = { UnsignedRowCount(), UnsignedColCount() };
return (rc[0] > rc[1]) ? rc[0] : rc[1];
}
bool ON_Matrix::Create( int row_count, int col_count)
{
bool b = false;
Destroy();
if ( row_count > 0 && col_count > 0 )
{
m_rowmem.Reserve(row_count);
if ( 0 != m_rowmem.Array() )
{
m_rowmem.SetCount(row_count);
// In general, allocate coefficient memory in chunks
// of <= max_dblblk_size bytes. The value of max_dblblk_size
// is tuned to maximize speed on calculations involving
// large matrices. If all of the coefficients will fit
// into a chunk of memory <= 1.1*max_dblblk_size, then
// a single chunk is allocated.
// In limited testing, these two values appeared to work ok.
// The latter was a hair faster in solving large row reduction
// problems (for reasons I do not understand).
//const int max_dblblk_size = 1024*1024*8;
const int max_dblblk_size = 512*1024;
int rows_per_block = max_dblblk_size/(col_count*sizeof(double));
if ( rows_per_block > row_count )
rows_per_block = row_count;
else if ( rows_per_block < 1 )
rows_per_block = 1;
else if ( rows_per_block < row_count && 11*rows_per_block >= 10*row_count )
rows_per_block = row_count;
int j, i = row_count;
m = m_rowmem.Array();
double** row = m;
for ( i = row_count; i > 0; i -= rows_per_block )
{
if ( i < rows_per_block )
rows_per_block = i;
int dblblk_count = rows_per_block*col_count;
// TODO: Make this malloc size a static on the DBLBLK class, this is too ugly
struct DBLBLK* p = (struct DBLBLK*)onmalloc(sizeof(*p) + dblblk_count*sizeof(p->a[0]));
p->a = (double*)(p+1);
p->count = dblblk_count;
p->next = (struct DBLBLK*)m_cmem;
m_cmem = p;
*row = p->a;
j = rows_per_block-1;
while(j--)
{
row[1] = row[0] + col_count;
row++;
}
row++;
}
m_row_count = row_count;
m_col_count = col_count;
b = true;
}
}
return b;
}
bool ON_Matrix::Create( // E.g., Create(1,5,1,7) creates a 5x7 sized matrix that with
// "top" row = m[1][1],...,m[1][7] and "bottom" row
// = m[5][1],...,m[5][7]. The result of Create(0,m,0,n) is
// identical to the result of Create(m+1,n+1).
int ri0, // first valid row index
int ri1, // last valid row index
int ci0, // first valid column index
int ci1 // last valid column index
)
{
bool b = false;
if ( ri1 > ri0 && ci1 > ci0 )
{
// juggle m[] pointers so that m[ri0+i][ci0+j] = m_row[i][j];
b = Create( ri1-ri0, ci1-ci0 );
if (b)
{
m_row_offset = ri0; // this is the only line of code where m_row_offset should be set to a non-zero value
m_col_offset = ci0; // this is the only line of code where m_col_offset should be set to a non-zero value
if ( ci0 != 0 )
{
int i;
for ( i = 0; i < m_row_count; i++ ) {
m[i] -= ci0;
}
}
if ( ri0 != 0 )
m -= ri0;
}
}
return b;
}
bool ON_Matrix::Create(
int row_count,
int col_count,
double** M,
bool bDestructorFreeM
)
{
Destroy();
if ( row_count < 1 || col_count < 1 || 0 == M )
return false;
m = M;
m_row_count = row_count;
m_col_count = col_count;
if ( bDestructorFreeM )
m_Mmem = M;
return true;
}
void ON_Matrix::Destroy()
{
m = 0;
m_row_count = 0;
m_col_count = 0;
m_rowmem.SetCount(0);
if ( 0 != m_Mmem )
{
// pointer passed to Create( row_count, col_count, M, bDestructorFreeM )
// when bDestructorFreeM = true.
onfree(m_Mmem);
m_Mmem = 0;
}
m_row_offset = 0;
m_col_offset = 0;
struct DBLBLK* cmem = (struct DBLBLK*)m_cmem;
m_cmem = 0;
while( 0 != cmem )
{
struct DBLBLK* next_cmem = cmem->next;
onfree(cmem);
cmem = next_cmem;
}
}
void ON_Matrix::EmergencyDestroy()
{
// call if memory pool used matrix by becomes invalid
m = 0;
m_row_count = 0;
m_col_count = 0;
m_rowmem.EmergencyDestroy();
m_Mmem = 0;
m_row_offset = 0;
m_col_offset = 0;
m_cmem = 0;
}
ON_Matrix& ON_Matrix::operator=(const ON_Matrix& src)
{
if ( this != &src )
{
if ( src.m_row_count != m_row_count || src.m_col_count != m_col_count || 0 == m )
{
Destroy();
Create( src.RowCount(), src.ColCount() );
}
if (src.m_row_count == m_row_count && src.m_col_count == m_col_count && 0 != m )
{
int i;
// src rows may be permuted - copy row by row
double** m_dest = ThisM();
double const*const* m_src = src.ThisM();
const int sizeof_row = m_col_count*sizeof(m_dest[0][0]);
for ( i = 0; i < m_row_count; i++ )
{
memcpy( m_dest[i], m_src[i], sizeof_row );
}
m_row_offset = src.m_row_offset;
m_col_offset = src.m_col_offset;
}
}
return *this;
}
ON_Matrix& ON_Matrix::operator=(const ON_Xform& src)
{
m_row_offset = 0;
m_col_offset = 0;
if ( 4 != m_row_count || 4 != m_col_count || 0 == m )
{
Destroy();
Create( 4, 4 );
}
if ( 4 == m_row_count && 4 == m_col_count && 0 != m )
{
double** this_m = ThisM();
if ( this_m )
{
memcpy( this_m[0], &src.m_xform[0][0], 4*sizeof(this_m[0][0]) );
memcpy( this_m[1], &src.m_xform[1][0], 4*sizeof(this_m[0][0]) );
memcpy( this_m[2], &src.m_xform[2][0], 4*sizeof(this_m[0][0]) );
memcpy( this_m[3], &src.m_xform[3][0], 4*sizeof(this_m[0][0]) );
}
}
return *this;
}
bool ON_Matrix::Transpose()
{
bool rc = false;
int i, j;
double t;
const int row_count = RowCount();
const int col_count = ColCount();
if ( row_count > 0 && col_count > 0 )
{
double** this_m = ThisM();
if ( row_count == col_count )
{
rc = true;
for ( i = 0; i < row_count; i++ ) for ( j = i+1; j < row_count; j++ )
{
t = this_m[i][j]; this_m[i][j] = this_m[j][i]; this_m[j][i] = t;
}
}
else if ( this_m == m_rowmem.Array() )
{
ON_Matrix A(*this);
rc = Create(col_count,row_count)
&& m_row_count == A.ColCount()
&& m_col_count == A.RowCount();
if (rc)
{
double const*const* Am = A.ThisM();
this_m = ThisM(); // Create allocates new memory
for ( i = 0; i < row_count; i++ ) for ( j = 0; j < col_count; j++ )
{
this_m[j][i] = Am[i][j];
}
m_row_offset = A.m_col_offset;
m_col_offset = A.m_row_offset;
}
else
{
// attempt to put values back
*this = A;
}
}
}
return rc;
}
bool ON_Matrix::SwapRows( int row0, int row1 )
{
bool b = false;
double** this_m = ThisM();
row0 -= m_row_offset;
row1 -= m_row_offset;
if ( this_m && 0 <= row0 && row0 < m_row_count && 0 <= row1 && row1 < m_row_count )
{
if ( row0 != row1 )
{
double* tmp = this_m[row0]; this_m[row0] = this_m[row1]; this_m[row1] = tmp;
}
b = true;
}
return b;
}
bool ON_Matrix::SwapCols( int col0, int col1 )
{
bool b = false;
int i;
double t;
double** this_m = ThisM();
col0 -= m_col_offset;
col1 -= m_col_offset;
if ( this_m && 0 <= col0 && col0 < m_col_count && 0 <= col1 && col1 < m_col_count )
{
if ( col0 != col1 )
{
for ( i = 0; i < m_row_count; i++ )
{
t = this_m[i][col0]; this_m[i][col0] = this_m[i][col1]; this_m[i][col1] = t;
}
}
b = true;
}
return b;
}
void ON_Matrix::RowScale( int dest_row, double s )
{
double** this_m = ThisM();
dest_row -= m_row_offset;
ON_ArrayScale( m_col_count, s, this_m[dest_row], this_m[dest_row] );
}
void ON_Matrix::RowOp( int dest_row, double s, int src_row )
{
double** this_m = ThisM();
dest_row -= m_row_offset;
src_row -= m_row_offset;
ON_Array_aA_plus_B( m_col_count, s, this_m[src_row], this_m[dest_row], this_m[dest_row] );
}
void ON_Matrix::ColScale( int dest_col, double s )
{
int i;
double** this_m = ThisM();
dest_col -= m_col_offset;
for ( i = 0; i < m_row_count; i++ )
{
this_m[i][dest_col] *= s;
}
}
void ON_Matrix::ColOp( int dest_col, double s, int src_col )
{
int i;
double** this_m = ThisM();
dest_col -= m_col_offset;
src_col -= m_col_offset;
for ( i = 0; i < m_row_count; i++ )
{
this_m[i][dest_col] += s*this_m[i][src_col];
}
}
int
ON_Matrix::RowReduce(
double zero_tolerance,
double& determinant,
double& pivot
)
{
double x, piv, det;
int i, k, ix, rank;
double** this_m = ThisM();
piv = det = 1.0;
rank = 0;
const int n = m_row_count <= m_col_count ? m_row_count : m_col_count;
for ( k = 0; k < n; k++ ) {
ix = k;
x = fabs(this_m[ix][k]);
for ( i = k+1; i < m_row_count; i++ ) {
if ( fabs(this_m[i][k]) > x ) {
ix = i;
x = fabs(this_m[ix][k]);
}
}
if ( x < piv || k == 0 ) {
piv = x;
}
if ( x <= zero_tolerance ) {
det = 0.0;
break;
}
rank++;
if ( ix != k )
{
// swap rows
SwapRows( ix, k );
det = -det;
}
// scale row k of matrix and B
det *= this_m[k][k];
x = 1.0/this_m[k][k];
this_m[k][k] = 1.0;
ON_ArrayScale( m_col_count - 1 - k, x, &this_m[k][k+1], &this_m[k][k+1] );
// zero column k for rows below this_m[k][k]
for ( i = k+1; i < m_row_count; i++ ) {
x = -this_m[i][k];
this_m[i][k] = 0.0;
if ( fabs(x) > zero_tolerance ) {
ON_Array_aA_plus_B( m_col_count - 1 - k, x, &this_m[k][k+1], &this_m[i][k+1], &this_m[i][k+1] );
}
}
}
pivot = piv;
determinant = det;
return rank;
}
int
ON_Matrix::RowReduce(
double zero_tolerance,
double* B,
double* pivot
)
{
double t;
double x, piv;
int i, k, ix, rank;
double** this_m = ThisM();
piv = 0.0;
rank = 0;
const int n = m_row_count <= m_col_count ? m_row_count : m_col_count;
for ( k = 0; k < n; k++ ) {
ix = k;
x = fabs(this_m[ix][k]);
for ( i = k+1; i < m_row_count; i++ ) {
if ( fabs(this_m[i][k]) > x ) {
ix = i;
x = fabs(this_m[ix][k]);
}
}
if ( x < piv || k == 0 ) {
piv = x;
}
if ( x <= zero_tolerance )
break;
rank++;
if ( ix != k )
{
// swap rows of matrix and B
SwapRows( ix, k );
t = B[ix]; B[ix] = B[k]; B[k] = t;
}
// scale row k of matrix and B
x = 1.0/this_m[k][k];
this_m[k][k] = 1.0;
ON_ArrayScale( m_col_count - 1 - k, x, &this_m[k][k+1], &this_m[k][k+1] );
B[k] *= x;
// zero column k for rows below this_m[k][k]
for ( i = k+1; i < m_row_count; i++ ) {
x = -this_m[i][k];
this_m[i][k] = 0.0;
if ( fabs(x) > zero_tolerance ) {
ON_Array_aA_plus_B( m_col_count - 1 - k, x, &this_m[k][k+1], &this_m[i][k+1], &this_m[i][k+1] );
B[i] += x*B[k];
}
}
}
if ( pivot )
*pivot = piv;
return rank;
}
int
ON_Matrix::RowReduce(
double zero_tolerance,
ON_3dPoint* B,
double* pivot
)
{
ON_3dPoint t;
double x, piv;
int i, k, ix, rank;
double** this_m = ThisM();
piv = 0.0;
rank = 0;
const int n = m_row_count <= m_col_count ? m_row_count : m_col_count;
for ( k = 0; k < n; k++ ) {
//onfree( onmalloc( 1)); // 8-06-03 lw for cancel thread responsiveness
onmalloc( 0); // 9-4-03 lw changed to 0
ix = k;
x = fabs(this_m[ix][k]);
for ( i = k+1; i < m_row_count; i++ ) {
if ( fabs(this_m[i][k]) > x ) {
ix = i;
x = fabs(this_m[ix][k]);
}
}
if ( x < piv || k == 0 ) {
piv = x;
}
if ( x <= zero_tolerance )
break;
rank++;
if ( ix != k )
{
// swap rows of matrix and B
SwapRows( ix, k );
t = B[ix]; B[ix] = B[k]; B[k] = t;
}
// scale row k of matrix and B
x = 1.0/this_m[k][k];
this_m[k][k] = 1.0;
ON_ArrayScale( m_col_count - 1 - k, x, &this_m[k][k+1], &this_m[k][k+1] );
B[k] *= x;
// zero column k for rows below this_m[k][k]
for ( i = k+1; i < m_row_count; i++ ) {
x = -this_m[i][k];
this_m[i][k] = 0.0;
if ( fabs(x) > zero_tolerance ) {
ON_Array_aA_plus_B( m_col_count - 1 - k, x, &this_m[k][k+1], &this_m[i][k+1], &this_m[i][k+1] );
B[i] += x*B[k];
}
}
}
if ( pivot )
*pivot = piv;
return rank;
}
int
ON_Matrix::RowReduce(
double zero_tolerance,
int pt_dim, int pt_stride, double* pt,
double* pivot
)
{
const int sizeof_pt = pt_dim*sizeof(pt[0]);
double* tmp_pt = (double*)onmalloc(pt_dim*sizeof(tmp_pt[0]));
double *ptA, *ptB;
double x, piv;
int i, k, ix, rank, pti;
double** this_m = ThisM();
piv = 0.0;
rank = 0;
const int n = m_row_count <= m_col_count ? m_row_count : m_col_count;
for ( k = 0; k < n; k++ ) {
// onfree( onmalloc( 1)); // 8-06-03 lw for cancel thread responsiveness
onmalloc( 0); // 9-4-03 lw changed to 0
ix = k;
x = fabs(this_m[ix][k]);
for ( i = k+1; i < m_row_count; i++ ) {
if ( fabs(this_m[i][k]) > x ) {
ix = i;
x = fabs(this_m[ix][k]);
}
}
if ( x < piv || k == 0 ) {
piv = x;
}
if ( x <= zero_tolerance )
break;
rank++;
// swap rows of matrix and B
if ( ix != k ) {
SwapRows( ix, k );
ptA = pt + (ix*pt_stride);
ptB = pt + (k*pt_stride);
memcpy( tmp_pt, ptA, sizeof_pt );
memcpy( ptA, ptB, sizeof_pt );
memcpy( ptB, tmp_pt, sizeof_pt );
}
// scale row k of matrix and B
x = 1.0/this_m[k][k];
if ( x != 1.0 ) {
this_m[k][k] = 1.0;
ON_ArrayScale( m_col_count - 1 - k, x, &this_m[k][k+1], &this_m[k][k+1] );
ptA = pt + (k*pt_stride);
for ( pti = 0; pti < pt_dim; pti++ )
ptA[pti] *= x;
}
// zero column k for rows below this_m[k][k]
ptB = pt + (k*pt_stride);
for ( i = k+1; i < m_row_count; i++ ) {
x = -this_m[i][k];
this_m[i][k] = 0.0;
if ( fabs(x) > zero_tolerance ) {
ON_Array_aA_plus_B( m_col_count - 1 - k, x, &this_m[k][k+1], &this_m[i][k+1], &this_m[i][k+1] );
ptA = pt + (i*pt_stride);
for ( pti = 0; pti < pt_dim; pti++ ) {
ptA[pti] += x*ptB[pti];
}
}
}
}
if ( pivot )
*pivot = piv;
onfree(tmp_pt);
return rank;
}
bool
ON_Matrix::BackSolve(
double zero_tolerance,
int Bsize,
const double* B,
double* X
) const
{
int i;
if ( m_col_count > m_row_count )
return false; // under determined
if ( Bsize < m_col_count || Bsize > m_row_count )
return false; // under determined
for ( i = m_col_count; i < Bsize; i++ ) {
if ( fabs(B[i]) > zero_tolerance )
return false; // over determined
}
// backsolve
double const*const* this_m = ThisM();
const int n = m_col_count-1;
if ( X != B )
X[n] = B[n];
for ( i = n-1; i >= 0; i-- ) {
X[i] = B[i] - ON_ArrayDotProduct( n-i, &this_m[i][i+1], &X[i+1] );
}
return true;
}
bool
ON_Matrix::BackSolve(
double zero_tolerance,
int Bsize,
const ON_3dPoint* B,
ON_3dPoint* X
) const
{
int i, j;
if ( m_col_count > m_row_count )
return false; // under determined
if ( Bsize < m_col_count || Bsize > m_row_count )
return false; // under determined
for ( i = m_col_count; i < Bsize; i++ ) {
if ( B[i].MaximumCoordinate() > zero_tolerance )
return false; // over determined
}
// backsolve
double const*const* this_m = ThisM();
if ( X != B )
{
X[m_col_count-1] = B[m_col_count-1];
for ( i = m_col_count-2; i >= 0; i-- ) {
X[i] = B[i];
for ( j = i+1; j < m_col_count; j++ ) {
X[i] -= this_m[i][j]*X[j];
}
}
}
else {
for ( i = m_col_count-2; i >= 0; i-- ) {
for ( j = i+1; j < m_col_count; j++ ) {
X[i] -= this_m[i][j]*X[j];
}
}
}
return true;
}
bool
ON_Matrix::BackSolve(
double zero_tolerance,
int pt_dim,
int Bsize,
int Bpt_stride,
const double* Bpt,
int Xpt_stride,
double* Xpt
) const
{
const int sizeof_pt = pt_dim*sizeof(double);
double mij;
int i, j, k;
const double* Bi;
double* Xi;
double* Xj;
if ( m_col_count > m_row_count )
return false; // under determined
if ( Bsize < m_col_count || Bsize > m_row_count )
return false; // under determined
for ( i = m_col_count; i < Bsize; i++ )
{
Bi = Bpt + i*Bpt_stride;
for( j = 0; j < pt_dim; j++ )
{
if ( fabs(Bi[j]) > zero_tolerance )
return false; // over determined
}
}
// backsolve
double const*const* this_m = ThisM();
if ( Xpt != Bpt )
{
Xi = Xpt + (m_col_count-1)*Xpt_stride;
Bi = Bpt + (m_col_count-1)*Bpt_stride;
memcpy(Xi,Bi,sizeof_pt);
for ( i = m_col_count-2; i >= 0; i-- ) {
Xi = Xpt + i*Xpt_stride;
Bi = Bpt + i*Bpt_stride;
memcpy(Xi,Bi,sizeof_pt);
for ( j = i+1; j < m_col_count; j++ ) {
Xj = Xpt + j*Xpt_stride;
mij = this_m[i][j];
for ( k = 0; k < pt_dim; k++ )
Xi[k] -= mij*Xj[k];
}
}
}
else {
for ( i = m_col_count-2; i >= 0; i-- ) {
Xi = Xpt + i*Xpt_stride;
for ( j = i+1; j < m_col_count; j++ ) {
Xj = Xpt + j*Xpt_stride;
mij = this_m[i][j];
for ( k = 0; k < pt_dim; k++ )
Xi[k] -= mij*Xj[k];
}
}
}
return true;
}
void ON_Matrix::Zero()
{
struct DBLBLK* cmem = (struct DBLBLK*)m_cmem;
while ( 0 != cmem )
{
if ( 0 != cmem->a && cmem->count > 0 )
{
memset( cmem->a, 0, cmem->count*sizeof(cmem->a[0]) );
}
onmalloc(0); // allow canceling
cmem = cmem->next;
}
//m_a.Zero();
}
void ON_Matrix::SetDiagonal( double d)
{
const int n = MinCount();
int i;
Zero();
double** this_m = ThisM();
for ( i = 0; i < n; i++ )
{
this_m[i][i] = d;
}
}
void ON_Matrix::SetDiagonal( const double* d )
{
Zero();
if (d)
{
double** this_m = ThisM();
const int n = MinCount();
int i;
for ( i = 0; i < n; i++ )
{
this_m[i][i] = *d++;
}
}
}
void ON_Matrix::SetDiagonal( int count, const double* d )
{
Create(count,count);
Zero();
SetDiagonal(d);
}
void ON_Matrix::SetDiagonal( const ON_SimpleArray<double>& a )
{
SetDiagonal( a.Count(), a.Array() );
}
bool ON_Matrix::IsValid() const
{
if ( m_row_count < 1 || m_col_count < 1 )
return false;
if ( 0 == m )
return false;
return true;
}
int ON_Matrix::IsSquare() const
{
return ( m_row_count > 0 && m_col_count == m_row_count ) ? m_row_count : 0;
}
bool ON_Matrix::IsRowOrthoganal() const
{
double d0, d1, d;
int i0, i1, j;
double const*const* this_m = ThisM();
bool rc = ( m_row_count <= m_col_count && m_row_count > 0 );
for ( i0 = 0; i0 < m_row_count && rc; i0++ ) for ( i1 = i0+1; i1 < m_row_count && rc; i1++ ) {
d0 = d1 = d = 0.0;
for ( j = 0; j < m_col_count; j++ ) {
d0 += fabs(this_m[i0][j]);
d1 += fabs(this_m[i0][j]);
d += this_m[i0][j]*this_m[i1][j];
}
if ( d0 <= ON_EPSILON || d1 <= ON_EPSILON || fabs(d) >= d0*d1* ON_SQRT_EPSILON )
rc = false;
}
return rc;
}
bool ON_Matrix::IsRowOrthoNormal() const
{
double d;
int i, j;
bool rc = IsRowOrthoganal();
if ( rc ) {
double const*const* this_m = ThisM();
for ( i = 0; i < m_row_count; i++ ) {
d = 0.0;
for ( j = 0; j < m_col_count; j++ ) {
d += this_m[i][j]*this_m[i][j];
}
if ( fabs(1.0-d) >= ON_SQRT_EPSILON )
rc = false;
}
}
return rc;
}
bool ON_Matrix::IsColOrthoganal() const
{
double d0, d1, d;
int i, j0, j1;
bool rc = ( m_col_count <= m_row_count && m_col_count > 0 );
double const*const* this_m = ThisM();
for ( j0 = 0; j0 < m_col_count && rc; j0++ ) for ( j1 = j0+1; j1 < m_col_count && rc; j1++ ) {
d0 = d1 = d = 0.0;
for ( i = 0; i < m_row_count; i++ ) {
d0 += fabs(this_m[i][j0]);
d1 += fabs(this_m[i][j0]);
d += this_m[i][j0]*this_m[i][j1];
}
if ( d0 <= ON_EPSILON || d1 <= ON_EPSILON || fabs(d) > ON_SQRT_EPSILON )
rc = false;
}
return rc;
}
bool ON_Matrix::IsColOrthoNormal() const
{
double d;
int i, j;
bool rc = IsColOrthoganal();
double const*const* this_m = ThisM();
if ( rc ) {
for ( j = 0; j < m_col_count; j++ ) {
d = 0.0;
for ( i = 0; i < m_row_count; i++ ) {
d += this_m[i][j]*this_m[i][j];
}
if ( fabs(1.0-d) >= ON_SQRT_EPSILON )
rc = false;
}
}
return rc;
}
bool ON_Matrix::Invert( double zero_tolerance )
{
ON_Workspace ws;
int i, j, k, ix, jx, rank;
double x;
const int n = MinCount();
if ( n < 1 )
return false;
ON_Matrix I(m_col_count, m_row_count);
int* col = ws.GetIntMemory(n);
I.SetDiagonal(1.0);
rank = 0;
double** this_m = ThisM();
for ( k = 0; k < n; k++ ) {
// find largest value in sub matrix
ix = jx = k;
x = fabs(this_m[ix][jx]);
for ( i = k; i < n; i++ ) {
for ( j = k; j < n; j++ ) {
if ( fabs(this_m[i][j]) > x ) {
ix = i;
jx = j;
x = fabs(this_m[ix][jx]);
}
}
}
SwapRows( k, ix );
I.SwapRows( k, ix );
SwapCols( k, jx );
col[k] = jx;
if ( x <= zero_tolerance ) {
break;
}
x = 1.0/this_m[k][k];
this_m[k][k] = 1.0;
ON_ArrayScale( m_col_count-k-1, x, &this_m[k][k+1], &this_m[k][k+1] );
I.RowScale( k, x );
// zero this_m[!=k][k]'s
for ( i = 0; i < n; i++ ) {
if ( i != k ) {
x = -this_m[i][k];
this_m[i][k] = 0.0;
if ( fabs(x) > zero_tolerance ) {
ON_Array_aA_plus_B( m_col_count-k-1, x, &this_m[k][k+1], &this_m[i][k+1], &this_m[i][k+1] );
I.RowOp( i, x, k );
}
}
}
}
// take care of column swaps
for ( i = k-1; i >= 0; i-- ) {
if ( i != col[i] )
I.SwapRows(i,col[i]);
}
*this = I;
return (k == n) ? true : false;
}
bool ON_Matrix::Multiply( const ON_Matrix& a, const ON_Matrix& b )
{
int i, j, k, mult_count;
double x;
if (a.ColCount() != b.RowCount() )
return false;
if ( a.RowCount() < 1 || a.ColCount() < 1 || b.ColCount() < 1 )
return false;
if ( this == &a ) {
ON_Matrix tmp(a);
return Multiply(tmp,b);
}
if ( this == &b ) {
ON_Matrix tmp(b);
return Multiply(a,tmp);
}
Create( a.RowCount(), b.ColCount() );
mult_count = a.ColCount();
double const*const* am = a.ThisM();
double const*const* bm = b.ThisM();
double** this_m = ThisM();
for ( i = 0; i < m_row_count; i++ ) for ( j = 0; j < m_col_count; j++ ) {
x = 0.0;
for (k = 0; k < mult_count; k++ ) {
x += am[i][k] * bm[k][j];
}
this_m[i][j] = x;
}
return true;
}
bool ON_Matrix::Add( const ON_Matrix& a, const ON_Matrix& b )
{
int i, j;
if (a.ColCount() != b.ColCount() )
return false;
if (a.RowCount() != b.RowCount() )
return false;
if ( a.RowCount() < 1 || a.ColCount() < 1 )
return false;
if ( this != &a && this != &b ) {
Create( a.RowCount(), b.ColCount() );
}
double const*const* am = a.ThisM();
double const*const* bm = b.ThisM();
double** this_m = ThisM();
for ( i = 0; i < m_row_count; i++ ) for ( j = 0; j < m_col_count; j++ ) {
this_m[i][j] = am[i][j] + bm[i][j];
}
return true;
}
bool ON_Matrix::Scale( double s )
{
bool rc = false;
if ( m_row_count > 0 && m_col_count > 0 )
{
struct DBLBLK* cmem = (struct DBLBLK*)m_cmem;
int i;
double* p;
while ( 0 != cmem )
{
if ( 0 != cmem->a && cmem->count > 0 )
{
p = cmem->a;
i = cmem->count;
while(i--)
*p++ *= s;
}
cmem = cmem->next;
}
rc = true;
}
/*
int i = m_a.Capacity();
if ( m_row_count > 0 && m_col_count > 0 && m_row_count*m_col_count <= i ) {
double* p = m_a.Array();
while ( i-- )
*p++ *= s;
rc = true;
}
*/
return rc;
}
int ON_RowReduce( int row_count,
int col_count,
double zero_pivot,
double** A,
double** B,
double pivots[2]
)
{
// returned A is identity, B = inverse of input A
const int M = row_count;
const int N = col_count;
const size_t sizeof_row = N*sizeof(A[0][0]);
int i, j, ii;
double a, p, p0, p1;
const double* ptr0;
double* ptr1;
if ( pivots )
{
pivots[0] = 0.0;
pivots[1] = 0.0;
}
if ( zero_pivot <= 0.0 || !ON_IsValid(zero_pivot) )
zero_pivot = 0.0;
for ( i = 0; i < M; i++ )
{
memset(B[i],0,sizeof_row);
if ( i < N )
B[i][i] = 1.0;
}
p0 = p1 = A[0][0];
for ( i = 0; i < M; i++ )
{
p = fabs(a = A[i][i]);
if ( p < p0 ) p0 = p; else if (p > p1) p1 = p;
if ( 1.0 != a )
{
if ( p <= zero_pivot || !ON_IsValid(a) )
{
break;
}
a = 1.0/a;
//A[i][i] = 1.0; // no need to do this
// The "ptr" voodoo is faster but does the same thing as
//
//for ( j = i+1; j < N; j++ )
// A[i][j] *= a;
//
j = i+1;
ptr1 = A[i] + j;
j = N - j;
while(j--) *ptr1++ *= a;
// The "ptr" voodoo is faster but does the same thing as
//
//for ( j = 0; j <= i; j++ )
// B[i][j] *= a;
//
ptr1 = B[i];
j = i+1;
while(j--) *ptr1++ *= a;
}
for ( ii = i+1; ii < M; ii++ )
{
a = A[ii][i];
if ( 0.0 == a )
continue;
a = -a;
//A[ii][i] = 0.0; // no need to do this
// The "ptr" voodoo is faster but does the same thing as
//
//for( j = i+1; j < N; j++ )
// A[ii][j] += a*A[i][j];
//
j = i+1;
ptr0 = A[i] + j;
ptr1 = A[ii] + j;
j = N - j;
while(j--) *ptr1++ += a* *ptr0++;
for( j = 0; j <= i; j++ )
B[ii][j] += a*B[i][j];
}
}
if ( pivots )
{
pivots[0] = p0;
pivots[1] = p1;
}
if ( i < M )
{
return i;
}
// A is now upper triangular with all 1s on diagonal
// (That is, if the lines that say "no need to do this" are used.)
// B is lower triangular with a nonzero diagonal
for ( i = M-1; i >= 0; i-- )
{
for ( ii = i-1; ii >= 0; ii-- )
{
a = A[ii][i];
if ( 0.0 == a )
continue;
a = -a;
//A[ii][i] = 0.0; // no need to do this
// The "ptr" voodoo is faster but does the same thing as
//
//for( j = 0; j < N; j++ )
// B[ii][j] += a*B[i][j];
//
ptr0 = B[i];
ptr1 = B[ii];
j = N;
while(j--) *ptr1++ += a* *ptr0++;
}
}
// At this point, A is trash.
// If the input A was really nice (positive definite....)
// the B = inverse of the input A. If A was not nice,
// B is also trash.
return M;
}
unsigned int ON_RowReduce(
unsigned int row_count,
unsigned int col_count,
double zero_pivot_tolerance,
const double*const* constA,
bool bInitializeB,
bool bInitializeColumnPermutation,
double** A,
double** B,
unsigned int* column_permutation,
double pivots[3]
)
{
double pivots_buffer[3];
if (nullptr == pivots)
pivots = pivots_buffer;
pivots[0] = pivots[1] = -1.0;
pivots[2] = 0.0;
if (row_count < 1 || ON_UNSET_UINT_INDEX == row_count)
return ON_UNSET_UINT_INDEX;
if (col_count < 1 || ON_UNSET_UINT_INDEX == col_count)
return ON_UNSET_UINT_INDEX;
if (nullptr == B)
return ON_UNSET_UINT_INDEX;
unsigned int i, j;
double* a;
double* b;
if (bInitializeB)
{
for (i = 0; i < row_count; i++)
{
b = B[i];
for (j = 0; j < row_count; j++)
b[j] = 0.0;
b[i] = 1.0;
}
}
if (bInitializeColumnPermutation && nullptr != column_permutation)
{
for (j = 0; j < row_count; j++)
column_permutation[j] = j;
}
if (!(zero_pivot_tolerance >= 0.0))
zero_pivot_tolerance = 0.0;
std::unique_ptr< ON_Matrix > uA;
if (nullptr == A)
{
if (nullptr == constA)
return ON_UNSET_UINT_INDEX;
uA = std::unique_ptr< ON_Matrix >(new ON_Matrix(row_count, col_count));
A = uA.get()->m;
}
if (nullptr != constA && nullptr != A)
{
for (i = 0; i < row_count; i++)
{
const double* c = constA[i];
a = A[i];
for (j = 0; j < row_count; j++)
a[j] = c[j];
}
}
double* r;
double* s;
double x, xx;
unsigned int ii, jj;
ii = jj = 0;
xx = fabs(A[ii][jj]);
unsigned int rank;
for (rank = 0; rank < row_count; rank++)
{
if (rank >= col_count)
return rank;
xx = -1.0;
ii = jj = rank;
for (i = rank; i < row_count; i++)
{
r = A[i];
for (j = rank; j < col_count; j++)
{
x = fabs(r[j]);
if (x > xx)
{
ii = i;
jj = j;
xx = x;
}
}
}
if (!(xx >= 0.0))
return ON_UNSET_UINT_INDEX;
if (pivots[0] < 0.0)
pivots[0] = pivots[1] = xx;
if (xx <= zero_pivot_tolerance)
{
pivots[2] = xx;
return rank;
}
if (xx > pivots[0])
pivots[0] = xx;
else if (xx < pivots[1])
pivots[1] = xx;
a = A[ii];
b = B[ii];
if (ii > rank)
{
// swap rows M[ii] and M[rank] so maximum coefficient is in M[rank][jj]
A[ii] = A[rank];
A[rank] = a;
B[ii] = B[rank];
B[rank] = b;
if (nullptr != column_permutation)
{
j = column_permutation[ii];
column_permutation[ii] = column_permutation[rank];
column_permutation[rank] = j;
}
}
// swap columns M[jj] and M[rank] to maximum coefficient is in M[rank][rank]
xx = -a[jj];
a[jj] = a[rank];
//a[rank] = -xx; // DEBUG
for (i = rank + 1; i < row_count; i++)
{
// swap columns M[jj] and M[rank]
r = A[i];
x = r[jj] / xx;
//double dd = r[jj]; // DEBUG
r[jj] = r[rank];
//r[rank] = dd; // DEBUG
// Use row operation
// M[i] = M[i] - (row[i]/M[rank][rank])*M[rank]
// to M[i][rank]
if (0.0 != x)
{
s = B[i];
for (j = 0; j <= rank; j++)
{
s[j] += x*b[j];
//r[j] += x*a[j]; // DEBUG
}
for (/*empty init*/; j < col_count; j++)
{
s[j] += x*b[j];
r[j] += x*a[j];
}
}
}
}
return rank;
}
double ON_EigenvectorPrecision(
const unsigned int N,
const double*const* M,
bool bTransposeM,
double lambda,
const double* eigenvector
)
{
double delta = 0.0;
double len = 0.0;
if ( bTransposeM )
{
for (unsigned int i = 0; i < N; i++)
{
len += (eigenvector[i] * eigenvector[i]);
const double* e = eigenvector;
double Mei = 0.0;
for (unsigned int j = 0; j < N; j++)
Mei += M[j][i]*(*e++);
double d = fabs(Mei - lambda*eigenvector[i]);
if (d > delta)
delta = d;
}
}
else
{
const double* e0 = eigenvector + N;
for (unsigned int i = 0; i < N; i++)
{
len += (eigenvector[i] * eigenvector[i]);
const double* Mi = M[i];
const double* e = eigenvector;
double Mei = 0.0;
while (e < e0)
Mei += (*Mi++)*(*e++);
double d = fabs(Mei - lambda*eigenvector[i]);
if (d > delta)
delta = d;
}
}
if (delta > 0.0 && len > 0.0)
delta /= sqrt(len);
return delta;
}
double ON_MatrixSolutionPrecision(
const unsigned int N,
const double*const* M,
bool bTransposeM,
double lambda,
const double* X,
const double* B
)
{
double delta = 0.0;
if (bTransposeM)
{
for (unsigned int i = 0; i < N; i++)
{
const double* e = X;
double Mei = -(lambda*e[i]);
for ( unsigned int j = 0; j < N; j++ )
Mei += M[j][i]*(*e++);
double d = fabs(Mei - B[i]);
if (d > delta)
delta = d;
}
}
else
{
const double* e0 = X + N;
for (unsigned int i = 0; i < N; i++)
{
const double* Mi = M[i];
const double* e = X;
double Mei = -(lambda*e[i]);
while (e < e0)
Mei += (*Mi++)*(*e++);
double d = fabs(Mei - B[i]);
if (d > delta)
delta = d;
}
}
return delta;
}
static int CompareDoubleIncreasing(const void* a, const void* b)
{
double x = *((const double*)a);
double y = *((const double*)b);
if (x < y)
return -1;
if (x > y)
return 1;
return 0;
}
unsigned int ON_GetEigenvectors(
const unsigned int N,
const double*const* M,
bool bTransposeM,
double lambda,
unsigned int lambda_multiplicity,
const double* termination_tolerances,
double** eigenvectors,
double* eigenprecision,
double* eigenpivots
)
{
if (N < 1 || ON_UNSET_UINT_INDEX == N)
return ON_UNSET_UINT_INDEX;
if (1 == N)
{
eigenvectors[0][0] = 1.0;
if (nullptr != eigenpivots)
{
eigenpivots[0] = M[0][0];
eigenpivots[1] = M[0][0];
eigenpivots[2] = 0.0;
}
if (nullptr != eigenprecision)
{
eigenprecision[3] = fabs(lambda - M[0][0]);
}
return (1 == lambda_multiplicity) ? 1 : 0;
}
double tols[3] = { 1.0e-12, 1.0e-3, 1.0e4 };
if (nullptr != termination_tolerances)
{
if (termination_tolerances[0] > 0.0)
tols[0] = termination_tolerances[0];
if (termination_tolerances[1] > 0.0)
tols[1] = termination_tolerances[0];
if (termination_tolerances[2] > 0.0)
tols[2] = termination_tolerances[2];
}
ON_Matrix Abuffer(N, N);
double** A = Abuffer.m;
ON_Matrix Bbuffer(N, N);
double** B = Bbuffer.m;
unsigned int i, j;
double pivots[3] = { 0.0, 0.0, 0.0 };
double zero_pivot_tolerance0 = 0.0;
double zero_pivot_tolerance = 0.0;
const bool bUnknownLambdaMultiplicity = (lambda_multiplicity < 1 || lambda_multiplicity > N);
if (bUnknownLambdaMultiplicity)
lambda_multiplicity = 1;
unsigned int rank = N + 1;
bool bLastTry = false;
for(unsigned int rank0 = N+1; rank0 > 0; /*empty increment*/)
{
if (bTransposeM)
{
// A = (M - lambda*I);
for (i = 0; i < N; i++)
{
double* r = A[i];
for (j = 0; j < N; j++)
{
r[j] = M[i][j];
}
r[i] -= lambda;
}
}
else
{
// A = Transpose(M - lambda*I);
for (i = 0; i < N; i++)
{
double* r = A[i];
for (j = 0; j < N; j++)
{
r[j] = M[j][i];
}
r[i] -= lambda;
}
}
zero_pivot_tolerance = pivots[1];
if (!(zero_pivot_tolerance >= 0.0))
{
// This should never happen. The test is here because
// it has no performance penalty and will detect bugs
// if this code is incorrectly modified.
ON_ERROR("invalid zero_pivot_tolerance value");
break;
}
pivots[0] = 0.0; // maximum pivot
pivots[1] = 0.0; // minimum "nonzero" pivot
pivots[2] = 0.0; // maximum "zero" pivot
rank = ON_RowReduce(N, N, zero_pivot_tolerance, nullptr, true, false, A, B, nullptr, pivots);
if (bLastTry)
{
// For an explanation, see the code below where bLastTry is set to true.
break;
}
if (rank >= rank0 || rank > N)
{
// failure
break;
}
if (rank == N - lambda_multiplicity)
{
// We found exactly lambda_multiplicity eigenvectors.
// If the value of zero_pivot_tolerance was appropriate
// and double precision arithmetic is appropriate for
// the matrix, then we are returning reliable
// eigenvectors.
break;
}
if (rank < N - lambda_multiplicity)
{
// Theoretically, the dimension of the eigenspace cannot be larger than
// the multiplicity of the eigenvalue.
//
// Either we have an unstable case (perhaps there is another
// eigenvalue that is close to lambda), or the value of
// zero_pivot_tolerance was too big, or double precision
// arithmetic is not good enough for the matrix.
if (rank0 > 0 && rank0 < N && zero_pivot_tolerance > zero_pivot_tolerance0 && zero_pivot_tolerance0 >= 0.0)
{
// We will use the previous result.
pivots[1] = zero_pivot_tolerance0;
bLastTry = true;
continue;
}
if (bUnknownLambdaMultiplicity)
{
lambda_multiplicity = N - rank;
}
// Otherwise, we will pick the best eigenvectors below.
break;
}
if (!(pivots[1] > 0.0 && pivots[0] >= pivots[1] && pivots[1] > pivots[2] && pivots[2] <= zero_pivot_tolerance))
{
// Basic sanity check failed.
// - should have a positive minimum "nonzero" pivot
// - maximum "nonzero" should be >= minimum "nonzero"
// - maximum "zero" should be < minimum "nonzero"
// - maximum "zero" should be <= zero_pivot_tolerance
break;
}
if (!(pivots[1] > zero_pivot_tolerance))
{
// If we don't increase zero_pivot_tolerance,
// we will note get a smaller rank from ON_RowReduce().
break;
}
double r1 = pivots[1] / pivots[0];
// At this point,
//
// pivot[0] = maximum pivot
// pivot[1] = minimum "nonzero" pivot > zero_pivot_tolerance
// pivot[2] = maximum "zero" pivot <= zero_pivot_tolerance
//
// from the call to ON_RowReduce and we have found
// (N - rank) eigenvectors. Assuming the input is valid,
// that is lambda really is an eigenvalue with multiplicity
// lambda_multiplicity, then there are more eigenvectors or
// we are in the case where the dimension of the eigenspace
// is less than the multiplicity of the eigenvalue
// (there are 1s on the super diagonal in Jordan decomposition).
//
// In order for an additional pass to be useful, the value
// of zero_pivot_tolerance needs to be increased but it
// must still be reasonable numerically.
//
// The value of pivot[1] is the smallest value for the
// next candidate for zero_pivot_tolerance.
//
// If pivots[1] / pivots[0] <= tols[0](1.0e-12), pivots[1] is "tiny"
// with respect to pivot[0], from the point of view of a
// double and addition. If the entries in the matrix
// are numerically reasonable for double precision
// calculations, then pivot[1] is a reasonable choice for
// a zero tolerance.
if (!(r1 <= tols[0]))
{
double d1 = pivots[0] - pivots[1];
// If r1 < tols[0](1.0e-3), there are at least 3 orders of
// magnitude between the maximum pivot and pivot[1].
// If d1 > tols[2](1.0e4) *pivots[1], pivot[1] is substantially closer to
// zero than it is to pivot[0].
if (!(r1 <= tols[1] && d1 > tols[2]*pivots[1]))
{
break;
}
}
rank0 = rank;
}
if (nullptr != eigenpivots)
{
eigenpivots[0] = pivots[0];
eigenpivots[1] = pivots[1];
eigenpivots[2] = pivots[2];
}
if (rank >= N)
return 0;
if (nullptr == B)
return 0;
// Return the best eigenvector candidates first.
ON_SimpleArray< double > Bprecision(N - rank);
for (unsigned int k = rank; k < N; k++)
Bprecision.Append(ON_EigenvectorPrecision(N, M, bTransposeM, lambda, B[k]));
ON_SimpleArray< unsigned int > _Bdex(Bprecision.UnsignedCount());
unsigned int* Bdex = _Bdex.Array();
ON_Sort(ON::sort_algorithm::quick_sort, Bdex, Bprecision.Array(), Bprecision.UnsignedCount(), sizeof(double), CompareDoubleIncreasing);
const unsigned int Bi0 = rank;
if (rank < N - lambda_multiplicity)
{
// we had too many candidates - return the best ones.
rank = N - lambda_multiplicity;
}
// B is a nonsingular row operation matrix and the last (N-rank) rows of B*A are zero.
// Therefore, the last (N-rank) columns of Transpose(A)*Transpose(B) are zero.
// Therefore, the last (N-rank) columns of Transpose(B) are a basis for the kernel of Transpose(A).
// Therefore, the last (N-rank) rows of B are a basis for the kernel of Transpose(A).
// Therefore, the last (N-rank) rows of B are a basis for the kernel of of (M - lambda*I).
// Therefore, the last (N-rank) rows of B are a basis for the lambda eigenspace of M.
for (unsigned int k = rank; k < N; k++)
{
const unsigned int ei = k - rank;
const unsigned int Bi = Bdex[ei];
double* V = eigenvectors[ei];
for (j = 0; j < N; j++)
V[j] = B[Bi0 + Bi][j];
if (nullptr != eigenprecision)
eigenprecision[ei] = Bprecision[Bi];
}
return (rank < N) ? (N - rank) : 0U;
}