Hi, here now a small code example to make clear what I was taking about when I mentioned class-functions. A log10 function is declared as an example. The fclass (-> function) class basically installs some default printing and has some convenience ctors. I know there are some issues about the macros (fixed archiving defaults, naming, ...), some print stuff is missing in fclass, and there is no special code for log10 (series, ...) yet, but the main concept should become clear. Eval: - compatibility is severly broken (but maybe we should break it anyhow: why not rename tgamma to Gamma, beta to Beta, lgamma to logGamma?) + logic like if (is_a<function>(e)) { std::string name = ex_to<function>(e).get_name(); if (name == "H") { ... can be replaced by more efficient code + hierarchies like polylog->nielsen_polylog->multiple_polylog possible + function.pl is no longer needed + eval/evalf slightly faster (?) + non-ex arguments for ctors (and eval, ...) possible + extra methods/logic/data can be included in the function class (projects like nestedsums, xloops suffer from not being able to do this at the moment) + more OOP like (disputable, but we will never hear the often raised question 'why didn't you just define the functions as classes?' again + better (OOP-like) handling of look-up tables (as static data or maybe the function might become a singleton) - no nice separation of cln and GiNaC code possible + print-Methods can also be changed at runtime for functions - archiving slightly slower because of longer reg_info list Regards, Jens #include <iostream> #include <fstream> #include <stdexcept> #include <ginac/ginac.h> using namespace std; using namespace GiNaC; namespace GiNaC { /** Primary macro for inclusion in the declaration of each registered class. */ #define NEW_GINAC_DECLARE_REGISTERED_CLASS_NO_CTORS(classname, supername) \ public: \ typedef supername inherited; \ private: \ static GiNaC::registered_class_info reg_info; \ public: \ static GiNaC::registered_class_info &get_class_info_static() { return reg_info; } \ virtual const GiNaC::registered_class_info &get_class_info() const { return classname::get_class_info_static(); } \ virtual GiNaC::registered_class_info &get_class_info() { return classname::get_class_info_static(); } \ virtual const char *class_name() const { return classname::get_class_info_static().options.get_name(); } \ \ classname(const GiNaC::archive_node &n, GiNaC::lst &sym_lst) : inherited(n, sym_lst) {} ; \ static GiNaC::ex unarchive(const GiNaC::archive_node &n, GiNaC::lst &sym_lst) \ { return (new classname(n, sym_lst))->setflag(status_flags::dynallocated); }; \ class visitor { \ public: \ virtual void visit(const classname &) = 0; \ virtual ~visitor() {}; \ }; \ private: /** Macro for inclusion in the declaration of each registered class. * It declares some functions that are common to all classes derived * from 'basic' as well as all required stuff for the GiNaC class * registry (mainly needed for archiving). */ #define NEW_GINAC_DECLARE_REGISTERED_CLASS(classname, supername) \ NEW_GINAC_DECLARE_REGISTERED_CLASS_NO_CTORS(classname, supername) \ public: \ classname() { tinfo_key = TINFO_##classname; } \ virtual classname * duplicate() const { return new classname(*this); } \ \ virtual void accept(GiNaC::visitor & v) const \ { \ if (visitor *p = dynamic_cast<visitor *>(&v)) \ p->visit(*this); \ else \ inherited::accept(v); \ } \ private: //////////////////////////////////////////////////////////////////////////////// const unsigned TINFO_fclass = 0x47231232U; class fclass : public exprseq { NEW_GINAC_DECLARE_REGISTERED_CLASS(fclass, exprseq) public: fclass(unsigned ti, const ex& x1) : inherited(x1) { tinfo_key = ti; } fclass(unsigned ti, const ex& x1, const ex& x2) : inherited(x1,x2) { tinfo_key = ti; } fclass(unsigned ti, const ex& x1, const ex& x2, const ex& x3) : inherited(x1,x2,x3) { tinfo_key = ti; } protected: void fclass::do_print(const print_context &c, unsigned level) const { c.s << class_name(); inherited::do_print(c,level); } }; GINAC_IMPLEMENT_REGISTERED_CLASS_OPT(fclass, exprseq, print_func<print_context>(&fclass::do_print)) //////////////////////////////////////////////////////////////////////////////// const unsigned TINFO_log10 = 0x47321232U; class log10 : public fclass { NEW_GINAC_DECLARE_REGISTERED_CLASS(log10, fclass) public: log10(const ex& x) : inherited(TINFO_log10, x) {} log10(const ex& x, const ex& y) : inherited(TINFO_log10, x, y) {} }; GINAC_IMPLEMENT_REGISTERED_CLASS(log10, fclass) //////////////////////////////////////////////////////////////////////////////// } main() { try { symbol x("x"); ex d = x; ex r1 = log10(d) * log10(x) + x; cout << r1 << endl; cout << tree << r1 << endl; cout << dflt << log10(lst(x,x),lst(x)) << endl; } catch (const exception& e) { cout << e.what() << endl; } return 0; }
Hi Jens! Jens Vollinga wrote:
here now a small code example to make clear what I was taking about when I mentioned class-functions. A log10 function is declared as an example. The fclass (-> function) class basically installs some default printing and has some convenience ctors. I know there are some issues about the macros (fixed archiving defaults, naming, ...), some print stuff is missing in fclass, and there is no special code for log10 (series, ...) yet, but the main concept should become clear.
Eval: - compatibility is severly broken (but maybe we should break it anyhow: why not rename tgamma to Gamma, beta to Beta, lgamma to logGamma?) + logic like if (is_a<function>(e)) { std::string name = ex_to<function>(e).get_name(); if (name == "H") { ... can be replaced by more efficient code + hierarchies like polylog->nielsen_polylog->multiple_polylog possible + function.pl is no longer needed + eval/evalf slightly faster (?) + non-ex arguments for ctors (and eval, ...) possible + extra methods/logic/data can be included in the function class (projects like nestedsums, xloops suffer from not being able to do this at the moment) + more OOP like (disputable, but we will never hear the often raised question 'why didn't you just define the functions as classes?' again + better (OOP-like) handling of look-up tables (as static data or maybe the function might become a singleton) - no nice separation of cln and GiNaC code possible + print-Methods can also be changed at runtime for functions - archiving slightly slower because of longer reg_info list
Basically, I begin to appreciate (well, again) the idea of having symbolic functions as objects of different classes, one for each mathematical function. But the idea of _not_ being able to write sin(something), when I want a sine of something is not very appealing, I think. What about this alternative: Name the classes sin_t, log_t, tgamma_t (or some other uniform scheme) and provide functions sin, log, tgamma that return objects of these classes. The advantages are that people a) don't have to rewrite too much code and b) we don't bother our users with yet another convention for naming mathematical functions. Does that sound reasonable? Though, I totally agree with your earlier sentiment that in the general case we shouldn't worry too much about name clashes and instead we should put the burden of disambiguation on the user. But in special cases where there are conflicts with functions of built-in integral type, I think it is just fine to disambiguate in favor of the GiNaC functions by adding an explicit signature. This way, sin(1) stays what it is: sin(1) (well, sin_t(1) or function(42,1) or whatever) and does not become 0.84147... I remember that when we first hit the problem of what sin(1) is supposed to be, I had tons of explicit overloads but quickly found that the template was a more clever idea. Well, if it clashes again because GCC's cmath has become just as clever, good for GCC and maybe bad for us. Going back to some (not all!) explicit overloads sounds elegant, if it solves the problems at hand, doesn't it? Cheers -richy. -- Richard B. Kreckel <http://www.ginac.de/~kreckel/>
Hi, Richard B. Kreckel wrote:
What about this alternative:
Name the classes sin_t, log_t, tgamma_t (or some other uniform scheme) and provide functions sin, log, tgamma that return objects of these classes. The advantages are that people a) don't have to rewrite too much code and b) we don't bother our users with yet another convention for naming mathematical functions. Does that sound reasonable?
Though, I totally agree with your earlier sentiment that in the general case we shouldn't worry too much about name clashes and instead we should put the burden of disambiguation on the user. But in special cases where there are conflicts with functions of built-in integral type, I think it is just fine to disambiguate in favor of the GiNaC functions by adding an explicit signature. This way, sin(1) stays what it is: sin(1) (well, sin_t(1) or function(42,1) or whatever) and does not become 0.84147...
sorry, I don't fully understand your proposal, yet. Why should the user have to learn another convention (as stated above under b)) if all the classes have the same name as the current functions? Why is it fine to disambiguate just in case of integral types (see question below)? sin(1) doesn't disambiguate currently when one includes cmath!! Does it really make sense to have sin(1) or zeta(2) evaled on creation (by means of a special function)? I remember having to 'tweak' zeta a little bit to not throw an exception when the argument is 1. And with the polylogs I often have to use a lot of .holds() just because I don't want the automatic evaluation in intermediate expressions. And isn't there an issue with 1/tgamma(-n) ...? Maybe a solution to this is easier without the automatic evaluation on creation. To have automatic evaluation just on assignment might (I am not sure though, please teach me!) be more clear, powerful, ... Regards, Jens
Hi Jens, Jens Vollinga wrote:
Why should the user have to learn another convention (as stated above under b)) if all the classes have the same name as the current functions?
Oh, you aren't proposing logGamma, etc. any more? Good. (And, please excuse my misunderstanding you.)
Why is it fine to disambiguate just in case of integral types (see question below)? sin(1) doesn't disambiguate currently when one includes cmath!!
Well, I was just suggesting to disambiguate the common cases like ex sin(int) in order to reduce the number of surprises. What else could one override? ex sin(double)? No! That would really conflict with cmath's declaration. (Note that CLN has sin(own types), as have many other such libraries).
Does it really make sense to have sin(1) or zeta(2) evaled on creation (by means of a special function)? I remember having to 'tweak' zeta a little bit to not throw an exception when the argument is 1.
You mean: to actually _throw_ an exception, I suppose?
And with the polylogs I often have to use a lot of .holds() just because I don't want the automatic evaluation in intermediate expressions. And isn't there an issue with 1/tgamma(-n) ...?
What issue? Limits are quite another story, aren't they?
Maybe a solution to this is easier without the automatic evaluation on creation. To have automatic evaluation just on assignment might (I am not sure though, please teach me!) be more clear, powerful, ...
I don't understand: Each class has two evaluations: ctor and eval. The ctor cannot do everything because it is contrained to a specific class. The eval member functions, in contrast, have more leeway with the general ex they return. The intent was to do as much term rewriting as possible in the ctors, and do as much term rewriting as reasonable in the eval member functions. Maybe that is questionable, I don't know. However, all this doesn't hold for our functions, does it? We have sin(ex) invoking the ctor function::function(unsigned ser, const ex & param1) which doesn't do anything intersting. (This also accounts for the behavior described in <http://www.ginac.de/FAQ.html#evaluation>.) Cheers -richy. -- Richard B. Kreckel <http://www.ginac.de/~kreckel/>
Hi, Richard B. Kreckel wrote:
Oh, you aren't proposing logGamma, etc. any more? Good. (And, please excuse my misunderstanding you.)
Well, I wouldn't be unhappy if tgamma would be renamed to gamma, but I don't care too much. So, I don't propose it (anymore).
Well, I was just suggesting to disambiguate the common cases like ex sin(int) in order to reduce the number of surprises. What else could one override? ex sin(double)? No! That would really conflict with cmath's declaration. (Note that CLN has sin(own types), as have many other such libraries).
Just to understand it: This disambiguation is not in GiNaC right now, but you propose it to be done like in your email posting from 2001? This probably got me confused and therefore I mentioned sin(1) etc...
Does it really make sense to have sin(1) or zeta(2) evaled on creation (by means of a special function)? I remember having to 'tweak' zeta a little bit to not throw an exception when the argument is 1.
You mean: to actually _throw_ an exception, I suppose?
Forget about the eval on creation stuff I wrote. I got confused there, too. But, to NOT throw an exception in the case of arg=1 was a correct statement.
expressions. And isn't there an issue with 1/tgamma(-n) ...?
What issue? Limits are quite another story, aren't they?
No, I meant 1/tgamma(-2) for example. Should be zero, I guess. But GiNaC doesn't like it and throws up ...
I don't understand: Each class has two evaluations: ctor and eval. The ctor cannot do everything because it is contrained to a specific class. The eval member functions, in contrast, have more leeway with the general ex they return. The intent was to do as much term rewriting as possible in the ctors, and do as much term rewriting as reasonable in the eval member functions. Maybe that is questionable, I don't know. However, all this doesn't hold for our functions, does it? We have sin(ex) invoking the ctor function::function(unsigned ser, const ex & param1) which doesn't do anything intersting. (This also accounts for the behavior described in <http://www.ginac.de/FAQ.html#evaluation>.)
As stated above, I got a little confused %^) The new class-functions would not change anything here, or would they? So the point we are arguing about it whether more specializations in order to prevent ambiguities should be added? Regards, Jens
Hi! Jens Vollinga wrote:
Well, I wouldn't be unhappy if tgamma would be renamed to gamma, but I don't care too much. So, I don't propose it (anymore).
Whatever. The name was chosen in anticipation of a future revision of the C++ standard. All we had back then was the C99 standard which named the two functions tgamma and lgamma. It appeard natural to assume that C++0x would eventually follow. Also gamma(x) was deemed to be a conflict with the derivatives of Riemann's zeta function.
Well, I was just suggesting to disambiguate the common cases like ex sin(int) in order to reduce the number of surprises. What else could one override? ex sin(double)? No! That would really conflict with cmath's declaration. (Note that CLN has sin(own types), as have many other such libraries).
Just to understand it: This disambiguation is not in GiNaC right now, but you propose it to be done like in your email posting from 2001? This probably got me confused and therefore I mentioned sin(1) etc...
All I propose is to add a few additional signatures for ex GiNaC::sin(<built-in integral-type>) such that it is preferred over the definition in the <cmath> header file when there would otherwise be a conflict. I suppose this is independent of whether the thing is a true ctor or a helper function returning an object representing a function in a symbolic way. (E.g. sin(ex) returning a sin_t object, maybe.)
Does it really make sense to have sin(1) or zeta(2) evaled on creation (by means of a special function)? I remember having to 'tweak' zeta a little bit to not throw an exception when the argument is 1.
You mean: to actually _throw_ an exception, I suppose?
Forget about the eval on creation stuff I wrote. I got confused there, too. But, to NOT throw an exception in the case of arg=1 was a correct statement.
But why? The zeta function has a simple pole at x==1 that goes like 1/x. With the same reasoning we should get rid of the exception thrown by tan(Pi/2) and all other pole_errors. Now, series((x-1)*zeta(x),x==1,2) returns zeta(1)*(x-1)+Order((x-1)^2). This is less helpful than 1+Order((x-1)^2).
expressions. And isn't there an issue with 1/tgamma(-n) ...?
What issue? Limits are quite another story, aren't they?
No, I meant 1/tgamma(-2) for example. Should be zero, I guess. But GiNaC doesn't like it and throws up ...
But this is precisely the issue of limits! Why should it be zero? After all, there is a simple pole in the denomiator. It makes perfect sense to stuff it into a Laurent series expansion, though. Indeed, series(1/tgamma(eps-2),eps==0,1) correctly returns Order(eps). If we had a limit command, then limit(1/tgamma(x),x==-2) should return 0, all right. Until then, Laurent series expansion must hold as a substitute for limits. Whether the approach to base the series expansion on limits is more fruitful than the other way round, I don't know. I think both ways work. If we want to deal with the Riemann sphere instead of just the complex numbers, then 1/tgamma(-2) should indeed be zero. But then we would have to add ComplexInfinity to our constants and make the corresponding adjustments to add, mul, etc. in order to avoid errors that would follow from rewriting ComplexInfinity-ComplexInfinity as 0. That is certainly possible.
I don't understand: Each class has two evaluations: ctor and eval. The ctor cannot do everything because it is contrained to a specific class. The eval member functions, in contrast, have more leeway with the general ex they return. The intent was to do as much term rewriting as possible in the ctors, and do as much term rewriting as reasonable in the eval member functions. Maybe that is questionable, I don't know. However, all this doesn't hold for our functions, does it? We have sin(ex) invoking the ctor function::function(unsigned ser, const ex & param1) which doesn't do anything intersting. (This also accounts for the behavior described in <http://www.ginac.de/FAQ.html#evaluation>.)
As stated above, I got a little confused %^) The new class-functions would not change anything here, or would they?
Right. Cheers -richy. -- Richard B. Kreckel <http://www.ginac.de/~kreckel/>
participants (2)
-
Jens Vollinga
-
Richard B. Kreckel