|
GiNaC
1.6.2
|
00001 00005 /* 00006 * GiNaC Copyright (C) 1999-2011 Johannes Gutenberg University Mainz, Germany 00007 * 00008 * This program is free software; you can redistribute it and/or modify 00009 * it under the terms of the GNU General Public License as published by 00010 * the Free Software Foundation; either version 2 of the License, or 00011 * (at your option) any later version. 00012 * 00013 * This program is distributed in the hope that it will be useful, 00014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00016 * GNU General Public License for more details. 00017 * 00018 * You should have received a copy of the GNU General Public License 00019 * along with this program; if not, write to the Free Software 00020 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 00021 */ 00022 00023 #include "basic.h" 00024 #include "ex.h" 00025 #include "numeric.h" 00026 #include "power.h" 00027 #include "add.h" 00028 #include "symbol.h" 00029 #include "lst.h" 00030 #include "ncmul.h" 00031 #include "relational.h" 00032 #include "operators.h" 00033 #include "wildcard.h" 00034 #include "archive.h" 00035 #include "utils.h" 00036 #include "hash_seed.h" 00037 #include "inifcns.h" 00038 00039 #include <iostream> 00040 #include <stdexcept> 00041 #include <typeinfo> 00042 00043 namespace GiNaC { 00044 00045 GINAC_IMPLEMENT_REGISTERED_CLASS_OPT(basic, void, 00046 print_func<print_context>(&basic::do_print). 00047 print_func<print_tree>(&basic::do_print_tree). 00048 print_func<print_python_repr>(&basic::do_print_python_repr)) 00049 00050 00051 // default constructor, destructor, copy constructor and assignment operator 00053 00054 // public 00055 00056 00059 basic::basic(const basic & other) : flags(other.flags & ~status_flags::dynallocated), hashvalue(other.hashvalue) 00060 { 00061 } 00062 00064 const basic & basic::operator=(const basic & other) 00065 { 00066 unsigned fl = other.flags & ~status_flags::dynallocated; 00067 if (typeid(*this) != typeid(other)) { 00068 // The other object is of a derived class, so clear the flags as they 00069 // might no longer apply (especially hash_calculated). Oh, and don't 00070 // copy the tinfo_key: it is already set correctly for this object. 00071 fl &= ~(status_flags::evaluated | status_flags::expanded | status_flags::hash_calculated); 00072 } else { 00073 // The objects are of the exact same class, so copy the hash value. 00074 hashvalue = other.hashvalue; 00075 } 00076 flags = fl; 00077 set_refcount(0); 00078 return *this; 00079 } 00080 00081 // protected 00082 00083 // none (all inlined) 00084 00086 // other constructors 00088 00089 // none (all inlined) 00090 00092 // archiving 00094 00096 void basic::read_archive(const archive_node& n, lst& syms) 00097 { } 00098 00100 void basic::archive(archive_node &n) const 00101 { 00102 n.add_string("class", class_name()); 00103 } 00104 00106 // new virtual functions which can be overridden by derived classes 00108 00109 // public 00110 00116 void basic::print(const print_context & c, unsigned level) const 00117 { 00118 print_dispatch(get_class_info(), c, level); 00119 } 00120 00126 void basic::print_dispatch(const registered_class_info & ri, const print_context & c, unsigned level) const 00127 { 00128 // Double dispatch on object type and print_context type 00129 const registered_class_info * reg_info = &ri; 00130 const print_context_class_info * pc_info = &c.get_class_info(); 00131 00132 next_class: 00133 const std::vector<print_functor> & pdt = reg_info->options.get_print_dispatch_table(); 00134 00135 next_context: 00136 unsigned id = pc_info->options.get_id(); 00137 if (id >= pdt.size() || !(pdt[id].is_valid())) { 00138 00139 // Method not found, try parent print_context class 00140 const print_context_class_info * parent_pc_info = pc_info->get_parent(); 00141 if (parent_pc_info) { 00142 pc_info = parent_pc_info; 00143 goto next_context; 00144 } 00145 00146 // Method still not found, try parent class 00147 const registered_class_info * parent_reg_info = reg_info->get_parent(); 00148 if (parent_reg_info) { 00149 reg_info = parent_reg_info; 00150 pc_info = &c.get_class_info(); 00151 goto next_class; 00152 } 00153 00154 // Method still not found. This shouldn't happen because basic (the 00155 // base class of the algebraic hierarchy) registers a method for 00156 // print_context (the base class of the print context hierarchy), 00157 // so if we end up here, there's something wrong with the class 00158 // registry. 00159 throw (std::runtime_error(std::string("basic::print(): method for ") + class_name() + "/" + c.class_name() + " not found")); 00160 00161 } else { 00162 00163 // Call method 00164 pdt[id](*this, c, level); 00165 } 00166 } 00167 00169 void basic::do_print(const print_context & c, unsigned level) const 00170 { 00171 c.s << "[" << class_name() << " object]"; 00172 } 00173 00175 void basic::do_print_tree(const print_tree & c, unsigned level) const 00176 { 00177 c.s << std::string(level, ' ') << class_name() << " @" << this 00178 << std::hex << ", hash=0x" << hashvalue << ", flags=0x" << flags << std::dec; 00179 if (nops()) 00180 c.s << ", nops=" << nops(); 00181 c.s << std::endl; 00182 for (size_t i=0; i<nops(); ++i) 00183 op(i).print(c, level + c.delta_indent); 00184 } 00185 00187 void basic::do_print_python_repr(const print_python_repr & c, unsigned level) const 00188 { 00189 c.s << class_name() << "()"; 00190 } 00191 00199 void basic::dbgprint() const 00200 { 00201 this->print(print_dflt(std::cerr)); 00202 std::cerr << std::endl; 00203 } 00204 00208 void basic::dbgprinttree() const 00209 { 00210 this->print(print_tree(std::cerr)); 00211 } 00212 00214 unsigned basic::precedence() const 00215 { 00216 return 70; 00217 } 00218 00222 bool basic::info(unsigned inf) const 00223 { 00224 // all possible properties are false for basic objects 00225 return false; 00226 } 00227 00229 size_t basic::nops() const 00230 { 00231 // iterating from 0 to nops() on atomic objects should be an empty loop, 00232 // and accessing their elements is a range error. Container objects should 00233 // override this. 00234 return 0; 00235 } 00236 00238 ex basic::op(size_t i) const 00239 { 00240 throw(std::range_error(std::string("basic::op(): ") + class_name() + std::string(" has no operands"))); 00241 } 00242 00244 ex & basic::let_op(size_t i) 00245 { 00246 ensure_if_modifiable(); 00247 throw(std::range_error(std::string("basic::let_op(): ") + class_name() + std::string(" has no operands"))); 00248 } 00249 00250 ex basic::operator[](const ex & index) const 00251 { 00252 if (is_exactly_a<numeric>(index)) 00253 return op(static_cast<size_t>(ex_to<numeric>(index).to_int())); 00254 00255 throw(std::invalid_argument(std::string("non-numeric indices not supported by ") + class_name())); 00256 } 00257 00258 ex basic::operator[](size_t i) const 00259 { 00260 return op(i); 00261 } 00262 00263 ex & basic::operator[](const ex & index) 00264 { 00265 if (is_exactly_a<numeric>(index)) 00266 return let_op(ex_to<numeric>(index).to_int()); 00267 00268 throw(std::invalid_argument(std::string("non-numeric indices not supported by ") + class_name())); 00269 } 00270 00271 ex & basic::operator[](size_t i) 00272 { 00273 return let_op(i); 00274 } 00275 00280 bool basic::has(const ex & pattern, unsigned options) const 00281 { 00282 exmap repl_lst; 00283 if (match(pattern, repl_lst)) 00284 return true; 00285 for (size_t i=0; i<nops(); i++) 00286 if (op(i).has(pattern, options)) 00287 return true; 00288 00289 return false; 00290 } 00291 00294 ex basic::map(map_function & f) const 00295 { 00296 size_t num = nops(); 00297 if (num == 0) 00298 return *this; 00299 00300 basic *copy = NULL; 00301 for (size_t i=0; i<num; i++) { 00302 const ex & o = op(i); 00303 const ex & n = f(o); 00304 if (!are_ex_trivially_equal(o, n)) { 00305 if (copy == NULL) 00306 copy = duplicate(); 00307 copy->let_op(i) = n; 00308 } 00309 } 00310 00311 if (copy) { 00312 copy->setflag(status_flags::dynallocated); 00313 copy->clearflag(status_flags::hash_calculated | status_flags::expanded); 00314 return *copy; 00315 } else 00316 return *this; 00317 } 00318 00320 bool basic::is_polynomial(const ex & var) const 00321 { 00322 return !has(var) || is_equal(ex_to<basic>(var)); 00323 } 00324 00326 int basic::degree(const ex & s) const 00327 { 00328 return is_equal(ex_to<basic>(s)) ? 1 : 0; 00329 } 00330 00332 int basic::ldegree(const ex & s) const 00333 { 00334 return is_equal(ex_to<basic>(s)) ? 1 : 0; 00335 } 00336 00338 ex basic::coeff(const ex & s, int n) const 00339 { 00340 if (is_equal(ex_to<basic>(s))) 00341 return n==1 ? _ex1 : _ex0; 00342 else 00343 return n==0 ? *this : _ex0; 00344 } 00345 00349 ex basic::collect(const ex & s, bool distributed) const 00350 { 00351 ex x; 00352 if (is_a<lst>(s)) { 00353 00354 // List of objects specified 00355 if (s.nops() == 0) 00356 return *this; 00357 if (s.nops() == 1) 00358 return collect(s.op(0)); 00359 00360 else if (distributed) { 00361 00362 x = this->expand(); 00363 if (! is_a<add>(x)) 00364 return x; 00365 const lst& l(ex_to<lst>(s)); 00366 00367 exmap cmap; 00368 cmap[_ex1] = _ex0; 00369 for (const_iterator xi=x.begin(); xi!=x.end(); ++xi) { 00370 ex key = _ex1; 00371 ex pre_coeff = *xi; 00372 for (lst::const_iterator li=l.begin(); li!=l.end(); ++li) { 00373 int cexp = pre_coeff.degree(*li); 00374 pre_coeff = pre_coeff.coeff(*li, cexp); 00375 key *= pow(*li, cexp); 00376 } 00377 exmap::iterator ci = cmap.find(key); 00378 if (ci != cmap.end()) 00379 ci->second += pre_coeff; 00380 else 00381 cmap.insert(exmap::value_type(key, pre_coeff)); 00382 } 00383 00384 exvector resv; 00385 for (exmap::const_iterator mi=cmap.begin(); mi != cmap.end(); ++mi) 00386 resv.push_back((mi->first)*(mi->second)); 00387 return (new add(resv))->setflag(status_flags::dynallocated); 00388 00389 } else { 00390 00391 // Recursive form 00392 x = *this; 00393 size_t n = s.nops() - 1; 00394 while (true) { 00395 x = x.collect(s[n]); 00396 if (n == 0) 00397 break; 00398 n--; 00399 } 00400 } 00401 00402 } else { 00403 00404 // Only one object specified 00405 for (int n=this->ldegree(s); n<=this->degree(s); ++n) 00406 x += this->coeff(s,n)*power(s,n); 00407 } 00408 00409 // correct for lost fractional arguments and return 00410 return x + (*this - x).expand(); 00411 } 00412 00414 ex basic::eval(int level) const 00415 { 00416 // There is nothing to do for basic objects: 00417 return hold(); 00418 } 00419 00421 struct evalf_map_function : public map_function { 00422 int level; 00423 evalf_map_function(int l) : level(l) {} 00424 ex operator()(const ex & e) { return evalf(e, level); } 00425 }; 00426 00428 ex basic::evalf(int level) const 00429 { 00430 if (nops() == 0) 00431 return *this; 00432 else { 00433 if (level == 1) 00434 return *this; 00435 else if (level == -max_recursion_level) 00436 throw(std::runtime_error("max recursion level reached")); 00437 else { 00438 evalf_map_function map_evalf(level - 1); 00439 return map(map_evalf); 00440 } 00441 } 00442 } 00443 00445 struct evalm_map_function : public map_function { 00446 ex operator()(const ex & e) { return evalm(e); } 00447 } map_evalm; 00448 00450 ex basic::evalm() const 00451 { 00452 if (nops() == 0) 00453 return *this; 00454 else 00455 return map(map_evalm); 00456 } 00457 00459 struct eval_integ_map_function : public map_function { 00460 ex operator()(const ex & e) { return eval_integ(e); } 00461 } map_eval_integ; 00462 00464 ex basic::eval_integ() const 00465 { 00466 if (nops() == 0) 00467 return *this; 00468 else 00469 return map(map_eval_integ); 00470 } 00471 00474 ex basic::eval_indexed(const basic & i) const 00475 // this function can't take a "const ex & i" because that would result 00476 // in an infinite eval() loop 00477 { 00478 // There is nothing to do for basic objects 00479 return i.hold(); 00480 } 00481 00490 ex basic::add_indexed(const ex & self, const ex & other) const 00491 { 00492 return self + other; 00493 } 00494 00502 ex basic::scalar_mul_indexed(const ex & self, const numeric & other) const 00503 { 00504 return self * other; 00505 } 00506 00519 bool basic::contract_with(exvector::iterator self, exvector::iterator other, exvector & v) const 00520 { 00521 // Do nothing 00522 return false; 00523 } 00524 00528 bool basic::match(const ex & pattern, exmap& repl_lst) const 00529 { 00530 /* 00531 Sweet sweet shapes, sweet sweet shapes, 00532 That's the key thing, right right. 00533 Feed feed face, feed feed shapes, 00534 But who is the king tonight? 00535 Who is the king tonight? 00536 Pattern is the thing, the key thing-a-ling, 00537 But who is the king of Pattern? 00538 But who is the king, the king thing-a-ling, 00539 Who is the king of Pattern? 00540 Bog is the king, the king thing-a-ling, 00541 Bog is the king of Pattern. 00542 Ba bu-bu-bu-bu bu-bu-bu-bu-bu-bu bu-bu 00543 Bog is the king of Pattern. 00544 */ 00545 00546 if (is_exactly_a<wildcard>(pattern)) { 00547 00548 // Wildcard matches anything, but check whether we already have found 00549 // a match for that wildcard first (if so, the earlier match must be 00550 // the same expression) 00551 for (exmap::const_iterator it = repl_lst.begin(); it != repl_lst.end(); ++it) { 00552 if (it->first.is_equal(pattern)) 00553 return is_equal(ex_to<basic>(it->second)); 00554 } 00555 repl_lst[pattern] = *this; 00556 return true; 00557 00558 } else { 00559 00560 // Expression must be of the same type as the pattern 00561 if (typeid(*this) != typeid(ex_to<basic>(pattern))) 00562 return false; 00563 00564 // Number of subexpressions must match 00565 if (nops() != pattern.nops()) 00566 return false; 00567 00568 // No subexpressions? Then just compare the objects (there can't be 00569 // wildcards in the pattern) 00570 if (nops() == 0) 00571 return is_equal_same_type(ex_to<basic>(pattern)); 00572 00573 // Check whether attributes that are not subexpressions match 00574 if (!match_same_type(ex_to<basic>(pattern))) 00575 return false; 00576 00577 // Even if the expression does not match the pattern, some of 00578 // its subexpressions could match it. For example, x^5*y^(-1) 00579 // does not match the pattern $0^5, but its subexpression x^5 00580 // does. So, save repl_lst in order to not add bogus entries. 00581 exmap tmp_repl = repl_lst; 00582 // Otherwise the subexpressions must match one-to-one 00583 for (size_t i=0; i<nops(); i++) 00584 if (!op(i).match(pattern.op(i), tmp_repl)) 00585 return false; 00586 00587 // Looks similar enough, match found 00588 repl_lst = tmp_repl; 00589 return true; 00590 } 00591 } 00592 00594 ex basic::subs_one_level(const exmap & m, unsigned options) const 00595 { 00596 exmap::const_iterator it; 00597 00598 if (options & subs_options::no_pattern) { 00599 ex thisex = *this; 00600 it = m.find(thisex); 00601 if (it != m.end()) 00602 return it->second; 00603 return thisex; 00604 } else { 00605 for (it = m.begin(); it != m.end(); ++it) { 00606 exmap repl_lst; 00607 if (match(ex_to<basic>(it->first), repl_lst)) 00608 return it->second.subs(repl_lst, options | subs_options::no_pattern); 00609 // avoid infinite recursion when re-substituting the wildcards 00610 } 00611 } 00612 00613 return *this; 00614 } 00615 00618 ex basic::subs(const exmap & m, unsigned options) const 00619 { 00620 size_t num = nops(); 00621 if (num) { 00622 00623 // Substitute in subexpressions 00624 for (size_t i=0; i<num; i++) { 00625 const ex & orig_op = op(i); 00626 const ex & subsed_op = orig_op.subs(m, options); 00627 if (!are_ex_trivially_equal(orig_op, subsed_op)) { 00628 00629 // Something changed, clone the object 00630 basic *copy = duplicate(); 00631 copy->setflag(status_flags::dynallocated); 00632 copy->clearflag(status_flags::hash_calculated | status_flags::expanded); 00633 00634 // Substitute the changed operand 00635 copy->let_op(i++) = subsed_op; 00636 00637 // Substitute the other operands 00638 for (; i<num; i++) 00639 copy->let_op(i) = op(i).subs(m, options); 00640 00641 // Perform substitutions on the new object as a whole 00642 return copy->subs_one_level(m, options); 00643 } 00644 } 00645 } 00646 00647 // Nothing changed or no subexpressions 00648 return subs_one_level(m, options); 00649 } 00650 00658 ex basic::diff(const symbol & s, unsigned nth) const 00659 { 00660 // trivial: zeroth derivative 00661 if (nth==0) 00662 return ex(*this); 00663 00664 // evaluate unevaluated *this before differentiating 00665 if (!(flags & status_flags::evaluated)) 00666 return ex(*this).diff(s, nth); 00667 00668 ex ndiff = this->derivative(s); 00669 while (!ndiff.is_zero() && // stop differentiating zeros 00670 nth>1) { 00671 ndiff = ndiff.diff(s); 00672 --nth; 00673 } 00674 return ndiff; 00675 } 00676 00678 exvector basic::get_free_indices() const 00679 { 00680 return exvector(); // return an empty exvector 00681 } 00682 00683 ex basic::conjugate() const 00684 { 00685 return *this; 00686 } 00687 00688 ex basic::real_part() const 00689 { 00690 return real_part_function(*this).hold(); 00691 } 00692 00693 ex basic::imag_part() const 00694 { 00695 return imag_part_function(*this).hold(); 00696 } 00697 00698 ex basic::eval_ncmul(const exvector & v) const 00699 { 00700 return hold_ncmul(v); 00701 } 00702 00703 // protected 00704 00706 struct derivative_map_function : public map_function { 00707 const symbol &s; 00708 derivative_map_function(const symbol &sym) : s(sym) {} 00709 ex operator()(const ex & e) { return diff(e, s); } 00710 }; 00711 00716 ex basic::derivative(const symbol & s) const 00717 { 00718 if (nops() == 0) 00719 return _ex0; 00720 else { 00721 derivative_map_function map_derivative(s); 00722 return map(map_derivative); 00723 } 00724 } 00725 00731 int basic::compare_same_type(const basic & other) const 00732 { 00733 return compare_pointers(this, &other); 00734 } 00735 00741 bool basic::is_equal_same_type(const basic & other) const 00742 { 00743 return compare_same_type(other)==0; 00744 } 00745 00756 bool basic::match_same_type(const basic & other) const 00757 { 00758 // The default is to only consider subexpressions, but not any other 00759 // attributes 00760 return true; 00761 } 00762 00763 unsigned basic::return_type() const 00764 { 00765 return return_types::commutative; 00766 } 00767 00768 return_type_t basic::return_type_tinfo() const 00769 { 00770 return_type_t rt; 00771 rt.tinfo = &typeid(*this); 00772 rt.rl = 0; 00773 return rt; 00774 } 00775 00782 unsigned basic::calchash() const 00783 { 00784 unsigned v = make_hash_seed(typeid(*this)); 00785 for (size_t i=0; i<nops(); i++) { 00786 v = rotate_left(v); 00787 v ^= this->op(i).gethash(); 00788 } 00789 00790 // store calculated hash value only if object is already evaluated 00791 if (flags & status_flags::evaluated) { 00792 setflag(status_flags::hash_calculated); 00793 hashvalue = v; 00794 } 00795 00796 return v; 00797 } 00798 00800 struct expand_map_function : public map_function { 00801 unsigned options; 00802 expand_map_function(unsigned o) : options(o) {} 00803 ex operator()(const ex & e) { return e.expand(options); } 00804 }; 00805 00808 ex basic::expand(unsigned options) const 00809 { 00810 if (nops() == 0) 00811 return (options == 0) ? setflag(status_flags::expanded) : *this; 00812 else { 00813 expand_map_function map_expand(options); 00814 return ex_to<basic>(map(map_expand)).setflag(options == 0 ? status_flags::expanded : 0); 00815 } 00816 } 00817 00818 00820 // non-virtual functions in this class 00822 00823 // public 00824 00828 int basic::compare(const basic & other) const 00829 { 00830 #ifdef GINAC_COMPARE_STATISTICS 00831 compare_statistics.total_basic_compares++; 00832 #endif 00833 const unsigned hash_this = gethash(); 00834 const unsigned hash_other = other.gethash(); 00835 if (hash_this<hash_other) return -1; 00836 if (hash_this>hash_other) return 1; 00837 #ifdef GINAC_COMPARE_STATISTICS 00838 compare_statistics.compare_same_hashvalue++; 00839 #endif 00840 00841 const std::type_info& typeid_this = typeid(*this); 00842 const std::type_info& typeid_other = typeid(other); 00843 if (typeid_this == typeid_other) { 00844 // int cmpval = compare_same_type(other); 00845 // if (cmpval!=0) { 00846 // std::cout << "hash collision, same type: " 00847 // << *this << " and " << other << std::endl; 00848 // this->print(print_tree(std::cout)); 00849 // std::cout << " and "; 00850 // other.print(print_tree(std::cout)); 00851 // std::cout << std::endl; 00852 // } 00853 // return cmpval; 00854 #ifdef GINAC_COMPARE_STATISTICS 00855 compare_statistics.compare_same_type++; 00856 #endif 00857 return compare_same_type(other); 00858 } else { 00859 // std::cout << "hash collision, different types: " 00860 // << *this << " and " << other << std::endl; 00861 // this->print(print_tree(std::cout)); 00862 // std::cout << " and "; 00863 // other.print(print_tree(std::cout)); 00864 // std::cout << std::endl; 00865 return (typeid_this.before(typeid_other) ? -1 : 1); 00866 } 00867 } 00868 00875 bool basic::is_equal(const basic & other) const 00876 { 00877 #ifdef GINAC_COMPARE_STATISTICS 00878 compare_statistics.total_basic_is_equals++; 00879 #endif 00880 if (this->gethash()!=other.gethash()) 00881 return false; 00882 #ifdef GINAC_COMPARE_STATISTICS 00883 compare_statistics.is_equal_same_hashvalue++; 00884 #endif 00885 if (typeid(*this) != typeid(other)) 00886 return false; 00887 00888 #ifdef GINAC_COMPARE_STATISTICS 00889 compare_statistics.is_equal_same_type++; 00890 #endif 00891 return is_equal_same_type(other); 00892 } 00893 00894 // protected 00895 00899 const basic & basic::hold() const 00900 { 00901 return setflag(status_flags::evaluated); 00902 } 00903 00906 void basic::ensure_if_modifiable() const 00907 { 00908 if (get_refcount() > 1) 00909 throw(std::runtime_error("cannot modify multiply referenced object")); 00910 clearflag(status_flags::hash_calculated | status_flags::evaluated); 00911 } 00912 00914 // global variables 00916 00917 int max_recursion_level = 1024; 00918 00919 00920 #ifdef GINAC_COMPARE_STATISTICS 00921 compare_statistics_t::~compare_statistics_t() 00922 { 00923 std::clog << "ex::compare() called " << total_compares << " times" << std::endl; 00924 std::clog << "nontrivial compares: " << nontrivial_compares << " times" << std::endl; 00925 std::clog << "basic::compare() called " << total_basic_compares << " times" << std::endl; 00926 std::clog << "same hashvalue in compare(): " << compare_same_hashvalue << " times" << std::endl; 00927 std::clog << "compare_same_type() called " << compare_same_type << " times" << std::endl; 00928 std::clog << std::endl; 00929 std::clog << "ex::is_equal() called " << total_is_equals << " times" << std::endl; 00930 std::clog << "nontrivial is_equals: " << nontrivial_is_equals << " times" << std::endl; 00931 std::clog << "basic::is_equal() called " << total_basic_is_equals << " times" << std::endl; 00932 std::clog << "same hashvalue in is_equal(): " << is_equal_same_hashvalue << " times" << std::endl; 00933 std::clog << "is_equal_same_type() called " << is_equal_same_type << " times" << std::endl; 00934 std::clog << std::endl; 00935 std::clog << "basic::gethash() called " << total_gethash << " times" << std::endl; 00936 std::clog << "used cached hashvalue " << gethash_cached << " times" << std::endl; 00937 } 00938 00939 compare_statistics_t compare_statistics; 00940 #endif 00941 00942 } // namespace GiNaC