views:

144

answers:

4

I am trying to implement an infrastructure in C# that would allow me to make arbitrary mathematical expressions. For example, I want to be able to take an expression like

asin(sqrt(z - sin(x+y)^2))

and turn it into an object that will allow me to evaluate it in terms of x,y, and z, get derivatives, and possibly do some kind of symbolic algebra on it. What are people's thoughts on a good model for this in C#?

I have my own take, which I am afraid is heading off into architecture astronautics, so I want to make sure that is not the case.

Basically, the functions like sin, +, sqrt, etc. have classes based off a base class:

Function

Function<TOut> : Function
    TOut Value

Function<Tin, TOut> : Function
    TOut Evaluate(TIn value)
    Function Derivative
    Function<TOut, TIn> INverse

Function<TInA, TInB, TOut> : Function
    TOut Evaluate(TInA valueA, TInB valueB)
    Function PartialDerivativeA
    Function PartialDerivativeB

So far, so simple. The trick is how to compose the functions. Here I believe I want something like a currying approach so that I can evaluate the function for a single parameter, and have the other ones remain. So I am thinking of having a factory class like this:

Function<TInA, TInB, TOut> -> 
           Function<TInA, Function<TInB, TOut>>

(Function<TInA, TInB, TOut>, Function<TInX, TInA>, null) -> 
           Function<TInX, Function<TInB, TOut>>

(Function<TInA, TInB, TOut>, Function<TInA>, Function<TInX, TInY, TInB>) -> 
           Function<TInX, Function<TInY, TInB>>

and so on. My main concerns are that the generic types might make the system unusable (if the user is required to know the full generic types just to evaluate), and that I might not be able to construct all of the generic types from the input arguments.

Thanks for your input!

+1  A: 

Note that it is possible to use the C# compiler to evaluate expressions.

Evaluating Mathematical Expressions by Compiling C# Code at Runtime http://www.codeproject.com/KB/recipes/matheval.aspx

Robert Harvey
It seems like the compiled expression would be just as opaque to me as a delegate would . . . I'm going to need to do meta-things to these objects like displaying the underlying expression.
Reveazure
True enough. Anders and his team intend on breaking open the C# compiler, allowing it to be used as a service (which would provide far greater flexibility, as the current compiler is essentially a black box), but that capability doesn't exist yet.
Robert Harvey
A: 

I'm not entirely sure what currying is, but the usual approach to parsing expressions is to build an abstract syntax tree.
From this it shouldn't be difficult to evalute the expression, find the derivative, or whatever it is you want to do.


[Edit] I'm afraid your comments make no sense. From the sounds of it, you want to parse an expression and build an AST, from which you can do whatever you want with it. Yes, you will build classes for each type of node; something like this

public class PlusNode : BinaryNode
{
    public PlusNode(Node left, Node right) { base(left, right); }
    public virtual double Evaluate() { return Left.Evaluate() + Right.Evaluate(); }
    public virtual Node BuildDerivative()
    {
        return new PlusNode(Left.BuildDerivative(), Right.BuildDerivative());
    }
}

public class SinNode : UnaryNode
{
    public SinNode(Node child) { base(child); }
    public virtual double Evaluate() { return Math.Sin(Child.Evaluate()); }
    public virtual Node BuildDerivative()
    {
        return new MultiplyNode(new CosNode(Child.Clone()), Child.BuildDerivative()); //chain rule
    }
}
BlueRaja - Danny Pflughoeft
Right, the question is, basically, how to represent the free parameters of the AST, and also what is the right balance of flexibility vs. typesafety?
Reveazure
@Reveazure: If by "free parameters" you mean "variables," do it however you want; you could store strings, or as objects with a name and a value. It doesn't really matter, just as long as when you reach one in the tree you can recognize it (note that all leaves will either be numbers or variables, and vice-versa)
BlueRaja - Danny Pflughoeft
Here's my article on what currying is, if you're interested in that subject: http://blogs.msdn.com/ericlippert/archive/2009/06/25/mmm-curry.aspx
Eric Lippert
The trouble with all of the responses so far is basically that the functionality provided for functions, I need for classes, and rather than currying the function parameters, I need to "curry" the class type arguments.
Reveazure
Yes, but let's say I have an expression like (- (/ x (^ y 2))).^ is a binary operator, however (^ y 2) only has one parameter. So what should the type of (^ y 2) be? I think you can see where I'm going.I freely admit that I'm somewhat confused. That's why I'm asking.
Reveazure
(^ y 2) has two parameters - one is a variable (y, the "free parameter"), and one is a constant (the number 2). You would write two separate classes for these two kinds of nodes, since variables are a special case. As I mentioned in my previous comment, all leaves will be one of these two.
BlueRaja - Danny Pflughoeft
A: 

What about using Expression Trees? Note that on the linked page, there's even an example for building sort of a curried function (building a "less than five" function from a generic "less than" operator and a fixed constant)

MartinStettner
The trouble here is that what I really want is a tree of classes, which have things like ToString and let me get their derivatives. Also, I don't really need the ability to compile the expression or dynamically generate it from C# code.
Reveazure
A: 

Funny, I actually did this a few months ago in D and it wasn't received as particularly interesting. My approach was to use templated expression tree classes. I had a Binary class template that could be instantiated with +, *, etc, a unary class that could be instantiated with sin, exp, etc. Derivatives worked by mostly just recursively applying the chain and product rules. For example:

class Binary(alias fun) : MathExpression {
    MathExpression left, right;

    MathExpression derivative() {
        static if(is(fun == add)) {
            return left.derivative + right.derivative;
        } else static if(is(fun == mul)) {
            return left.derivative * right + right.derivative * left;
        }
    }

    real opCall(real x) {
        return fun(left(x), right(x));
    }
}


class Unary(alias fun) : MathExpression {
    MathExpression inner;

    MathExpression derivative() {
        static if(is(fun == sin)) {
            return Unary!(sin)(inner.derivative);
        }
    }

    real opCall(real x) {
        return fun(inner(x));
    }
}

class Constant : MathExpression {

    real val;

    real opCall(real x) {
        return val;
    }

    real derivative() {
        return new Constant(0);
    }
}
dsimcha