codeplea / tinyexpr

tiny recursive descent expression parser, compiler, and evaluation engine for math expressions
https://codeplea.com/tinyexpr
zlib License
1.58k stars 240 forks source link

Support for symbolic differentiation #73

Open AntonC9018 opened 3 years ago

AntonC9018 commented 3 years ago

Hello, very nice project. I've been looking for a relatively simple C library for expression parsing that also supported symbolic differentiation and happened to find your project. Your code seems pretty straightforward and easy to use. However, it lacks the differentiation feature. So I have a question: do you know of anybody who has extended this code to support that? If so, please point me there. If not, do you know of any other similar lightweight library that supports it? Also, if I were to try and implement it, do you have any suggestions (general guidelines) on how to tackle that? To be clear, I'm not asking about the theory, like the power or the chain rule, but about how you would go about implementing it. Thanks.

codeplea commented 3 years ago

Sorry, I don't know of any small libraries that support symbolic differentiation. I do think that TinyExpr could be a good base to start with, precisely because it is small and doesn't have a lot of cruft that you would be fighting against whilst extending it.

I don't really have any tips. I think you just recursively match against rules and simplify when possible. Think about how the rules (e.g. chain rule) apply over a symbolic tree. It won't be an easy project, by any means, but it should be straightforward enough to make some progress with. You might want to look into how other computer algebra systems do it.

If you're working on a funded project, I am available for hire.

AntonC9018 commented 3 years ago

Ok, finally got some free time to implement this.

I've hacked together a working function that does symbolic differentiation (see my fork). I must warn you, that it really is hacked together in a couple of hours and is not optimal at all. For now it works with just some of the builtin functions (+, -, ^, *, /, sin, cos, exp and ln). These functions are currently differentiated separately, in an if-else chain.

Also, no hate on you from my side, I do respect your style of coding since every person has their own one, but I had to do a clean-up pass over all of the code, making it a bit more readable for me (some renames, brackets on new lines, comments, etc). I might have broken compatibility with older C compilers in the process but I don't know for sure (see comments in my version of the code for more details). I don't have much experience with C anyway.

Right now it is clearly nowhere near the quality level for a PR.

I don't know if I'm going to work much more on this, so if anybody would want to progress on the groundwork I've started, below are some ideas. (Note that I will most likely also hack together a pretty print for the expressions.)

The code will have to make tradeoffs if it becomes more complicated. E.g. if one wanted to properly support differentiation, they would have to decide between making all code a bit slower, while keeping differentiation relatively fast or keep the differentiation feature slower, while keeping the rest of the code at the same speed level. What I mean by this are two things:

  1. Storing pointers to whole variables, instead of just functions, in expressions. This will slow down expression evaluation, but would allow the addition of extra data in those variables. Since we're talking about differentiation, adding a generator function that would give you a differentiated expression to each of these would be useful. It would eliminate completely the need for if-else. This would also make differentiation of custom functions easier with the user providing a generator function for the given function variable. If the slowdown of function evaluation is a no-no, these differentiated expression generator functions will have to be looked up by expression's function in the table of variables, which is slow.
  2. Use smart pointers for expressions. I know, this will complicate the library a lot, but will also eliminate much of the memory usage necessary for differentiation. It needs a bunch, since in many cases it has to create copies of e.g. the inner expressions when doing the power rule, or copies for both sides when doing the product rule.

The last but, oh my, not the least is a better optimization strategy. The one currently incorporated in the library is a pretty simplistic one and does not simplify the expression much at all when used with differentiation. For it not to produce gargantuan expressions, the optimizer (or expression simplifier) has to be more context aware.

Feel free to throw in some more, if you have any in mind.

AntonC9018 commented 3 years ago

Oh, yeah, just remembered another thing: there is no error handling at the moment.

codeplea commented 3 years ago

Hey, that looks cool! I'm impressed that you got so far with such a small amount of additional code. Thanks for sharing.

ianhbell commented 8 months ago

An orthogonal way to do derivatives is to use complex step derivatives if you can do the evaluation in complex math. Essentially no loss in precision.