X-Git-Url: https://www.ginac.de/ginac.git//ginac.git?p=ginac.git;a=blobdiff_plain;f=ginac%2Fmatrix.cpp;h=f5c99a37b2d98fab176b97562e83c587ac3895b5;hp=ec7ee2c8e004415bd04eb4e8df44acc9d3fa7294;hb=45ca93fc48c14f733de73ffbbfef0834be731b08;hpb=e58227e1112f989f3b5417e497a61d53fc2971fa diff --git a/ginac/matrix.cpp b/ginac/matrix.cpp index ec7ee2c8..f5c99a37 100644 --- a/ginac/matrix.cpp +++ b/ginac/matrix.cpp @@ -3,7 +3,7 @@ * Implementation of symbolic matrices */ /* - * GiNaC Copyright (C) 1999-2001 Johannes Gutenberg University Mainz, Germany + * GiNaC Copyright (C) 1999-2018 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 @@ -17,78 +17,45 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#include -#include -#include - #include "matrix.h" -#include "archive.h" #include "numeric.h" #include "lst.h" -#include "utils.h" -#include "debugmsg.h" +#include "idx.h" +#include "indexed.h" +#include "add.h" #include "power.h" #include "symbol.h" +#include "operators.h" #include "normal.h" +#include "archive.h" +#include "utils.h" + +#include +#include +#include +#include +#include +#include -#ifndef NO_NAMESPACE_GINAC namespace GiNaC { -#endif // ndef NO_NAMESPACE_GINAC -GINAC_IMPLEMENT_REGISTERED_CLASS(matrix, basic) +GINAC_IMPLEMENT_REGISTERED_CLASS_OPT(matrix, basic, + print_func(&matrix::do_print). + print_func(&matrix::do_print_latex). + print_func(&matrix::do_print_tree). + print_func(&matrix::do_print_python_repr)) ////////// -// default constructor, destructor, copy constructor, assignment operator -// and helpers: +// default constructor ////////// -// public - /** Default ctor. Initializes to 1 x 1-dimensional zero-matrix. */ -matrix::matrix() : inherited(TINFO_matrix), row(1), col(1) -{ - debugmsg("matrix default constructor",LOGLEVEL_CONSTRUCT); - m.push_back(_ex0()); -} - -matrix::~matrix() -{ - debugmsg("matrix destructor",LOGLEVEL_DESTRUCT); - destroy(false); -} - -matrix::matrix(const matrix & other) -{ - debugmsg("matrix copy constructor",LOGLEVEL_CONSTRUCT); - copy(other); -} - -const matrix & matrix::operator=(const matrix & other) +matrix::matrix() : row(1), col(1), m(1, _ex0) { - debugmsg("matrix operator=",LOGLEVEL_ASSIGNMENT); - if (this != &other) { - destroy(true); - copy(other); - } - return *this; -} - -// protected - -void matrix::copy(const matrix & other) -{ - inherited::copy(other); - row = other.row; - col = other.col; - m = other.m; // STL's vector copying invoked here -} - -void matrix::destroy(bool call_parent) -{ - if (call_parent) inherited::destroy(call_parent); + setflag(status_flags::not_shareable); } ////////// @@ -101,206 +68,225 @@ void matrix::destroy(bool call_parent) * * @param r number of rows * @param c number of cols */ -matrix::matrix(unsigned r, unsigned c) - : inherited(TINFO_matrix), row(r), col(c) +matrix::matrix(unsigned r, unsigned c) : row(r), col(c), m(r*c, _ex0) +{ + 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 + * thrown away. */ +matrix::matrix(unsigned r, unsigned c, const lst & l) + : row(r), col(c), m(r*c, _ex0) +{ + setflag(status_flags::not_shareable); + + size_t i = 0; + 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; + ++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()) { - debugmsg("matrix constructor from unsigned,unsigned",LOGLEVEL_CONSTRUCT); - m.resize(r*c, _ex0()); + 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) - : inherited(TINFO_matrix), row(r), col(c), m(m2) + : row(r), col(c), m(m2) { - debugmsg("matrix constructor from unsigned,unsigned,exvector",LOGLEVEL_CONSTRUCT); + 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 ////////// -/** Construct object from archive_node. */ -matrix::matrix(const archive_node &n, const lst &sym_lst) : inherited(n, sym_lst) +void matrix::read_archive(const archive_node &n, lst &sym_lst) { - debugmsg("matrix constructor from archive_node", LOGLEVEL_CONSTRUCT); + inherited::read_archive(n, sym_lst); + if (!(n.find_unsigned("row", row)) || !(n.find_unsigned("col", col))) throw (std::runtime_error("unknown matrix dimensions in archive")); m.reserve(row * col); - for (unsigned int i=0; true; i++) { + // XXX: default ctor inserts a zero element, we need to erase it here. + m.pop_back(); + auto first = n.find_first("m"); + auto last = n.find_last("m"); + ++last; + for (auto i=first; i != last; ++i) { ex e; - if (n.find_ex("m", e, sym_lst, i)) - m.push_back(e); - else - break; + n.find_ex_by_loc(i, e, sym_lst); + m.push_back(e); } } +GINAC_BIND_UNARCHIVER(matrix); -/** Unarchive the object. */ -ex matrix::unarchive(const archive_node &n, const lst &sym_lst) -{ - return (new matrix(n, sym_lst))->setflag(status_flags::dynallocated); -} - -/** Archive the object. */ 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); } } ////////// -// functions overriding virtual functions from bases classes +// functions overriding virtual functions from base classes ////////// // public -basic * matrix::duplicate() const +void matrix::print_elements(const print_context & c, const char *row_start, const char *row_end, const char *row_sep, const char *col_sep) const { - debugmsg("matrix duplicate",LOGLEVEL_DUPLICATE); - return new matrix(*this); + for (unsigned ro=0; ro(row) * static_cast(col); } /** returns matrix entry at position (i/col, i%col). */ -ex matrix::op(int i) const +ex matrix::op(size_t i) const { + GINAC_ASSERT(i=0); GINAC_ASSERT(i ev(nullptr); + for (auto i=m.begin(); i!=m.end(); ++i) { + ex x = i->conjugate(); + if (ev) { + ev->push_back(x); + continue; + } + if (are_ex_trivially_equal(x, *i)) { + continue; + } + ev.reset(new exvector); + ev->reserve(m.size()); + for (auto j=m.begin(); j!=i; ++j) { + ev->push_back(*j); + } + ev->push_back(x); + } + if (ev) { + return matrix(row, col, std::move(*ev)); + } + return *this; } -/** evaluate matrix entry by entry. */ -ex matrix::eval(int level) const +ex matrix::real_part() const { - debugmsg("matrix eval",LOGLEVEL_MEMBER_FUNCTION); - - // 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 ); + exvector v; + v.reserve(m.size()); + for (auto & i : m) + v.push_back(i.real_part()); + return matrix(row, col, std::move(v)); } -/** evaluate matrix numerically entry by entry. */ -ex matrix::evalf(int level) const +ex matrix::imag_part() const { - debugmsg("matrix evalf",LOGLEVEL_MEMBER_FUNCTION); - - // check if we have to do anything at all - if (level==1) - return *this; - - // emergency break - if (level == -max_recursion_level) { - throw (std::runtime_error("matrix::evalf(): recursion limit exceeded")); - } - - // evalf() entry by entry - exvector m2(row*col); - --level; - for (unsigned r=0; r(const_cast(other)); + GINAC_ASSERT(is_exactly_a(other)); + const matrix &o = static_cast(other); // compare number of rows if (row != o.rows()) @@ -322,6 +308,243 @@ int matrix::compare_same_type(const basic & other) const return 0; } +bool matrix::match_same_type(const basic & other) const +{ + GINAC_ASSERT(is_exactly_a(other)); + const matrix & o = static_cast(other); + + // The number of rows and columns must be the same. This is necessary to + // prevent a 2x3 matrix from matching a 3x2 one. + return row == o.rows() && col == o.cols(); +} + +/** Automatic symbolic evaluation of an indexed matrix. */ +ex matrix::eval_indexed(const basic & i) const +{ + GINAC_ASSERT(is_a(i)); + GINAC_ASSERT(is_a(i.op(0))); + + bool all_indices_unsigned = static_cast(i).all_index_values_are(info_flags::nonnegint); + + // Check indices + if (i.nops() == 2) { + + // One index, must be one-dimensional vector + if (row != 1 && col != 1) + throw (std::runtime_error("matrix::eval_indexed(): vector must have exactly 1 index")); + + const idx & i1 = ex_to(i.op(1)); + + if (col == 1) { + + // Column vector + if (!i1.get_dim().is_equal(row)) + throw (std::runtime_error("matrix::eval_indexed(): dimension of index must match number of vector elements")); + + // Index numeric -> return vector element + if (all_indices_unsigned) { + unsigned n1 = ex_to(i1.get_value()).to_int(); + if (n1 >= row) + throw (std::runtime_error("matrix::eval_indexed(): value of index exceeds number of vector elements")); + return (*this)(n1, 0); + } + + } else { + + // Row vector + if (!i1.get_dim().is_equal(col)) + throw (std::runtime_error("matrix::eval_indexed(): dimension of index must match number of vector elements")); + + // Index numeric -> return vector element + if (all_indices_unsigned) { + unsigned n1 = ex_to(i1.get_value()).to_int(); + if (n1 >= col) + throw (std::runtime_error("matrix::eval_indexed(): value of index exceeds number of vector elements")); + return (*this)(0, n1); + } + } + + } else if (i.nops() == 3) { + + // Two indices + const idx & i1 = ex_to(i.op(1)); + const idx & i2 = ex_to(i.op(2)); + + if (!i1.get_dim().is_equal(row)) + throw (std::runtime_error("matrix::eval_indexed(): dimension of first index must match number of rows")); + if (!i2.get_dim().is_equal(col)) + throw (std::runtime_error("matrix::eval_indexed(): dimension of second index must match number of columns")); + + // Pair of dummy indices -> compute trace + if (is_dummy_pair(i1, i2)) + return trace(); + + // Both indices numeric -> return matrix element + if (all_indices_unsigned) { + unsigned n1 = ex_to(i1.get_value()).to_int(), n2 = ex_to(i2.get_value()).to_int(); + if (n1 >= row) + throw (std::runtime_error("matrix::eval_indexed(): value of first index exceeds number of rows")); + if (n2 >= col) + throw (std::runtime_error("matrix::eval_indexed(): value of second index exceeds number of columns")); + return (*this)(n1, n2); + } + + } else + throw (std::runtime_error("matrix::eval_indexed(): matrix must have exactly 2 indices")); + + return i.hold(); +} + +/** Sum of two indexed matrices. */ +ex matrix::add_indexed(const ex & self, const ex & other) const +{ + GINAC_ASSERT(is_a(self)); + GINAC_ASSERT(is_a(self.op(0))); + GINAC_ASSERT(is_a(other)); + GINAC_ASSERT(self.nops() == 2 || self.nops() == 3); + + // Only add two matrices + if (is_a(other.op(0))) { + GINAC_ASSERT(other.nops() == 2 || other.nops() == 3); + + const matrix &self_matrix = ex_to(self.op(0)); + const matrix &other_matrix = ex_to(other.op(0)); + + if (self.nops() == 2 && other.nops() == 2) { // vector + vector + + if (self_matrix.row == other_matrix.row) + return indexed(self_matrix.add(other_matrix), self.op(1)); + else if (self_matrix.row == other_matrix.col) + return indexed(self_matrix.add(other_matrix.transpose()), self.op(1)); + + } else if (self.nops() == 3 && other.nops() == 3) { // matrix + matrix + + if (self.op(1).is_equal(other.op(1)) && self.op(2).is_equal(other.op(2))) + return indexed(self_matrix.add(other_matrix), self.op(1), self.op(2)); + else if (self.op(1).is_equal(other.op(2)) && self.op(2).is_equal(other.op(1))) + return indexed(self_matrix.add(other_matrix.transpose()), self.op(1), self.op(2)); + + } + } + + // Don't know what to do, return unevaluated sum + return self + other; +} + +/** Product of an indexed matrix with a number. */ +ex matrix::scalar_mul_indexed(const ex & self, const numeric & other) const +{ + GINAC_ASSERT(is_a(self)); + GINAC_ASSERT(is_a(self.op(0))); + GINAC_ASSERT(self.nops() == 2 || self.nops() == 3); + + const matrix &self_matrix = ex_to(self.op(0)); + + if (self.nops() == 2) + return indexed(self_matrix.mul(other), self.op(1)); + else // self.nops() == 3 + return indexed(self_matrix.mul(other), self.op(1), self.op(2)); +} + +/** Contraction of an indexed matrix with something else. */ +bool matrix::contract_with(exvector::iterator self, exvector::iterator other, exvector & v) const +{ + GINAC_ASSERT(is_a(*self)); + GINAC_ASSERT(is_a(*other)); + GINAC_ASSERT(self->nops() == 2 || self->nops() == 3); + GINAC_ASSERT(is_a(self->op(0))); + + // Only contract with other matrices + if (!is_a(other->op(0))) + return false; + + GINAC_ASSERT(other->nops() == 2 || other->nops() == 3); + + const matrix &self_matrix = ex_to(self->op(0)); + const matrix &other_matrix = ex_to(other->op(0)); + + if (self->nops() == 2) { + + if (other->nops() == 2) { // vector * vector (scalar product) + + if (self_matrix.col == 1) { + if (other_matrix.col == 1) { + // Column vector * column vector, transpose first vector + *self = self_matrix.transpose().mul(other_matrix)(0, 0); + } else { + // Column vector * row vector, swap factors + *self = other_matrix.mul(self_matrix)(0, 0); + } + } else { + if (other_matrix.col == 1) { + // Row vector * column vector, perfect + *self = self_matrix.mul(other_matrix)(0, 0); + } else { + // Row vector * row vector, transpose second vector + *self = self_matrix.mul(other_matrix.transpose())(0, 0); + } + } + *other = _ex1; + return true; + + } else { // vector * matrix + + // B_i * A_ij = (B*A)_j (B is row vector) + if (is_dummy_pair(self->op(1), other->op(1))) { + if (self_matrix.row == 1) + *self = indexed(self_matrix.mul(other_matrix), other->op(2)); + else + *self = indexed(self_matrix.transpose().mul(other_matrix), other->op(2)); + *other = _ex1; + return true; + } + + // B_j * A_ij = (A*B)_i (B is column vector) + if (is_dummy_pair(self->op(1), other->op(2))) { + if (self_matrix.col == 1) + *self = indexed(other_matrix.mul(self_matrix), other->op(1)); + else + *self = indexed(other_matrix.mul(self_matrix.transpose()), other->op(1)); + *other = _ex1; + return true; + } + } + + } else if (other->nops() == 3) { // matrix * matrix + + // A_ij * B_jk = (A*B)_ik + if (is_dummy_pair(self->op(2), other->op(1))) { + *self = indexed(self_matrix.mul(other_matrix), self->op(1), other->op(2)); + *other = _ex1; + return true; + } + + // A_ij * B_kj = (A*Btrans)_ik + if (is_dummy_pair(self->op(2), other->op(2))) { + *self = indexed(self_matrix.mul(other_matrix.transpose()), self->op(1), other->op(1)); + *other = _ex1; + return true; + } + + // A_ji * B_jk = (Atrans*B)_ik + if (is_dummy_pair(self->op(1), other->op(1))) { + *self = indexed(self_matrix.transpose().mul(other_matrix), self->op(2), other->op(2)); + *other = _ex1; + return true; + } + + // A_ji * B_kj = (B*A)_ki + if (is_dummy_pair(self->op(1), other->op(2))) { + *self = indexed(other_matrix.mul(self_matrix), other->op(1), self->op(2)); + *other = _ex1; + return true; + } + } + + return false; +} + + ////////// // non-virtual functions in this class ////////// @@ -334,15 +557,14 @@ int matrix::compare_same_type(const basic & other) const matrix matrix::add(const matrix & other) const { if (col != other.col || row != other.row) - throw (std::logic_error("matrix::add(): incompatible matrices")); + throw std::logic_error("matrix::add(): incompatible matrices"); exvector sum(this->m); - exvector::iterator i; - exvector::const_iterator ci; - for (i=sum.begin(), ci=other.m.begin(); i!=sum.end(); ++i, ++ci) - (*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)); } @@ -352,15 +574,14 @@ matrix matrix::add(const matrix & other) const matrix matrix::sub(const matrix & other) const { if (col != other.col || row != other.row) - throw (std::logic_error("matrix::sub(): incompatible matrices")); + throw std::logic_error("matrix::sub(): incompatible matrices"); exvector dif(this->m); - exvector::iterator i; - exvector::const_iterator ci; - for (i=dif.begin(), ci=other.m.begin(); i!=dif.end(); ++i, ++ci) - (*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)); } @@ -370,23 +591,96 @@ matrix matrix::sub(const matrix & other) const matrix matrix::mul(const matrix & other) const { if (this->cols() != other.rows()) - throw (std::logic_error("matrix::mul(): incompatible matrices")); + throw std::logic_error("matrix::mul(): incompatible matrices"); exvector prod(this->rows()*other.cols()); for (unsigned r1=0; r1rows(); ++r1) { for (unsigned c=0; ccols(); ++c) { + // Quick test: can we shortcut? if (m[r1*col+c].is_zero()) continue; for (unsigned r2=0; r2(expn)) { + // Integer cases are computed by successive multiplication, using the + // obvious shortcut of storing temporaries, like A^4 == (A*A)*(A*A). + if (expn.info(info_flags::integer)) { + numeric b = ex_to(expn); + matrix A(row,col); + if (expn.info(info_flags::negative)) { + b *= -1; + A = this->inverse(); + } else { + A = *this; + } + matrix C(row,col); + for (unsigned r=0; r=row || co>=col) - throw (std::range_error("matrix::set(): index out of range")); - + throw (std::range_error("matrix::operator(): index out of range")); + ensure_if_modifiable(); - m[ro*col+co] = value; - return *this; + return m[ro*col+co]; } /** Transposed of an m x n matrix, producing a new n x m matrix object that * represents the transposed. */ -matrix matrix::transpose(void) const +matrix matrix::transpose() const { exvector trans(this->cols()*this->rows()); @@ -424,10 +719,9 @@ matrix matrix::transpose(void) 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 * determinant, it only implements some heuristics about which algorithm to * run. If all the elements of the matrix are elements of an integral domain @@ -452,15 +746,15 @@ ex matrix::determinant(unsigned algo) const bool numeric_flag = true; bool normal_flag = false; unsigned sparse_count = 0; // counts non-zero elements - for (exvector::const_iterator r=m.begin(); r!=m.end(); ++r) { - lst srl; // symbol replacement list - ex rtest = (*r).to_rational(srl); + for (auto r : m) { + if (!r.info(info_flags::numeric)) + numeric_flag = false; + exmap srl; // symbol replacement list + ex rtest = r.to_rational(srl); if (!rtest.is_zero()) ++sparse_count; - if (!rtest.info(info_flags::numeric)) - numeric_flag = false; if (!rtest.info(info_flags::crational_polynomial) && - rtest.info(info_flags::rational_function)) + rtest.info(info_flags::rational_function)) normal_flag = true; } @@ -486,7 +780,7 @@ ex matrix::determinant(unsigned algo) const else return m[0].expand(); } - + // Compute the determinant switch(algo) { case determinant_algo::gauss: { @@ -514,7 +808,7 @@ ex matrix::determinant(unsigned algo) const int sign; sign = tmp.division_free_elimination(true); if (sign==0) - return _ex0(); + return _ex0; ex det = tmp.m[row*col-1]; // factor out accumulated bogus slag for (unsigned d=0; d uintpair; std::vector c_zeros; // number of zeros in column for (unsigned c=0; c pre_sort; - for (std::vector::iterator i=c_zeros.begin(); i!=c_zeros.end(); ++i) - pre_sort.push_back(i->second); - int sign = permutation_sign(pre_sort); + 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::iterator i=pre_sort.begin(); - i!=pre_sort.end(); - ++i,++c) { + for (auto & it : pre_sort) { for (unsigned r=0; rmul(B); - c = B.trace()/ex(i+1); - poly -= c*power(lambda,row-i-1); + c = B.trace() / ex(i+1); + poly -= c*power(lambda, row-i-1); } if (row%2) return -poly; else return poly; - } + + } else { - matrix M(*this); - for (unsigned r=0; rsolve(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")); - } - if (indx != 0) { // swap rows r and indx of matrix tmp - for (unsigned i=0; irows(); 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; ro=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; ccols(); @@ -832,9 +1120,9 @@ ex matrix::determinant_minor(void) const // for (unsigned r=0; r +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 @@ -936,8 +1286,8 @@ int matrix::gauss_elimination(const bool det) int sign = 1; unsigned r0 = 0; - for (unsigned r1=0; (r1 0) sign = -sign; for (unsigned r2=r0+1; r2m[r2*n+r1].is_zero()) { + if (!this->m[r2*n+c0].is_zero()) { // yes, there is something to do in this row - ex piv = this->m[r2*n+r1] / this->m[r0*n+r1]; - for (unsigned c=r1+1; cm[r2*n+c0] / this->m[r0*n+c0]; + for (unsigned c=c0+1; cm[r2*n+c] -= piv * this->m[r0*n+c]; if (!this->m[r2*n+c].info(info_flags::numeric)) this->m[r2*n+c] = this->m[r2*n+c].normal(); } } // fill up left hand side with zeros - for (unsigned c=0; c<=r1; ++c) - this->m[r2*n+c] = _ex0(); + for (unsigned c=r0; c<=c0; ++c) + this->m[r2*n+c] = _ex0; } if (det) { // save space by deleting no longer needed elements for (unsigned c=r0+1; cm[r0*n+c] = _ex0(); + this->m[r0*n+c] = _ex0; } ++r0; } } - + // clear remaining rows + for (unsigned r=r0+1; rm[r*n+c] = _ex0; + } + 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. @@ -990,8 +1458,8 @@ int matrix::division_free_elimination(const bool det) int sign = 1; unsigned r0 = 0; - for (unsigned r1=0; (r10) sign = -sign; for (unsigned r2=r0+1; r2m[r2*n+c] = (this->m[r0*n+r1]*this->m[r2*n+c] - this->m[r2*n+r1]*this->m[r0*n+c]).expand(); + for (unsigned c=c0+1; cm[r2*n+c] = (this->m[r0*n+c0]*this->m[r2*n+c] - this->m[r2*n+c0]*this->m[r0*n+c]).expand(); // fill up left hand side with zeros - for (unsigned c=0; c<=r1; ++c) - this->m[r2*n+c] = _ex0(); + for (unsigned c=r0; c<=c0; ++c) + this->m[r2*n+c] = _ex0; } if (det) { // save space by deleting no longer needed elements for (unsigned c=r0+1; cm[r0*n+c] = _ex0(); + this->m[r0*n+c] = _ex0; } ++r0; } } - + // clear remaining rows + for (unsigned r=r0+1; rm[r*n+c] = _ex0; + } + return sign; } @@ -1040,7 +1513,7 @@ int matrix::fraction_free_elimination(const bool det) // // Bareiss (fraction-free) elimination in addition divides that element // by m[k-1](k-1,k-1) for k>1, where it can be shown by means of the - // Sylvester determinant that this really divides m[k+1](r,c). + // Sylvester identity that this really divides m[k+1](r,c). // // We also allow rational functions where the original prove still holds. // However, we must care for numerator and denominator separately and @@ -1077,39 +1550,46 @@ int matrix::fraction_free_elimination(const bool det) // makes things more complicated than they need to be. matrix tmp_n(*this); matrix tmp_d(m,n); // for denominators, if needed - lst srl; // symbol replacement list - exvector::iterator it = this->m.begin(); - exvector::iterator tmp_n_it = tmp_n.m.begin(); - exvector::iterator tmp_d_it = tmp_d.m.begin(); - for (; it!= this->m.end(); ++it, ++tmp_n_it, ++tmp_d_it) { - (*tmp_n_it) = (*it).normal().to_rational(srl); - (*tmp_d_it) = (*tmp_n_it).denom(); - (*tmp_n_it) = (*tmp_n_it).numer(); + exmap srl; // symbol replacement list + 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); } unsigned r0 = 0; - for (unsigned r1=0; (r1=0) { - if (indx>0) { + } else { + if (indx>r0) { + // Matrix needs pivoting, swap rows r0 and indx of tmp_n and tmp_d. sign = -sign; - // tmp_n's rows r0 and indx were swapped, do the same in tmp_d: - for (unsigned c=r1; cm.begin(); tmp_n_it = tmp_n.m.begin(); tmp_d_it = tmp_d.m.begin(); - for (; it!= this->m.end(); ++it, ++tmp_n_it, ++tmp_d_it) - (*it) = ((*tmp_n_it)/(*tmp_d_it)).subs(srl); + for (auto & it : this->m) + it = ((*tmp_n_it++)/(*tmp_d_it++)).subs(srl, subs_options::no_pattern); return sign; } @@ -1156,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) @@ -1168,12 +1653,12 @@ int matrix::pivot(unsigned ro, unsigned co, bool symbolic) ++k; } else { // search largest element in column co beginning at row ro - GINAC_ASSERT(is_ex_of_type(this->m[k*col+co],numeric)); + GINAC_ASSERT(is_exactly_a(this->m[k*col+co])); unsigned kmax = k+1; - numeric mmax = abs(ex_to_numeric(m[kmax*col+co])); + numeric mmax = abs(ex_to(m[kmax*col+co])); while (kmaxm[kmax*col+co],numeric)); - numeric tmp = ex_to_numeric(this->m[kmax*col+co]); + GINAC_ASSERT(is_exactly_a(this->m[kmax*col+co])); + numeric tmp = ex_to(this->m[kmax*col+co]); if (abs(tmp) > mmax) { mmax = tmp; k = kmax; @@ -1197,29 +1682,168 @@ int matrix::pivot(unsigned ro, unsigned co, bool symbolic) return k; } -/** Convert list of lists to matrix. */ -ex lst_to_matrix(const ex &l) +/** Function to check that all elements of the matrix are zero. + */ +bool matrix::is_zero_matrix() const +{ + for (auto & i : m) + if (!i.is_zero()) + return false; + return true; +} + +ex lst_to_matrix(const lst & l) { - if (!is_ex_of_type(l, lst)) - throw(std::invalid_argument("argument to lst_to_matrix() must be a lst")); - // Find number of rows and columns - unsigned rows = l.nops(), cols = 0, i, j; - for (i=0; i cols) - cols = l.op(i).nops(); - + size_t rows = l.nops(), cols = 0; + 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(); + } + // Allocate and fill matrix - matrix &m = *new matrix(rows, cols); - for (i=0; i j) - m.set(i, j, l.op(i).op(j)); - else - m.set(i, j, ex(0)); - return m; + 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; +} + +ex diag_matrix(const lst & l) +{ + size_t dim = l.nops(); + + // Allocate and fill matrix + matrix & M = dynallocate(dim, dim); + + 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 = 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); + + for (unsigned i=0; im.rows() || c+1>m.cols() || m.cols()<2 || m.rows()<2) + throw std::runtime_error("minor_matrix(): index out of bounds"); + + const unsigned rows = m.rows()-1; + const unsigned cols = m.cols()-1; + matrix & M = dynallocate(rows, cols); + M.setflag(status_flags::evaluated); + + unsigned ro = 0; + unsigned ro2 = 0; + while (ro2m.rows() || c+nc>m.cols()) + throw std::runtime_error("sub_matrix(): index out of bounds"); + + matrix & M = dynallocate(nr, nc); + M.setflag(status_flags::evaluated); + + for (unsigned ro=0; ro