X-Git-Url: https://www.ginac.de/ginac.git//ginac.git?p=ginac.git;a=blobdiff_plain;f=ginac%2Fmatrix.cpp;h=299cacfc98fa58ce8a6d78322ca49be9928794ce;hp=66f3999a4e37574d142f57d4dc68d486c572864c;hb=8cffcdf13d817a47f217f1a1043317d95969e070;hpb=67edef78ce992a8f6ad704bfac228b8dec6eacd2 diff --git a/ginac/matrix.cpp b/ginac/matrix.cpp index 66f3999a..299cacfc 100644 --- a/ginac/matrix.cpp +++ b/ginac/matrix.cpp @@ -3,7 +3,7 @@ * Implementation of symbolic matrices */ /* - * GiNaC Copyright (C) 1999-2010 Johannes Gutenberg University Mainz, Germany + * GiNaC Copyright (C) 1999-2019 Johannes Gutenberg University Mainz, Germany * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -73,15 +73,6 @@ matrix::matrix(unsigned r, unsigned c) : row(r), col(c), m(r*c, _ex0) setflag(status_flags::not_shareable); } -// protected - -/** Ctor from representation, for internal use only. */ -matrix::matrix(unsigned r, unsigned c, const exvector & m2) - : row(r), col(c), m(m2) -{ - setflag(status_flags::not_shareable); -} - /** Construct matrix from (flat) list of elements. If the list has fewer * elements than the matrix, the remaining matrix elements are set to zero. * If the list has more elements than the matrix, the excessive elements are @@ -92,15 +83,50 @@ matrix::matrix(unsigned r, unsigned c, const lst & l) setflag(status_flags::not_shareable); size_t i = 0; - for (lst::const_iterator it = l.begin(); it != l.end(); ++it, ++i) { + for (auto & it : l) { size_t x = i % c; size_t y = i / c; if (y >= r) break; // matrix smaller than list: throw away excessive elements - m[y*c+x] = *it; + m[y*c+x] = it; + ++i; + } +} + +/** Construct a matrix from an 2 dimensional initializer list. + * Throws an exception if some row has a different length than all the others. + */ +matrix::matrix(std::initializer_list> l) + : row(l.size()), col(l.begin()->size()) +{ + setflag(status_flags::not_shareable); + + m.reserve(row*col); + for (const auto & r : l) { + unsigned c = 0; + for (const auto & e : r) { + m.push_back(e); + ++c; + } + if (c != col) + throw std::invalid_argument("matrix::matrix{{}}: wrong dimension"); } } +// protected + +/** Ctor from representation, for internal use only. */ +matrix::matrix(unsigned r, unsigned c, const exvector & m2) + : row(r), col(c), m(m2) +{ + setflag(status_flags::not_shareable); +} +matrix::matrix(unsigned r, unsigned c, exvector && m2) + : row(r), col(c), m(std::move(m2)) +{ + setflag(status_flags::not_shareable); +} + ////////// // archiving ////////// @@ -114,10 +140,10 @@ void matrix::read_archive(const archive_node &n, lst &sym_lst) m.reserve(row * col); // XXX: default ctor inserts a zero element, we need to erase it here. m.pop_back(); - archive_node::archive_node_cit first = n.find_first("m"); - archive_node::archive_node_cit last = n.find_last("m"); + auto first = n.find_first("m"); + auto last = n.find_last("m"); ++last; - for (archive_node::archive_node_cit i=first; i != last; ++i) { + for (auto i=first; i != last; ++i) { ex e; n.find_ex_by_loc(i, e, sym_lst); m.push_back(e); @@ -130,10 +156,8 @@ void matrix::archive(archive_node &n) const inherited::archive(n); n.add_unsigned("row", row); n.add_unsigned("col", col); - exvector::const_iterator i = m.begin(), iend = m.end(); - while (i != iend) { - n.add_ex("m", *i); - ++i; + for (auto & i : m) { + n.add_ex("m", i); } } @@ -203,28 +227,6 @@ ex & matrix::let_op(size_t i) return m[i]; } -/** Evaluate matrix entry by entry. */ -ex matrix::eval(int level) const -{ - // check if we have to do anything at all - if ((level==1)&&(flags & status_flags::evaluated)) - return *this; - - // emergency break - if (level == -max_recursion_level) - throw (std::runtime_error("matrix::eval(): recursion limit exceeded")); - - // eval() entry by entry - exvector m2(row*col); - --level; - for (unsigned r=0; rsetflag(status_flags::dynallocated | - status_flags::evaluated); -} - ex matrix::subs(const exmap & mp, unsigned options) const { exvector m2(row * col); @@ -232,14 +234,14 @@ ex matrix::subs(const exmap & mp, unsigned options) const for (unsigned c=0; c ev(nullptr); + for (auto i=m.begin(); i!=m.end(); ++i) { ex x = i->conjugate(); if (ev) { ev->push_back(x); @@ -248,17 +250,15 @@ ex matrix::conjugate() const if (are_ex_trivially_equal(x, *i)) { continue; } - ev = new exvector; + ev.reset(new exvector); ev->reserve(m.size()); - for (exvector::const_iterator j=m.begin(); j!=i; ++j) { + for (auto j=m.begin(); j!=i; ++j) { ev->push_back(*j); } ev->push_back(x); } if (ev) { - ex result = matrix(row, col, *ev); - delete ev; - return result; + return matrix(row, col, std::move(*ev)); } return *this; } @@ -267,18 +267,18 @@ ex matrix::real_part() const { exvector v; v.reserve(m.size()); - for (exvector::const_iterator i=m.begin(); i!=m.end(); ++i) - v.push_back(i->real_part()); - return matrix(row, col, v); + for (auto & i : m) + v.push_back(i.real_part()); + return matrix(row, col, std::move(v)); } ex matrix::imag_part() const { exvector v; v.reserve(m.size()); - for (exvector::const_iterator i=m.begin(); i!=m.end(); ++i) - v.push_back(i->imag_part()); - return matrix(row, col, v); + for (auto & i : m) + v.push_back(i.imag_part()); + return matrix(row, col, std::move(v)); } // protected @@ -560,12 +560,11 @@ matrix matrix::add(const matrix & other) const throw std::logic_error("matrix::add(): incompatible matrices"); exvector sum(this->m); - exvector::iterator i = sum.begin(), end = sum.end(); - exvector::const_iterator ci = other.m.begin(); - while (i != end) - *i++ += *ci++; + auto ci = other.m.begin(); + for (auto & i : sum) + i += *ci++; - return matrix(row,col,sum); + return matrix(row, col, std::move(sum)); } @@ -578,12 +577,11 @@ matrix matrix::sub(const matrix & other) const throw std::logic_error("matrix::sub(): incompatible matrices"); exvector dif(this->m); - exvector::iterator i = dif.begin(), end = dif.end(); - exvector::const_iterator ci = other.m.begin(); - while (i != end) - *i++ -= *ci++; + auto ci = other.m.begin(); + for (auto & i : dif) + i -= *ci++; - return matrix(row,col,dif); + return matrix(row, col, std::move(dif)); } @@ -606,7 +604,7 @@ matrix matrix::mul(const matrix & other) const prod[r1*other.col+r2] += (m[r1*col+c] * other.m[c*other.col+r2]); } } - return matrix(row, other.col, prod); + return matrix(row, other.col, std::move(prod)); } @@ -619,7 +617,7 @@ matrix matrix::mul(const numeric & other) const for (unsigned c=0; crows(); ++c) trans[r*this->rows()+c] = m[c*this->cols()+r]; - return matrix(this->cols(),this->rows(),trans); + return matrix(this->cols(), this->rows(), std::move(trans)); } /** Determinant of square matrix. This routine doesn't actually calculate the @@ -748,18 +746,16 @@ ex matrix::determinant(unsigned algo) const bool numeric_flag = true; bool normal_flag = false; unsigned sparse_count = 0; // counts non-zero elements - exvector::const_iterator r = m.begin(), rend = m.end(); - while (r != rend) { - if (!r->info(info_flags::numeric)) + for (auto r : m) { + if (!r.info(info_flags::numeric)) numeric_flag = false; exmap srl; // symbol replacement list - ex rtest = r->to_rational(srl); + ex rtest = r.to_rational(srl); if (!rtest.is_zero()) ++sparse_count; if (!rtest.info(info_flags::crational_polynomial) && - rtest.info(info_flags::rational_function)) + rtest.info(info_flags::rational_function)) normal_flag = true; - ++r; } // Here is the heuristics in case this routine has to decide: @@ -842,23 +838,22 @@ ex matrix::determinant(unsigned algo) const } std::sort(c_zeros.begin(),c_zeros.end()); std::vector pre_sort; - for (std::vector::const_iterator i=c_zeros.begin(); i!=c_zeros.end(); ++i) - pre_sort.push_back(i->second); + for (auto & i : c_zeros) + pre_sort.push_back(i.second); std::vector pre_sort_test(pre_sort); // permutation_sign() modifies the vector so we make a copy here int sign = permutation_sign(pre_sort_test.begin(), pre_sort_test.end()); exvector result(row*col); // represents sorted matrix unsigned c = 0; - for (std::vector::const_iterator i=pre_sort.begin(); - i!=pre_sort.end(); - ++i,++c) { + for (auto & it : pre_sort) { for (unsigned r=0; rinfo(info_flags::numeric)) + for (auto & r : m) { + if (!r.info(info_flags::numeric)) { numeric_flag = false; - ++r; + break; + } } // The pure numeric case is traditionally rather common. Hence, it is @@ -942,12 +937,19 @@ ex matrix::charpoly(const ex & lambda) const } +/** Inverse of this matrix, with automatic algorithm selection. */ +matrix matrix::inverse() const +{ + return inverse(solve_algo::automatic); +} + /** Inverse of this matrix. * + * @param algo selects the algorithm (one of solve_algo) * @return the inverted matrix * @exception logic_error (matrix not square) * @exception runtime_error (singular matrix) */ -matrix matrix::inverse() const +matrix matrix::inverse(unsigned algo) const { if (row != col) throw (std::logic_error("matrix::inverse(): matrix not square")); @@ -970,7 +972,7 @@ matrix matrix::inverse() const matrix sol(row,col); try { - sol = this->solve(vars,identity); + sol = this->solve(vars, identity, algo); } catch (const std::runtime_error & e) { if (e.what()==std::string("matrix::solve(): inconsistent linear system")) throw (std::runtime_error("matrix::inverse(): singular matrix")); @@ -984,7 +986,7 @@ matrix matrix::inverse() const /** Solve a linear system consisting of a m x n matrix and a m x p right hand * side by applying an elimination scheme to the augmented matrix. * - * @param vars n x p matrix, all elements must be symbols + * @param vars n x p matrix, all elements must be symbols * @param rhs m x p matrix * @param algo selects the solving algorithm * @return n x p solution matrix @@ -1000,8 +1002,8 @@ matrix matrix::solve(const matrix & vars, const unsigned n = this->cols(); const unsigned p = rhs.cols(); - // syntax checks - if ((rhs.rows() != m) || (vars.rows() != n) || (vars.col != p)) + // syntax checks + if ((rhs.rows() != m) || (vars.rows() != n) || (vars.cols() != p)) throw (std::logic_error("matrix::solve(): incompatible matrices")); for (unsigned ro=0; roinfo(info_flags::numeric)) - numeric_flag = false; - ++r; - } - - // Here is the heuristics in case this routine has to decide: - if (algo == solve_algo::automatic) { - // Bareiss (fraction-free) elimination is generally a good guess: - algo = solve_algo::bareiss; - // For m<3, Bareiss elimination is equivalent to division free - // elimination but has more logistic overhead - if (m<3) - algo = solve_algo::divfree; - // This overrides any prior decisions. - if (numeric_flag) - algo = solve_algo::gauss; - } - + // Eliminate the augmented matrix: - switch(algo) { - case solve_algo::gauss: - aug.gauss_elimination(); - break; - case solve_algo::divfree: - aug.division_free_elimination(); - break; - case solve_algo::bareiss: - default: - aug.fraction_free_elimination(); - } + auto colid = aug.echelon_form(algo, n); // assemble the solution matrix: matrix sol(n,p); @@ -1058,48 +1028,51 @@ matrix matrix::solve(const matrix & vars, unsigned last_assigned_sol = n+1; for (int r=m-1; r>=0; --r) { unsigned fnz = 1; // first non-zero in row - while ((fnz<=n) && (aug.m[r*(n+p)+(fnz-1)].is_zero())) + while ((fnz<=n) && (aug.m[r*(n+p)+(fnz-1)].normal().is_zero())) ++fnz; if (fnz>n) { // row consists only of zeros, corresponding rhs must be 0, too - if (!aug.m[r*(n+p)+n+co].is_zero()) { + if (!aug.m[r*(n+p)+n+co].normal().is_zero()) { throw (std::runtime_error("matrix::solve(): inconsistent linear system")); } } else { // assign solutions for vars between fnz+1 and // last_assigned_sol-1: free parameters for (unsigned c=fnz; c +matrix::echelon_form(unsigned algo, int n) +{ + // Here is the heuristics in case this routine has to decide: + if (algo == solve_algo::automatic) { + // Gather some statistical information about the augmented matrix: + bool numeric_flag = true; + for (const auto & r : m) { + if (!r.info(info_flags::numeric)) { + numeric_flag = false; + break; + } + } + unsigned density = 0; + for (const auto & r : m) { + density += !r.is_zero(); + } + unsigned ncells = col*row; + if (numeric_flag) { + // For numerical matrices Gauss is good, but Markowitz becomes + // better for large sparse matrices. + if ((ncells > 200) && (density < ncells/2)) { + algo = solve_algo::markowitz; + } else { + algo = solve_algo::gauss; + } + } else { + // For symbolic matrices Markowitz is good, but Bareiss/Divfree + // is better for small and dense matrices. + if ((ncells < 120) && (density*5 > ncells*3)) { + if (ncells <= 12) { + algo = solve_algo::divfree; + } else { + algo = solve_algo::bareiss; + } + } else { + algo = solve_algo::markowitz; + } + } + } + // Eliminate the augmented matrix: + std::vector colid(col); + for (unsigned c = 0; c < col; c++) { + colid[c] = c; + } + switch(algo) { + case solve_algo::gauss: + gauss_elimination(); + break; + case solve_algo::divfree: + division_free_elimination(); + break; + case solve_algo::bareiss: + fraction_free_elimination(); + break; + case solve_algo::markowitz: + colid = markowitz_elimination(n); + break; + default: + throw std::invalid_argument("matrix::echelon_form(): 'algo' is not one of the solve_algo enum"); + } + return colid; +} /** Perform the steps of an ordinary Gaussian elimination to bring the m x n * matrix into an upper echelon form. The algorithm is ok for matrices @@ -1292,6 +1327,119 @@ int matrix::gauss_elimination(const bool det) return sign; } +/* Perform Markowitz-ordered Gaussian elimination (with full + * pivoting) on a matrix, constraining the choice of pivots to + * the first n columns (this simplifies handling of augmented + * matrices). Return the column id vector v, such that v[column] + * is the original number of the column before shuffling (v[i]==i + * for i >= n). */ +std::vector +matrix::markowitz_elimination(unsigned n) +{ + GINAC_ASSERT(n <= col); + std::vector rowcnt(row, 0); + std::vector colcnt(col, 0); + // Normalize everything before start. We'll keep all the + // cells normalized throughout the algorithm to properly + // handle unnormal zeros. + for (unsigned r = 0; r < row; r++) { + for (unsigned c = 0; c < col; c++) { + if (!m[r*col + c].is_zero()) { + m[r*col + c] = m[r*col + c].normal(); + rowcnt[r]++; + colcnt[c]++; + } + } + } + std::vector colid(col); + for (unsigned c = 0; c < col; c++) { + colid[c] = c; + } + exvector ab(row); + for (unsigned k = 0; (k < col) && (k < row - 1); k++) { + // Find the pivot that minimizes (rowcnt[r]-1)*(colcnt[c]-1). + unsigned pivot_r = row + 1; + unsigned pivot_c = col + 1; + int pivot_m = row*col; + for (unsigned r = k; r < row; r++) { + for (unsigned c = k; c < n; c++) { + const ex &mrc = m[r*col + c]; + if (mrc.is_zero()) + continue; + GINAC_ASSERT(rowcnt[r] > 0); + GINAC_ASSERT(colcnt[c] > 0); + int measure = (rowcnt[r] - 1)*(colcnt[c] - 1); + if (measure < pivot_m) { + pivot_m = measure; + pivot_r = r; + pivot_c = c; + } + } + } + if (pivot_m == row*col) { + // The rest of the matrix is zero. + break; + } + GINAC_ASSERT(k <= pivot_r && pivot_r < row); + GINAC_ASSERT(k <= pivot_c && pivot_c < col); + // Swap the pivot into (k, k). + if (pivot_c != k) { + for (unsigned r = 0; r < row; r++) { + m[r*col + pivot_c].swap(m[r*col + k]); + } + std::swap(colid[pivot_c], colid[k]); + std::swap(colcnt[pivot_c], colcnt[k]); + } + if (pivot_r != k) { + for (unsigned c = k; c < col; c++) { + m[pivot_r*col + c].swap(m[k*col + c]); + } + std::swap(rowcnt[pivot_r], rowcnt[k]); + } + // No normalization before is_zero() here, because + // we maintain the matrix normalized throughout the + // algorithm. + ex a = m[k*col + k]; + GINAC_ASSERT(!a.is_zero()); + // Subtract the pivot row KJI-style (so: loop by pivot, then + // column, then row) to maximally exploit pivot row zeros (at + // the expense of the pivot column zeros). The speedup compared + // to the usual KIJ order is not really significant though... + for (unsigned r = k + 1; r < row; r++) { + const ex &b = m[r*col + k]; + if (!b.is_zero()) { + ab[r] = b/a; + rowcnt[r]--; + } + } + colcnt[k] = rowcnt[k] = 0; + for (unsigned c = k + 1; c < col; c++) { + const ex &mr0c = m[k*col + c]; + if (mr0c.is_zero()) + continue; + colcnt[c]--; + for (unsigned r = k + 1; r < row; r++) { + if (ab[r].is_zero()) + continue; + bool waszero = m[r*col + c].is_zero(); + m[r*col + c] = (m[r*col + c] - ab[r]*mr0c).normal(); + bool iszero = m[r*col + c].is_zero(); + if (waszero && !iszero) { + rowcnt[r]++; + colcnt[c]++; + } + if (!waszero && iszero) { + rowcnt[r]--; + colcnt[c]--; + } + } + } + for (unsigned r = k + 1; r < row; r++) { + ab[r] = m[r*col + k] = _ex0; + } + } + return colid; +} /** Perform the steps of division free elimination to bring the m x n matrix * into an upper echelon form. @@ -1322,7 +1470,7 @@ int matrix::division_free_elimination(const bool det) sign = -sign; for (unsigned r2=r0+1; r2m[r2*n+c] = (this->m[r0*n+c0]*this->m[r2*n+c] - this->m[r2*n+c0]*this->m[r0*n+c]).expand(); + this->m[r2*n+c] = (this->m[r0*n+c0]*this->m[r2*n+c] - this->m[r2*n+c0]*this->m[r0*n+c]).normal(); // fill up left hand side with zeros for (unsigned c=r0; c<=c0; ++c) this->m[r2*n+c] = _ex0; @@ -1403,11 +1551,9 @@ int matrix::fraction_free_elimination(const bool det) matrix tmp_n(*this); matrix tmp_d(m,n); // for denominators, if needed exmap srl; // symbol replacement list - exvector::const_iterator cit = this->m.begin(), citend = this->m.end(); - exvector::iterator tmp_n_it = tmp_n.m.begin(), tmp_d_it = tmp_d.m.begin(); - while (cit != citend) { - ex nd = cit->normal().to_rational(srl).numer_denom(); - ++cit; + auto tmp_n_it = tmp_n.m.begin(), tmp_d_it = tmp_d.m.begin(); + for (auto & it : this->m) { + ex nd = it.normal().to_rational(srl).numer_denom(); *tmp_n_it++ = nd.op(0); *tmp_d_it++ = nd.op(1); } @@ -1476,11 +1622,10 @@ int matrix::fraction_free_elimination(const bool det) } // repopulate *this matrix: - exvector::iterator it = this->m.begin(), itend = this->m.end(); tmp_n_it = tmp_n.m.begin(); tmp_d_it = tmp_d.m.begin(); - while (it != itend) - *it++ = ((*tmp_n_it++)/(*tmp_d_it++)).subs(srl, subs_options::no_pattern); + for (auto & it : this->m) + it = ((*tmp_n_it++)/(*tmp_d_it++)).subs(srl, subs_options::no_pattern); return sign; } @@ -1496,7 +1641,7 @@ int matrix::fraction_free_elimination(const bool det) * @param co is the column to be inspected * @param symbolic signal if we want the first non-zero element to be pivoted * (true) or the one with the largest absolute value (false). - * @return 0 if no interchange occured, -1 if all are zero (usually signaling + * @return 0 if no interchange occurred, -1 if all are zero (usually signaling * a degeneracy) and positive integer k means that rows ro and k were swapped. */ int matrix::pivot(unsigned ro, unsigned co, bool symbolic) @@ -1541,34 +1686,34 @@ int matrix::pivot(unsigned ro, unsigned co, bool symbolic) */ bool matrix::is_zero_matrix() const { - for (exvector::const_iterator i=m.begin(); i!=m.end(); ++i) - if(!(i->is_zero())) + for (auto & i : m) + if (!i.is_zero()) return false; return true; } ex lst_to_matrix(const lst & l) { - lst::const_iterator itr, itc; - // Find number of rows and columns size_t rows = l.nops(), cols = 0; - for (itr = l.begin(); itr != l.end(); ++itr) { - if (!is_a(*itr)) + for (auto & itr : l) { + if (!is_a(itr)) throw (std::invalid_argument("lst_to_matrix: argument must be a list of lists")); - if (itr->nops() > cols) - cols = itr->nops(); + if (itr.nops() > cols) + cols = itr.nops(); } // Allocate and fill matrix - matrix &M = *new matrix(rows, cols); - M.setflag(status_flags::dynallocated); - - unsigned i; - for (itr = l.begin(), i = 0; itr != l.end(); ++itr, ++i) { - unsigned j; - for (itc = ex_to(*itr).begin(), j = 0; itc != ex_to(*itr).end(); ++itc, ++j) - M(i, j) = *itc; + matrix & M = dynallocate(rows, cols); + + unsigned i = 0; + for (auto & itr : l) { + unsigned j = 0; + for (auto & itc : ex_to(itr)) { + M(i, j) = itc; + ++j; + } + ++i; } return M; @@ -1576,24 +1721,40 @@ ex lst_to_matrix(const lst & l) ex diag_matrix(const lst & l) { - lst::const_iterator it; size_t dim = l.nops(); // Allocate and fill matrix - matrix &M = *new matrix(dim, dim); - M.setflag(status_flags::dynallocated); + matrix & M = dynallocate(dim, dim); - unsigned i; - for (it = l.begin(), i = 0; it != l.end(); ++it, ++i) - M(i, i) = *it; + unsigned i = 0; + for (auto & it : l) { + M(i, i) = it; + ++i; + } + + return M; +} + +ex diag_matrix(std::initializer_list l) +{ + size_t dim = l.size(); + + // Allocate and fill matrix + matrix & M = dynallocate(dim, dim); + + unsigned i = 0; + for (auto & it : l) { + M(i, i) = it; + ++i; + } return M; } ex unit_matrix(unsigned r, unsigned c) { - matrix &Id = *new matrix(r, c); - Id.setflag(status_flags::dynallocated); + matrix & Id = dynallocate(r, c); + Id.setflag(status_flags::evaluated); for (unsigned i=0; i(r, c); + M.setflag(status_flags::evaluated); bool long_format = (r > 10 || c > 10); bool single_row = (r == 1 || c == 1); @@ -1644,8 +1805,8 @@ ex reduced_matrix(const matrix& m, unsigned r, unsigned c) const unsigned rows = m.rows()-1; const unsigned cols = m.cols()-1; - matrix &M = *new matrix(rows, cols); - M.setflag(status_flags::dynallocated | status_flags::evaluated); + matrix & M = dynallocate(rows, cols); + M.setflag(status_flags::evaluated); unsigned ro = 0; unsigned ro2 = 0; @@ -1673,8 +1834,8 @@ ex sub_matrix(const matrix&m, unsigned r, unsigned nr, unsigned c, unsigned nc) if (r+nr>m.rows() || c+nc>m.cols()) throw std::runtime_error("sub_matrix(): index out of bounds"); - matrix &M = *new matrix(nr, nc); - M.setflag(status_flags::dynallocated | status_flags::evaluated); + matrix & M = dynallocate(nr, nc); + M.setflag(status_flags::evaluated); for (unsigned ro=0; ro