I use ginac 1.3.4. I believe there is a bug in the implementation of evalm(). Its behaviour should be to do nothing if it is not applicable to the ex it is called on, right? The minimal example below throws an exception: #include <ginac/ginac.h> using namespace std; using namespace GiNaC; int main(int argc, char **argv) { ex x = symbol("x"); ex e = sin(x); ex s = e.series(x == 0, 3); cout << "This will crash:" << endl; ex m = s.evalm(); return 0; } The error is: terminate called after throwing an instance of 'std::range_error' what(): basic::let_op(): pseries has no operands Aborted (core dumped) The call stack from gdb is: (gdb) where #0 0xffffe410 in __kernel_vsyscall () #1 0xb7b12770 in raise () from /lib/tls/i686/cmov/libc.so.6 #2 0xb7b13ef3 in abort () from /lib/tls/i686/cmov/libc.so.6 #3 0xb7d02520 in __gnu_cxx::__verbose_terminate_handler () from /usr/lib/libstdc++.so.6 #4 0xb7cfff55 in std::set_unexpected () from /usr/lib/libstdc++.so.6 #5 0xb7cfff92 in std::terminate () from /usr/lib/libstdc++.so.6 #6 0xb7d000ca in __cxa_throw () from /usr/lib/libstdc++.so.6 #7 0xb7d9a4a6 in GiNaC::basic::let_op (this=0x80597d8, i=1) at basic.cpp:256 #8 0xb7d96dfd in GiNaC::basic::map (this=0x80595e0, f=@0xb7f8e824) at basic.cpp:316 #9 0xb7d962a9 in GiNaC::basic::evalm (this=0x80595e0) at basic.cpp:482 #10 0x0804b47a in GiNaC::ex::evalm (this=0xbfd820d4) at /usr/include/ginac/ex.h:118 #11 0x0804b214 in main () at test.cpp:12 Of course, it doesn't make much sense to call evalm() on this expression, but it is a problem in an application where we want to call evalm() "just in case" on all ex objects that pass a certain point in the application. Thus a "do nothing" behaviour for evalm() is of great importance. martin
Hello! On Fri, Feb 09, 2007 at 05:39:50PM +0100, Martin Sandve Alnæs wrote:
I believe there is a bug in the implementation of evalm(). Its behaviour should be to do nothing if it is not applicable to the ex it is called on, right?
As of now, eval* methods do behave this way (well, mostly). [I'd prefer them to throw an exception instead, so bugs in code become evident earlier].
The minimal example below throws an exception:
#include <ginac/ginac.h>
using namespace std; using namespace GiNaC;
int main(int argc, char **argv) { ex x = symbol("x"); ex e = sin(x); ex s = e.series(x == 0, 3); cout << "This will crash:" << endl;
Not exactly. This is an uncaught exception.
ex m = s.evalm(); return 0; }
The error is:
terminate called after throwing an instance of 'std::range_error' what(): basic::let_op(): pseries has no operands Aborted (core dumped)
This bug is unlikely to be fixed in GiNaC 1.3.X :( There are two possible work-arounds: 1) catch std::range_error exception, e.g. ex m; try { m = s.evalm(); } catch (std::range_error& oops) { m = s; } This is not exactly correct (one might got std::range_error for some "real" reason)... 2) convert the expression from power series to polynomial: ex m = series_to_poly(s).evalm(); This one is not exaclty fool-proof: it will fail if s is not a power series object...
Of course, it doesn't make much sense to call evalm() on this expression, but it is a problem in an application where we want to call evalm() "just in case" on all ex objects that pass a certain point in the application.
I'd say that such an "just in a case" code is buggy and/or very inefficient in first place.
Thus a "do nothing" behaviour for evalm() is of great importance.
I agree -- at least it should be consistent with behaviour of other eval* methods. @ developers The `pseries' class does not overload evalm(). So default basic::evalm() is used, which needs let_op() or map(). But these are not overloaded too, probably because semantics of such operations is not very clear. Thus default basic::let_op() is used [which throws an exception]... Here is a dumb fix: [PATCH] pseries.{h, cpp}: provide no-op pseries::evalm() Code like this: symbol x; ex test = sin(x).series(x, 5).evalm(); throws an exception. I'd say this is good, because it urges users to fix their sloppy code. But this behaviour is inconsistent with one of other eval* methods which are no-op if operation in question is meaningless. Unfortunately this change is not suitable for GiNaC 1.3 due to the A[PB]I breakage. --- ginac/pseries.cpp | 5 +++++ ginac/pseries.h | 1 + 2 files changed, 6 insertions(+), 0 deletions(-) diff --git a/ginac/pseries.cpp b/ginac/pseries.cpp index db7cef3..a6780a6 100644 --- a/ginac/pseries.cpp +++ b/ginac/pseries.cpp @@ -464,6 +464,11 @@ ex pseries::imag_part() const return (new pseries(var==point, v))->setflag(status_flags::dynallocated); } +ex pseries::evalm() const +{ + return *this; +} + ex pseries::eval_integ() const { epvector *newseq = NULL; diff --git a/ginac/pseries.h b/ginac/pseries.h index ed266c4..e53baf6 100644 --- a/ginac/pseries.h +++ b/ginac/pseries.h @@ -59,6 +59,7 @@ public: ex real_part() const; ex imag_part() const; ex eval_integ() const; + ex evalm() const; protected: ex derivative(const symbol & s) const; -- 1.4.4.4 Best regards, Alexei -- All science is either physics or stamp collecting.
On 2/9/07, Sheplyakov Alexei <varg@theor.jinr.ru> wrote:
On Fri, Feb 09, 2007 at 05:39:50PM +0100, Martin Sandve Alnæs wrote: This bug is unlikely to be fixed in GiNaC 1.3.X :( There are two possible work-arounds:
1) catch std::range_error exception, e.g.
ex m; try { m = s.evalm(); } catch (std::range_error& oops) { m = s; }
This is not exactly correct (one might got std::range_error for some "real" reason)...
... which is why I can't do this, since it is in a very general part of the code (see below).
2) convert the expression from power series to polynomial:
ex m = series_to_poly(s).evalm();
This one is not exaclty fool-proof: it will fail if s is not a power series object...
... which is not acceptable either.
Of course, it doesn't make much sense to call evalm() on this expression, but it is a problem in an application where we want to call evalm() "just in case" on all ex objects that pass a certain point in the application.
I'd say that such an "just in a case" code is buggy and/or very inefficient in first place.
Maybe I should explain my goal. This is for the swiginac python bindings, in output typemaps. Swiginac unwraps the underlying ginac object from the ex when returning an ex to python. This is necessary to make type specific functions like matrix::transpose() available in python. But when using operators (+, * etc) on matrix objects in python, this code will fail: a = matrix1 + matrix2 b = a.transpose() Since a will be an add object, not a matrix. We don't want the user to have to call evalm() everywhere, since it destroys the high level notation we strive to reach. To fix this, we need to always call evalm() on the ex output argument before determining the underlying type. If we can't do that, we can't use the operators, which will also destroy the high level notation. Maybe there's another way around this, but I don't see it right now.
Thus a "do nothing" behaviour for evalm() is of great importance.
I agree -- at least it should be consistent with behaviour of other eval* methods.
Thanks for the patch. But it might be problematic to require the users to have a patched or development version of ginac, so I'll have to discuss it with other developers. If this fix can't be part of ginac 1.3.x, when will 1.4 become the stable branch? martin
Dear Martin, On Sun, 11 Feb 2007, [ISO-8859-1] Martin Sandve Aln�s wrote:
ex m = series_to_poly(s).evalm();
Can't you simply call your own C++-written tree transversal algorithm at this point to check whether there actually are matrix products somewhere and then only call evalm to evaluate them? Best wishes, Chris
On 2/12/07, Chris Dams <Chris.Dams@mi.infn.it> wrote:
Dear Martin,
On Sun, 11 Feb 2007, [ISO-8859-1] Martin Sandve Alnæs wrote:
ex m = series_to_poly(s).evalm();
Can't you simply call your own C++-written tree transversal algorithm at this point to check whether there actually are matrix products somewhere and then only call evalm to evaluate them?
Hi Chris, that's a good idea. I'll try that. Otherwise, I think I can live with a try-catch, and use the original expression if evalm() fails. Thanks for the ideas everyone, I'll let you know when I've worked it out. martin
Dear Alexei and others, On Fri, 9 Feb 2007, Sheplyakov Alexei wrote:
ex test = sin(x).series(x, 5).evalm();
throws an exception. I'd say this is good, because it urges users to fix their sloppy code. But this behaviour is inconsistent with one of other eval* methods which are no-op if operation in question is meaningless. Unfortunately this change is not suitable for GiNaC 1.3 due to the A[PB]I breakage.
What about the following code? matrix m(2,2); m = exp(x), 0, 0, sin(x); ex f = m*m; f = f.series(x,2); f = evalm(f); I'd say that it would be best to make .map() also work with power series and to make it apply the map function to the coefficients of the power series. In that case I would also like to have pseries::eval check for zero coefficients and not store these. Any objections againts this idea? Best wishes, Chris
Hi, On Mon, Feb 12, 2007 at 10:01:14AM +0100, Chris Dams wrote:
What about the following code?
matrix m(2,2); m = exp(x), 0, 0, sin(x); ex f = m*m; f = f.series(x,2); f = evalm(f);
For series of matrix-valued functions my patch is obviously incorrect.
I'd say that it would be best to make .map() also work with power series and to make it apply the map function to the coefficients of the power series. In that case I would also like to have pseries::eval check for zero coefficients and not store these. Any objections againts this idea?
First of all, this violates principle of least surprise. For all other classes map() operates on each term, why pseries should be different? Secondly, when operating on coefficients one often needs to know corresponding degrees too. How do we provide access to degrees? Finally, a map function could transform terms in such a way that result is not a power series any more, e.g. a*x^n => a*exp(exp(...(exp(x)))) /* n times */. What then? Best regards, Alexei -- All science is either physics or stamp collecting.
Dear Alexei and others, On Mon, 12 Feb 2007, Sheplyakov Alexei wrote:
First of all, this violates principle of least surprise. For all other classes map() operates on each term, why pseries should be different?
Okay, then I think it is best to create a method pseries::evalm and have evalm act on every coefficient in the power series. The variable and expansion piont shoulnt' be matrix value anyway, as far as I know. Any objections against that? Best wishes, Chris
after all the difference between a expansion series and a polynomial is basically that the series contains, at the end, some information about the order of the error (i.e. O(x^n) ) In general, the error term, can not be evaluated to anything meaningful (for x!=0) (but have some properties, for example can be derived respect to x, or that O(x) - O(x) = O(x) -hey, just for curiosity: can something like this be possibly ever implemented?-). Because of the existence of the error term, in my opinion evalm must either fail or evaluate to something that holds the error term in some kind of new type (e.g. error_order type). If to fail (i.e. throw) is the choice, the 'principle of less surprise' can be honored by raising a meaningful exception (with some explanation) I must admit that I have almost no experience with ginac and that I don't fully understand the philosophy of the current developers, but, in my opinion, making every method available (or evaluable) for every type is not the way to solve this kind of problems because in many cases the possible returns could be ambiguous depending on the interpretation and this is more dangerous than throwing exception and tell the user to be more specific in the syntax. Regards, Alfredo On 2/12/07, Chris Dams <Chris.Dams@mi.infn.it> wrote:
Dear Alexei and others,
On Mon, 12 Feb 2007, Sheplyakov Alexei wrote:
First of all, this violates principle of least surprise. For all other classes map() operates on each term, why pseries should be different?
Okay, then I think it is best to create a method pseries::evalm and have evalm act on every coefficient in the power series. The variable and expansion piont shoulnt' be matrix value anyway, as far as I know. Any objections against that?
Best wishes, Chris
_______________________________________________ GiNaC-list mailing list GiNaC-list@ginac.de https://www.cebix.net/mailman/listinfo/ginac-list
Hi! On Mon, Feb 12, 2007 at 03:04:29AM -0800, Alfredo Correa wrote:
after all the difference between a expansion series and a polynomial is basically that the series contains, at the end, some information about the order of the error (i.e. O(x^n) )
In general, the error term, can not be evaluated to anything meaningful (for x!=0) (but have some properties, for example can be derived respect to x, or that O(x) - O(x) = O(x) -hey, just for curiosity: can something like this be possibly ever implemented?-).
Because of the existence of the error term, in my opinion evalm must either fail
Then valid code (like Chris posted) will fail...
or evaluate to something that holds the error term in some kind of new type (e.g. error_order type).
...so probably evalm() for power series will work this way.
If to fail (i.e. throw) is the choice, the 'principle of less surprise' can be honored by raising a meaningful exception (with some explanation)
I must admit that I have almost no experience with ginac and that I don't fully understand the philosophy of the current developers, but, in my opinion, making every method available (or evaluable) for every type is not the way to solve this kind of problems because in many cases the possible returns could be ambiguous depending on the interpretation and this is more dangerous than throwing exception and tell the user to be more specific in the syntax.
I agree with you completely: it's much better to not provide evalm() for anything but matrix (frankly, I think all those eval* things are plain evil). But with current [sloppy] type system such kind of interface is hardly ever possible. :( So, here is a second attempt: [PATCH] Declare and implement pseries::evalm() which evalm()s each coefficient. [in ginsh] evalm(series(sin(x), x, 2)); basic::let_op(): pseries has no operands Since let_op() and map() semantics for a power series is not clear, implement pseries::evalm() which evalm()s each coefficient. This makes evalm() behaviour consistent with other eval* methods (if operation is not applicable it should be no-op) and does some real job for matrix-valued series. --- ginac/pseries.cpp | 24 ++++++++++++++++++++++++ ginac/pseries.h | 1 + 2 files changed, 25 insertions(+), 0 deletions(-) diff --git a/ginac/pseries.cpp b/ginac/pseries.cpp index db7cef3..3b3ed51 100644 --- a/ginac/pseries.cpp +++ b/ginac/pseries.cpp @@ -489,6 +489,30 @@ ex pseries::eval_integ() const return *this; } +ex pseries::evalm() const +{ + // evalm each coefficient + epvector newseq; + bool something_changed = false; + for (epvector::const_iterator i=seq.begin(); i!=seq.end(); ++i) { + if (something_changed) + newseq.push_back(expair(i->rest.evalm(), i->coeff)); + else { + ex newcoeff = i->rest.evalm(); + if (!are_ex_trivially_equal(newcoeff, i->rest)) { + something_changed = true; + newseq.reserve(seq.size()); + std::copy(seq.begin(), i, std::back_inserter<epvector>(newseq)); + newseq.push_back(expair(newcoeff, i->coeff)); + } + } + } + if (something_changed) + return (new pseries(var==point, newseq))->setflag(status_flags::dynallocated); + else + return *this; +} + ex pseries::subs(const exmap & m, unsigned options) const { // If expansion variable is being substituted, convert the series to a diff --git a/ginac/pseries.h b/ginac/pseries.h index ed266c4..e53baf6 100644 --- a/ginac/pseries.h +++ b/ginac/pseries.h @@ -59,6 +59,7 @@ public: ex real_part() const; ex imag_part() const; ex eval_integ() const; + ex evalm() const; protected: ex derivative(const symbol & s) const; -- 1.4.4.4 Best regards, Alexei -- All science is either physics or stamp collecting.
Dear Alexei, On Mon, 12 Feb 2007, Sheplyakov Alexei wrote:
So, here is a second attempt:
[PATCH] Declare and implement pseries::evalm() which evalm()s each coefficient.
I committed this patch now after adding a check that the coefficients aren't zero. We don't want to have zero as a coefficient in a power series. Best wishes, Chris
I agree with you completely: it's much better to not provide evalm() for anything but matrix (frankly, I think all those eval* things are plain evil). But with current [sloppy] type system such kind of interface is hardly ever possible. :(
Hi, this is the reason why GiNaC is using ex - so that it automatically calls eval(). We had this discussion already - it's because of C++, memory handling, speed and automatic evaluation. But in Python I recently found a very elegant solution to this (in SymPy): I also have eval() but it's called automatically right after the construtor and if it returns a different instance, then that instance is returned. Example: e = Add(x,x) however "e" contains Mul(2,x), because in python you can make the Add.eval() get called right after the Add constructor. Thus Add.eval() evaluates x+x to 2*x and returns Mul(2,x). So in the end Add(x,x) is in fact the same as Mul(2,x). And in the end, you don't have to think about eval() at all. And you don't need ex (python is handling the garbage collection automatically). But soon I would like to implement the core of SymPy in C++ for speed and I am curious, if my system is going to be faster or slower than GiNaC. my question is this: do you know, if it is possible to implement the above procedure in C++? and how? maybe using some macros? Because it is certainly non standard, that you construct an instance of some class Add(x,x) and it returns an instance of a completely different class Mul(2,x). Thanks, Ondrej
my question is this: do you know, if it is possible to implement the above procedure in C++? and how? maybe using some macros? Because it is certainly non standard, that you construct an instance of some class Add(x,x) and it returns an instance of a completely different class Mul(2,x).
Well, one solution is to define all the C++ classes as Add_, Mul_, etc. and then have a macro (or a function) Add, Mul, that will construct an instance of Add_ and then return Add_.eval(). But it's not very elegant.... Ondrej
Hi Ondrej! Ondrej Certik wrote:
Hi, this is the reason why GiNaC is using ex - so that it automatically calls eval(). We had this discussion already - it's because of C++, memory handling, speed and automatic evaluation.
But in Python I recently found a very elegant solution to this (in SymPy): I also have eval() but it's called automatically right after the construtor and if it returns a different instance, then that instance is returned. Example:
e = Add(x,x)
however "e" contains Mul(2,x), because in python you can make the Add.eval() get called right after the Add constructor. Thus Add.eval() evaluates x+x to 2*x and returns Mul(2,x). So in the end Add(x,x) is in fact the same as Mul(2,x).
And in the end, you don't have to think about eval() at all. And you don't need ex (python is handling the garbage collection automatically).
But soon I would like to implement the core of SymPy in C++ for speed and I am curious, if my system is going to be faster or slower than GiNaC.
I gather that you can already tell us something about the speed of the pure-Python SymPy compared to GiNaC. We are so curious!
my question is this: do you know, if it is possible to implement the above procedure in C++? and how? maybe using some macros? Because it is certainly non standard, that you construct an instance of some class Add(x,x) and it returns an instance of a completely different class Mul(2,x).
In the end, it all boils down to the question how much you are willing to do statically (at compile time) as opposed to dynamically (at run time). If you want the degree of flexibility of GiNaC or a language like Python that features duck typing, where x-x can evaluate to a simple number at run time, then I don't see how macros, templates, covariant return types, and all the tricks you can read about in books on C++ can ever be helpful. Think about the eval() trick as GiNaC's way of doing duck typing in the C++ language! Please drop me a note if you happen to know a more straightforward way. I may be very biased, but I see two ways for you: Stick with Python, live happy, and don't be concerned about an occasional performance drop. Or reimplement (a possibly better) GiNaC and do Python bindings (again). But implementing the core of SymPy in C++ just for the sake of speed is probably not worth worrying about. But back to the original topic: I do hope that the sum-of-matrices evaluation thingie is no reason for despair. Way back, that was a deliberte decision. But maybe it was a mistake to place matrices in the type hierarchy at all. Maybe they would be less confusing if they were not subtypes of basic, managed by ex, but classes standing apart from the rest of the hierarchy. That would duplicate some code, however. From past discussions, my impression is that this idea would have some followers on this list. Cheers -richy. -- Richard B. Kreckel <http://www.ginac.de/~kreckel/>
I gather that you can already tell us something about the speed of the pure-Python SymPy compared to GiNaC. We are so curious!
Well, it's slow. For example expanding (x+y)**10 takes 2s, (x+y)**20 takes 20s etc. In GiNaC if I remember correctly, you can easily expand (x+y)**300 or more in a moment. Python generally is around 2000 times slower than C/C++, but I think I am using some bad algorithm in expand(). On the other hand, I have a code for calculating a Riemann and Ricci tensor from a given metric in 4D, I tried that on the Schwarzschild solution of Einstein equations and it produces the correct differential equations in about 2s (and it can be done even faster). Doing this by hand would take me 3 hours. The metric tensor contains 2 unknown functions, I don't know if GiNaC can do it as well.
In the end, it all boils down to the question how much you are willing to do statically (at compile time) as opposed to dynamically (at run time). If you want the degree of flexibility of GiNaC or a language like Python that features duck typing, where x-x can evaluate to a simple number at run time, then I don't see how macros, templates, covariant return types, and all the tricks you can read about in books on C++ can ever be helpful. Think about the eval() trick as GiNaC's way of doing duck typing in the C++ language! Please drop me a note if you happen to know a more straightforward way.
I think all the simplification is happening at runtime in both GiNaC and SymPy. But in GiNaC you are concerned with speed, that's why it's not easy to say which solution is the best. For example I believe that GiNaC is slower than FORM, but GiNaC is certainly more flexible. In SymPy I decided I want to have the simplest code possible.
I may be very biased, but I see two ways for you: Stick with Python, live happy, and don't be concerned about an occasional performance drop. Or reimplement (a possibly better) GiNaC and do Python bindings (again). But implementing the core of SymPy in C++ just for the sake of speed is probably not worth worrying about.
That is of course correct. I also implemented symbolic limits, and SymPy can do almost all of them below 1s. Currently we are just playing with the interface and algorithms, so it certainly doesn't make sense to rewrite it in C++ now. I want to do it in the future though, but only in exactly the same simple way as in SymPy (otherwise I could use GiNaC directly) if it is possible and also because that would answer your question, if there is a more straightforward way than the GiNaC's way. I think there is - using the solution from my last email + a garbage collector in C++. This should make the code as simple as SymPy is - but it's probably going to be slower (but who knows).
But back to the original topic: I do hope that the sum-of-matrices evaluation thingie is no reason for despair. Way back, that was a deliberte decision. But maybe it was a mistake to place matrices in the type hierarchy at all. Maybe they would be less confusing if they were not subtypes of basic, managed by ex, but classes standing apart from the rest of the hierarchy. That would duplicate some code, however. From past discussions, my impression is that this idea would have some followers on this list.
Exactly. I decided not to subclass a Matrix class from the Basic from precisely these reasons. Cheers, Ondrej
2007/3/26, Richard B. Kreckel <kreckel@ginac.de>:
But back to the original topic: I do hope that the sum-of-matrices evaluation thingie is no reason for despair. Way back, that was a deliberte decision. But maybe it was a mistake to place matrices in the type hierarchy at all. Maybe they would be less confusing if they were not subtypes of basic, managed by ex, but classes standing apart from the rest of the hierarchy. That would duplicate some code, however. From past discussions, my impression is that this idea would have some followers on this list.
At the moment, I think my own alternative would be to write such a matrix class myself. I might also handle general rank r tensors as well if I do that. -- --- Martin Sandve Alnæs PhD student, Cardiac Computations Simula Research Laboratory, Norway
2007/3/26, Ondrej Certik <ondrej@certik.cz>:
At the moment, I think my own alternative would be to write such a matrix class myself. I might also handle general rank r tensors as well if I do that.
Or, since you use Python anyway, you can write in Python everything and you'll get SymPy.
Not really, since I build on a C++ library which uses ginac (SyFi). And in certain cases, efficiency for large sets of large expressions may be important. martin
Hello, On Sun, Mar 25, 2007 at 10:30:52PM +0200, Ondrej Certik wrote:
I agree with you completely: it's much better to not provide evalm() for anything but matrix (frankly, I think all those eval* things are plain evil). But with current [sloppy] type system such kind of interface is hardly ever possible. :(
Hi, this is the reason why GiNaC is using ex - so that it automatically calls eval(). We had this discussion already - it's because of C++, memory handling, speed and automatic evaluation.
But in Python I recently found a very elegant solution to this (in SymPy): I also have eval() but it's called automatically right after the construtor and if it returns a different instance, then that instance is returned. Example:
e = Add(x,x)
however "e" contains Mul(2,x), because in python you can make the Add.eval() get called right after the Add constructor. Thus Add.eval() evaluates x+x to 2*x and returns Mul(2,x). So in the end Add(x,x) is in fact the same as Mul(2,x).
Anyway, Add(A,Mul(-1,A)) is going to be zero (a number). Sometimes this is just plain wrong, e.g. think about power series: (x+O(x))-(x+O(x)) should be O(x), not zero. I don't think the problem is some C++ limitation[s]. I think the problem is the sloppy, mathematically inconsistent type system of GiNaC. What is add, exactly? Is it a polynomial? A rational expression? An algebraic one? A tensor? Or what?
And in the end, you don't have to think about eval() at all. And you don't need ex (python is handling the garbage collection automatically).
Typically those general-purpose garbage collectors suck badly. Otherwise I would be using some [Free] CAS (such as Axiom or Maxima) instead of fiddling with C++ (to put it mildly, it is certainly not my favourite programming language).
But soon I would like to implement the core of SymPy in C++ for speed and I am curious, if my system is going to be faster or slower than GiNaC.
my question is this: do you know, if it is possible to implement the above procedure in C++? and how? maybe using some macros?
IFAIK this is what eval() for: some kind of "virtual constructor". The constructor can not return an object of arbitrary type, but eval() can return anything.
Because it is certainly non standard, that you construct an instance of some class Add(x,x) and it returns an instance of a completely different class Mul(2,x).
This is certainly impossible in C++. But the question is: may be "virtual constructors" are not necessary at all? Suppose there is the Polynomial<Ring> type, and Polynomial<Ring3> operator+(const Polynomial<Ring2>&, const Polynomial<Ring1>&); Polynomial<Ring3> operator*(const Polynomial<Ring2>&, const Polynomial<Ring1>&); Polynomial<Ring3> operator*(const Polynomial<Ring2>&, const Ring1&); etc. Thus the return types are known at the compile time: if x is polynomial, then x + x is polynomial too. Thus there is no need for "virtual constructors". And compiler can catch more mistakes: Polynomial<Z> A = x; // OK Polynomial<Z> B = x + 0.5; // ERROR Best regards, Alexei -- All science is either physics or stamp collecting.
Anyway, Add(A,Mul(-1,A)) is going to be zero (a number). Sometimes this is just plain wrong, e.g. think about power series: (x+O(x))-(x+O(x)) should be O(x), not zero.
Well, in SymPy it works like this: In [1]: Add(x,Mul(Rational(-1),x)) Out[1]: 0 In [2]: x-x Out[2]: 0 In [3]: (x+Order(x)) - (x+Order(x)) Out[3]: O(x) It's Order's responsibility, not to evaluate itself to zero. (in detail, the Mul and Add classe call some virtual method of Basic at the evaluation, and Order just implements this method)
I don't think the problem is some C++ limitation[s]. I think the problem is the sloppy, mathematically inconsistent type system of GiNaC. What is add, exactly? Is it a polynomial? A rational expression? An algebraic one? A tensor? Or what?
At least in SymPy the class Add is a correct class to call whenever you write * on the paper by a pencil. Whether the objects on the left and right hand side of * are commutative, or not (with respect to multiplication), is another thing. How they simplify is of course depending on the object, and in SymPy this is implemented in each particular object, not in Add or Mul.
Typically those general-purpose garbage collectors suck badly. Otherwise I would be using some [Free] CAS (such as Axiom or Maxima) instead of fiddling with C++ (to put it mildly, it is certainly not my favourite programming language).
I was using GiNaC because I like its philosophy. Being a library in a normal language. And I thought I could even improve the design a little bit. Speed is not (a top) priority for me. I just want something, that is extensible and simple and powerful.
IFAIK this is what eval() for: some kind of "virtual constructor". The constructor can not return an object of arbitrary type, but eval() can return anything.
In SymPy we have a normal constructor and eval(). But the Basic class contains a metaclass that assures that whenever the user calls Add(x,x), what actually happens is not just Add().__init__(x,x) (__init__ is a constructor in Python), but Add().__init__(x,x).eval() __init__ in Python also cannot return anything else besides the instance of the class Add, but the Add.eval() can return anything, and the metaclass just makes this automatic. But anyway, this is just specific to Python. In C++ we would have to use some kind of macro for that. (as suggested in my previous email). Ondrej
Hi! Sheplyakov Alexei wrote:
Because it is certainly non standard, that you construct an instance of some class Add(x,x) and it returns an instance of a completely different class Mul(2,x).
This is certainly impossible in C++. But the question is: may be "virtual constructors" are not necessary at all? Suppose there is the Polynomial<Ring> type, and
Polynomial<Ring3> operator+(const Polynomial<Ring2>&, const Polynomial<Ring1>&); Polynomial<Ring3> operator*(const Polynomial<Ring2>&, const Polynomial<Ring1>&); Polynomial<Ring3> operator*(const Polynomial<Ring2>&, const Ring1&); etc.
I suppose you meant Ring1 == Ring2 == Ring3.
Thus the return types are known at the compile time: if x is polynomial, then x + x is polynomial too. Thus there is no need for "virtual constructors". And compiler can catch more mistakes:
Polynomial<Z> A = x; // OK Polynomial<Z> B = x + 0.5; // ERROR
Right, but all this is not too helpful when there is no way of knowing at compile time what the ring is. After all, modular rings are different types if they have different moduli and knowing the modulus at compile time is not always possible. Think of public-key crypto algorithms where moduli have typically around 1000 bits and are determined dynamically by RNGs followed by a probabilistic primality test. Did you know that CLN's cl_univpoly_ring class is doing more or less what you suggest? It is delegating consistency checks like the above to runtime, however. And it is somwehat too complicated, in my opinion. Alas, there is no way out of this: Parametric types in C++ are determined at compile time. Just like all types in C++. Best -richy. -- Richard B. Kreckel <http://www.ginac.de/~kreckel/>
participants (6)
-
Alfredo Correa
-
Chris Dams
-
Martin Sandve Alnæs
-
Ondrej Certik
-
Richard B. Kreckel
-
varg@theor.jinr.ru