X-Git-Url: https://www.ginac.de/ginac.git//ginac.git?p=ginac.git;a=blobdiff_plain;f=ginac%2Fmatrix.cpp;h=6240ce20e9e36a14557e38a9918841ecf63923c7;hp=34d5f2f93c008a0deeac8aeaf0e4aff64992ed18;hb=db5765dc91202851739e196ba11bfccb0b3fe7bc;hpb=487e5659efe401683eee0381b0d23f967ffffc3c diff --git a/ginac/matrix.cpp b/ginac/matrix.cpp index 34d5f2f9..6240ce20 100644 --- a/ginac/matrix.cpp +++ b/ginac/matrix.cpp @@ -3,7 +3,7 @@ * Implementation of symbolic matrices */ /* - * GiNaC Copyright (C) 1999 Johannes Gutenberg University Mainz, Germany + * GiNaC Copyright (C) 1999-2000 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 @@ -21,9 +21,24 @@ */ #include +#include #include #include "matrix.h" +#include "archive.h" +#include "numeric.h" +#include "lst.h" +#include "utils.h" +#include "debugmsg.h" +#include "power.h" +#include "symbol.h" +#include "normal.h" + +#ifndef NO_NAMESPACE_GINAC +namespace GiNaC { +#endif // ndef NO_NAMESPACE_GINAC + +GINAC_IMPLEMENT_REGISTERED_CLASS(matrix, basic) ////////// // default constructor, destructor, copy constructor, assignment operator @@ -34,10 +49,10 @@ /** Default ctor. Initializes to 1 x 1-dimensional zero-matrix. */ matrix::matrix() - : basic(TINFO_matrix), row(1), col(1) + : inherited(TINFO_matrix), row(1), col(1) { debugmsg("matrix default constructor",LOGLEVEL_CONSTRUCT); - m.push_back(exZERO()); + m.push_back(_ex0()); } matrix::~matrix() @@ -45,13 +60,13 @@ matrix::~matrix() debugmsg("matrix destructor",LOGLEVEL_DESTRUCT); } -matrix::matrix(matrix const & other) +matrix::matrix(const matrix & other) { debugmsg("matrix copy constructor",LOGLEVEL_CONSTRUCT); copy(other); } -matrix const & matrix::operator=(matrix const & other) +const matrix & matrix::operator=(const matrix & other) { debugmsg("matrix operator=",LOGLEVEL_ASSIGNMENT); if (this != &other) { @@ -63,17 +78,17 @@ matrix const & matrix::operator=(matrix const & other) // protected -void matrix::copy(matrix const & other) +void matrix::copy(const matrix & other) { - basic::copy(other); - row=other.row; - col=other.col; - m=other.m; // use STL's vector copying + 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) basic::destroy(call_parent); + if (call_parent) inherited::destroy(call_parent); } ////////// @@ -86,20 +101,59 @@ void matrix::destroy(bool call_parent) * * @param r number of rows * @param c number of cols */ -matrix::matrix(int r, int c) - : basic(TINFO_matrix), row(r), col(c) +matrix::matrix(unsigned r, unsigned c) + : inherited(TINFO_matrix), row(r), col(c) { - debugmsg("matrix constructor from int,int",LOGLEVEL_CONSTRUCT); - m.resize(r*c, exZERO()); + debugmsg("matrix constructor from unsigned,unsigned",LOGLEVEL_CONSTRUCT); + m.resize(r*c, _ex0()); } // protected /** Ctor from representation, for internal use only. */ -matrix::matrix(int r, int c, vector const & m2) - : basic(TINFO_matrix), row(r), col(c), m(m2) +matrix::matrix(unsigned r, unsigned c, const exvector & m2) + : inherited(TINFO_matrix), row(r), col(c), m(m2) { - debugmsg("matrix constructor from int,int,vector",LOGLEVEL_CONSTRUCT); + debugmsg("matrix constructor from unsigned,unsigned,exvector",LOGLEVEL_CONSTRUCT); +} + +////////// +// archiving +////////// + +/** Construct object from archive_node. */ +matrix::matrix(const archive_node &n, const lst &sym_lst) : inherited(n, sym_lst) +{ + debugmsg("matrix constructor from archive_node", LOGLEVEL_CONSTRUCT); + 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++) { + ex e; + if (n.find_ex("m", e, sym_lst, i)) + m.push_back(e); + else + break; + } +} + +/** 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; + } } ////////// @@ -114,41 +168,82 @@ basic * matrix::duplicate() const return new matrix(*this); } +void matrix::print(std::ostream & os, unsigned upper_precedence) const +{ + debugmsg("matrix print",LOGLEVEL_PRINT); + os << "[[ "; + for (unsigned r=0; r=0); + GINAC_ASSERT(i tmp(row*col); - for (int i=0; i::const_iterator r=m.begin(); r!=m.end(); ++r) { + for (exvector::const_iterator r=m.begin(); r!=m.end(); ++r) if ((*r).has(other)) return true; - } + return false; } @@ -158,23 +253,19 @@ ex matrix::eval(int level) const debugmsg("matrix eval",LOGLEVEL_MEMBER_FUNCTION); // check if we have to do anything at all - if ((level==1)&&(flags & status_flags::evaluated)) { + if ((level==1)&&(flags & status_flags::evaluated)) return *this; - } // emergency break - if (level == -max_recursion_level) { + if (level == -max_recursion_level) throw (std::runtime_error("matrix::eval(): recursion limit exceeded")); - } // eval() entry by entry - vector m2(row*col); - --level; - for (int r=0; rsetflag(status_flags::dynallocated | status_flags::evaluated ); @@ -186,9 +277,8 @@ ex matrix::evalf(int level) const debugmsg("matrix evalf",LOGLEVEL_MEMBER_FUNCTION); // check if we have to do anything at all - if (level==1) { + if (level==1) return *this; - } // emergency break if (level == -max_recursion_level) { @@ -196,38 +286,35 @@ ex matrix::evalf(int level) const } // evalf() entry by entry - vector m2(row*col); + exvector m2(row*col); --level; - for (int r=0; r(const_cast(other)); + GINAC_ASSERT(is_exactly_of_type(other, matrix)); + const matrix & o = static_cast(const_cast(other)); // compare number of rows - if (row != o.rows()) { + if (row != o.rows()) return row < o.rows() ? -1 : 1; - } // compare number of columns - if (col != o.cols()) { + if (col != o.cols()) return col < o.cols() ? -1 : 1; - } // equal number of rows and columns, compare individual elements int cmpval; - for (int r=0; r sum(this->m); - vector::iterator i; - vector::const_iterator ci; - for (i=sum.begin(), ci=other.m.begin(); - i!=sum.end(); - ++i, ++ci) { + 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); - } + return matrix(row,col,sum); } + /** Difference of matrices. * * @exception logic_error (incompatible matrices) */ -matrix matrix::sub(matrix const & other) const +matrix matrix::sub(const matrix & other) const { - if (col != other.col || row != other.row) { + if (col != other.col || row != other.row) throw (std::logic_error("matrix::sub(): incompatible matrices")); - } - vector dif(this->m); - vector::iterator i; - vector::const_iterator ci; - for (i=dif.begin(), ci=other.m.begin(); - i!=dif.end(); - ++i, ++ci) { + 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); - } + return matrix(row,col,dif); } + /** Product of matrices. * * @exception logic_error (incompatible matrices) */ -matrix matrix::mul(matrix const & other) const +matrix matrix::mul(const matrix & other) const { - if (col != other.row) { + if (this->cols() != other.rows()) throw (std::logic_error("matrix::mul(): incompatible matrices")); - } - vector prod(row*other.col); - for (int i=0; irows()*other.cols()); + + for (unsigned r1=0; r1rows(); ++r1) { + for (unsigned c=0; ccols(); ++c) { + if (m[r1*col+c].is_zero()) + continue; + for (unsigned r2=0; r2=row || co<0 || co>=col) { + if (ro<0 || ro>=row || co<0 || co>=col) throw (std::range_error("matrix::operator(): index out of range")); - } - + return m[ro*col+co]; } + /** Set individual elements manually. * * @exception range_error (index out of range) */ -matrix & matrix::set(int ro, int co, ex value) +matrix & matrix::set(unsigned ro, unsigned co, ex value) { - if (ro<0 || ro>=row || co<0 || co>=col) { + if (ro<0 || ro>=row || co<0 || co>=col) throw (std::range_error("matrix::set(): index out of range")); - } ensure_if_modifiable(); - m[ro*col+co]=value; + m[ro*col+co] = value; return *this; } + /** Transposed of an m x n matrix, producing a new n x m matrix object that * represents the transposed. */ matrix matrix::transpose(void) const { - vector trans(col*row); + exvector trans(this->cols()*this->rows()); - for (int r=0; rcols(); ++r) + for (unsigned c=0; crows(); ++c) + trans[r*this->rows()+c] = m[c*this->cols()+r]; - for (int r1=0; r1cols(),this->rows(),trans); } -// Compute the sign of a permutation of a vector of things, used internally -// by determinant_symbolic_perm() where it is instantiated for int. -template -int permutation_sign(vector s) -{ - if (s.size() < 2) - return 0; - int sigma=1; - for (typename vector::iterator i=s.begin(); i!=s.end()-1; ++i) { - for (typename vector::iterator j=i+1; j!=s.end(); ++j) { - if (*i == *j) - return 0; - if (*i > *j) { - iter_swap(i,j); - sigma = -sigma; - } - } - } - return sigma; -} -/** Determinant built by application of the full permutation group. This - * routine is only called internally by matrix::determinant(). */ -ex determinant_symbolic_perm(const matrix & M) +/** 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 + * the determinant is also in that integral domain and the result is expanded + * only. If one or more elements are from a quotient field the determinant is + * usually also in that quotient field and the result is normalized before it + * is returned. This implies that the determinant of the symbolic 2x2 matrix + * [[a/(a-b),1],[b/(a-b),1]] is returned as unity. (In this respect, it + * behaves like MapleV and unlike Mathematica.) + * + * @param algo allows to chose an algorithm + * @return the determinant as a new expression + * @exception logic_error (matrix not square) + * @see determinant_algo */ +ex matrix::determinant(unsigned algo) const { - ASSERT(M.rows()==M.cols()); // cannot happen, just in case... + if (row!=col) + throw (std::logic_error("matrix::determinant(): matrix not square")); + GINAC_ASSERT(row*col==m.capacity()); - if (M.rows()==1) { // speed things up - return M(0,0); + // Gather some statistical information about this matrix: + 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); + 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)) + normal_flag = true; } - ex det; - ex term; - vector sigma(M.cols()); - for (int i=0; i3 && 5*sparse_count<=row*col) + algo = determinant_algo::bareiss; + // Purely numeric matrix can be handled by Gauss elimination. + // This overrides any prior decisions. + if (numeric_flag) + algo = determinant_algo::gauss; } - if (M.rows()==2) { // speed things up - return (M(0,0)*M(1,1)- - M(1,0)*M(0,1)); - } - if (M.rows()==3) { // speed things up even a little more - return ((M(2,1)*M(0,2)-M(2,2)*M(0,1))*M(1,0)+ - (M(1,2)*M(0,1)-M(1,1)*M(0,2))*M(2,0)+ - (M(2,2)*M(1,1)-M(2,1)*M(1,2))*M(0,0)); + + // Trap the trivial case here, since some algorithms don't like it + if (this->row==1) { + // for consistency with non-trivial determinants... + if (normal_flag) + return m[0].normal(); + else + return m[0].expand(); } - ex det; - matrix minorM(M.rows()-1,M.cols()-1); - for (int r1=0; r1::const_iterator r=m.begin(); r!=m.end(); ++r) { - if (!(*r).info(info_flags::numeric)) { - if (normalized) { - return determinant_symbolic_minor(*this).normal(); - } else { - return determinant_symbolic_perm(*this); + case determinant_algo::divfree: { + matrix tmp(*this); + int sign; + sign = tmp.division_free_elimination(true); + if (sign==0) + 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); + 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 (unsigned r=0; rmul(B); + c = B.trace()/ex(i+1); + poly -= c*power(lambda,row-i-1); + } + if (row%2) + return -poly; + else + return poly; } matrix M(*this); - for (int r=0; rrows(); + const unsigned n = this->cols(); + const unsigned p = rhs.cols(); + + // syntax checks + if ((rhs.rows() != m) || (vars.rows() != n) || (vars.col != p)) throw (std::logic_error("matrix::solve(): incompatible matrices")); + for (unsigned ro=0; rom[r*n+c]; + for (unsigned c=0; c=0; --r) { + unsigned fnz = 1; // first non-zero in row + while ((fnz<=n) && (aug.m[r*(n+p)+(fnz-1)].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()) { + throw (std::runtime_error("matrix::solve(): inconsistent linear system")); + } } else { - break; + // assign solutions for vars between fnz+1 and + // last_assigned_sol-1: free parameters + for (unsigned c=fnz; czero_in_last_row)||(zero_in_this_row=n)); - zero_in_last_row=zero_in_this_row; + // assign solutions for vars between 1 and + // last_assigned_sol-1: free parameters + for (unsigned ro=0; ro0; --r) { - int first_non_zero=1; - while ((first_non_zero<=n)&&(a.ffe_get(r,first_non_zero).is_zero())) { - first_non_zero++; - } - if (first_non_zero>n) { - // row consists only of zeroes, corresponding rhs must be 0 as well - if (!b.ffe_get(r,1).is_zero()) { - throw (std::runtime_error("matrix::fraction_free_elim(): singular matrix")); - } - } else { - // assign solutions for vars between first_non_zero+1 and - // last_assigned_sol-1: free parameters - for (int c=first_non_zero+1; c<=last_assigned_sol-1; ++c) { - sol.ffe_set(c,1,vars.ffe_get(c,1)); + + return sol; +} + + +// protected + +/** Recursive determinant for small matrices having at least one symbolic + * entry. The basic algorithm, known as Laplace-expansion, is enhanced by + * some bookkeeping to avoid calculation of the same submatrices ("minors") + * more than once. According to W.M.Gentleman and S.C.Johnson this algorithm + * is better than elimination schemes for matrices of sparse multivariate + * polynomials and also for matrices of dense univariate polynomials if the + * matrix' dimesion is larger than 7. + * + * @return the determinant as a new expression (in expanded form) + * @see matrix::determinant() */ +ex matrix::determinant_minor(void) const +{ + // for small matrices the algorithm does not make any sense: + const unsigned n = this->cols(); + if (n==1) + return m[0].expand(); + if (n==2) + return (m[0]*m[3]-m[2]*m[1]).expand(); + if (n==3) + return (m[0]*m[4]*m[8]-m[0]*m[5]*m[7]- + m[1]*m[3]*m[8]+m[2]*m[3]*m[7]+ + m[1]*m[5]*m[6]-m[2]*m[4]*m[6]).expand(); + + // This algorithm can best be understood by looking at a naive + // implementation of Laplace-expansion, like this one: + // ex det; + // matrix minorM(this->rows()-1,this->cols()-1); + // for (unsigned r1=0; r1rows(); ++r1) { + // // shortcut if element(r1,0) vanishes + // if (m[r1*col].is_zero()) + // continue; + // // assemble the minor matrix + // for (unsigned r=0; r Pkey; + Pkey.reserve(n); + // key for minor determinant (a subpartition of Pkey) + std::vector Mkey; + Mkey.reserve(n-1); + // we store our subminors in maps, keys being the rows they arise from + typedef std::map,class ex> Rmap; + typedef std::map,class ex>::value_type Rmap_value; + Rmap A; + Rmap B; + ex det; + // initialize A with last column: + for (unsigned r=0; r=0; --c) { + Pkey.erase(Pkey.begin(),Pkey.end()); // don't change capacity + Mkey.erase(Mkey.begin(),Mkey.end()); + for (unsigned i=0; i0; --fc) { + ++Pkey[fc-1]; + if (Pkey[fc-1]" << sol.ffe_get(c,1) << endl; - } - */ - -#ifdef DOASSERT - // test solution with echelon matrix - for (int r=1; r<=m; ++r) { - ex e=0; - for (int c=1; c<=n; ++c) { - e=e+a.ffe_get(r,c)*sol.ffe_get(c,1); + +/** 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 + * with numeric coefficients but quite unsuited for symbolic matrices. + * + * @param det may be set to true to save a lot of space if one is only + * interested in the diagonal elements (i.e. for calculating determinants). + * The others are set to zero in this case. + * @return sign is 1 if an even number of rows was swapped, -1 if an odd + * number of rows was swapped and 0 if the matrix is singular. */ +int matrix::gauss_elimination(const bool det) +{ + ensure_if_modifiable(); + const unsigned m = this->rows(); + const unsigned n = this->cols(); + GINAC_ASSERT(!det || n==m); + int sign = 1; + + unsigned r0 = 0; + for (unsigned r1=0; (r11, where it can be shown by means of the + // Sylvester determinant 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 + // "manually" work in the integral domains because of subtle cancellations + // (see below). This blows up the bookkeeping a bit and the formula has + // to be modified to expand like this (N{x} stands for numerator of x, + // D{x} for denominator of x): + // N{m[k+1](r,c)} = N{m[k](k,k)}*N{m[k](r,c)}*D{m[k](r,k)}*D{m[k](k,c)} + // -N{m[k](r,k)}*N{m[k](k,c)}*D{m[k](k,k)}*D{m[k](r,c)} + // D{m[k+1](r,c)} = D{m[k](k,k)}*D{m[k](r,c)}*D{m[k](r,k)}*D{m[k](k,c)} + // where for k>1 we now divide N{m[k+1](r,c)} by + // N{m[k-1](k-1,k-1)} + // and D{m[k+1](r,c)} by + // D{m[k-1](k-1,k-1)}. - // build the extended matrix of *this with v attached to the right - matrix tmp(row,col+v.col); - for (int r=0; rrows(); + const unsigned n = this->cols(); + GINAC_ASSERT(!det || n==m); + int sign = 1; + if (m==1) + return 1; + ex divisor_n = 1; + ex divisor_d = 1; + ex dividend_n; + ex dividend_d; + + // We populate temporary matrices to subsequently operate on. There is + // one holding numerators and another holding denominators of entries. + // This is a must since the evaluator (or even earlier mul's constructor) + // might cancel some trivial element which causes divide() to fail. The + // elements are normalized first (yes, even though this algorithm doesn't + // need GCDs) since the elements of *this might be unnormalized, which + // 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(); } - for (int r1=0; r1=0) { + if (indx>0) { + sign = -sign; + // tmp_n's rows r0 and indx were swapped, do the same in tmp_d: + for (unsigned c=r1; c sol(v.row*v.col); - for (int c=0; c=0; --r) { - sol[r*v.col+c] = tmp[r*tmp.col+c]; - for (int i=r+1; im.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); + + return sign; } -// protected -/** Partial pivoting method. - * Usual pivoting returns the index to the element with the largest absolute - * value and swaps the current row with the one where the element was found. - * Here it does the same with the first non-zero element. (This works fine, - * but may be far from optimal for numerics.) */ -int matrix::pivot(int ro) +/** Partial pivoting method for matrix elimination schemes. + * Usual pivoting (symbolic==false) returns the index to the element with the + * largest absolute value in column ro and swaps the current row with the one + * where the element was found. With (symbolic==true) it does the same thing + * with the first non-zero element. + * + * @param ro is the row from where to begin + * @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 + * a degeneracy) and positive integer k means that rows ro and k were swapped. + */ +int matrix::pivot(unsigned ro, unsigned co, bool symbolic) { - int k=ro; - - for (int r=ro; rm[k*col+co].expand().is_zero())) + ++k; + } else { + // search largest element in column co beginning at row ro + GINAC_ASSERT(is_ex_of_type(this->m[k*col+co],numeric)); + unsigned kmax = k+1; + numeric mmax = abs(ex_to_numeric(m[kmax*col+co])); + while (kmaxm[kmax*col+co],numeric)); + numeric tmp = ex_to_numeric(this->m[kmax*col+co]); + if (abs(tmp) > mmax) { + mmax = tmp; + k = kmax; + } + ++kmax; } + if (!mmax.is_zero()) + k = kmax; } - if (m[k*col+ro].is_zero()) { + if (k==row) + // all elements in column co below row ro vanish return -1; - } - if (k!=ro) { // swap rows - for (int c=0; c cols) + cols = l.op(i).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; } ////////// @@ -867,4 +1219,8 @@ int matrix::pivot(int ro) ////////// const matrix some_matrix; -type_info const & typeid_matrix=typeid(some_matrix); +const type_info & typeid_matrix=typeid(some_matrix); + +#ifndef NO_NAMESPACE_GINAC +} // namespace GiNaC +#endif // ndef NO_NAMESPACE_GINAC