Search code examples
c#mathexpression-treesfuncpolynomials

Building a Func<int, double> Polynomial using Expression Trees


TL;DR

How can I build up the expression using an array of coefficients and turn it into a Func<int, double>? Is there a better way than expression trees?


I have an immutable Sequence type that is constructed using a Func<int, double> formula that is used to generate the term An for a sequence A. I started building a helper class to construct common math formulas with some simple parameters:

public static Sequence CreateLinearSequence (double m, double b)
{ return new Sequence (n => m * n + b); }

I built standard methods for constant sequences, logarithms, and simple polynomials (linear, quadratic, cubic, and quartic) but I want to extend it to support an arbitrary number of terms using the params keyword.

Here's the method I have:

 public static Sequence CreatePolynomialSequence (params double[] coeff)
 {
     Expression<Func<int, double>> e = x => 0;
     double pow = 0;

     for (int i = coeff.Length - 1; i >= 0; i--)
     {
         double c = coeff[i];
         var p = Expression.Parameter (typeof (int), "x");
         e = Expression.Lambda<Func<int, double>> (
             Expression.Add (
                 e, 
                 (Expression<Func<int, double>>)(x => c * Math.Pow (x, pow))
             ), 
             p);
         pow++; 
     }
     return new Sequence (e.Compile ());
 }

It might be obvious to you guys what I'm doing wrong; I messed around a bit until I got something that I felt like should work, but it doesn't.

The goal is for the Sequence to work like this for an array double[] coeff = {a,b,c,d,e,f,g,h}

x => h + gx + fx^2 + ex^3 + dx^4 + cx^5 + bx^6 + ax^7 using the appropriate Math.Pow(x, exponent) calls.

Running

var s2 = SequenceHelper.CreatePolynomialSequence (new[] { 1d, 2 });
Console.WriteLine ("s2: " + s2);

results in

Unhandled Exception: System.InvalidOperationException: The binary operator Add is not defined for the types 'System.Func2[System.Int32,System.Double]' and 'System.Func2[System.Int32,System.Double]'. at System.Linq.Expressions.Expression.GetUserDefinedBinaryOperatorOrThrow (ExpressionType binaryType, System.String name, System.Linq.Expressions.Expression left, System.Linq.Expressions.Expression right, Boolean liftToNull) [0x0004a] in /private/tmp/source-mono-mac-4.2.0-branch/bockbuild-mono-4.2.0-branch/profiles/mono-mac-xamarin/build-root/mono-4.2.1/mcs/class/dlr/Runtime/Microsoft.Scripting.Core/Ast/BinaryExpression.cs:658 at System.Linq.Expressions.Expression.Add (System.Linq.Expressions.Expression left, System.Linq.Expressions.Expression right, System.Reflection.MethodInfo method) [0x00057] in /private/tmp/source-mono-mac-4.2.0-branch/bockbuild-mono-4.2.0-branch/profiles/mono-mac-xamarin/build-root/mono-4.2.1/mcs/class/dlr/Runtime/Microsoft.Scripting.Core/Ast/BinaryExpression.cs:1409 at System.Linq.Expressions.Expression.Add (System.Linq.Expressions.Expression left, System.Linq.Expressions.Expression right) [0x00000] in /private/tmp/source-mono-mac-4.2.0-branch/bockbuild-mono-4.2.0-branch/profiles/mono-mac-xamarin/build-root/mono-4.2.1/mcs/class/dlr/Runtime/Microsoft.Scripting.Core/Ast/BinaryExpression.cs:1390 at Sequence.SequenceHelper.CreatePolynomialSequence (System.Double[] coeff) [0x00110] in /Users/Knoble/MonoProjects/Sequences/Sequence/SequenceHelper.cs:88
at Sequence.Test.Main () [0x0001f] in /Users/Knoble/MonoProjects/Sequences/Sequence/Test.cs:53 [ERROR] FATAL UNHANDLED EXCEPTION: System.InvalidOperationException: The binary operator Add is not defined for the types 'System.Func2[System.Int32,System.Double]' and 'System.Func2[System.Int32,System.Double]'. at System.Linq.Expressions.Expression.GetUserDefinedBinaryOperatorOrThrow (ExpressionType binaryType, System.String name, System.Linq.Expressions.Expression left, System.Linq.Expressions.Expression right, Boolean liftToNull) [0x0004a] in /private/tmp/source-mono-mac-4.2.0-branch/bockbuild-mono-4.2.0-branch/profiles/mono-mac-xamarin/build-root/mono-4.2.1/mcs/class/dlr/Runtime/Microsoft.Scripting.Core/Ast/BinaryExpression.cs:658 at System.Linq.Expressions.Expression.Add (System.Linq.Expressions.Expression left, System.Linq.Expressions.Expression right, System.Reflection.MethodInfo method) [0x00057] in /private/tmp/source-mono-mac-4.2.0-branch/bockbuild-mono-4.2.0-branch/profiles/mono-mac-xamarin/build-root/mono-4.2.1/mcs/class/dlr/Runtime/Microsoft.Scripting.Core/Ast/BinaryExpression.cs:1409 at System.Linq.Expressions.Expression.Add (System.Linq.Expressions.Expression left, System.Linq.Expressions.Expression right) [0x00000] in /private/tmp/source-mono-mac-4.2.0-branch/bockbuild-mono-4.2.0-branch/profiles/mono-mac-xamarin/build-root/mono-4.2.1/mcs/class/dlr/Runtime/Microsoft.Scripting.Core/Ast/BinaryExpression.cs:1390 at Sequence.SequenceHelper.CreatePolynomialSequence (System.Double[] coeff) [0x00110] in /Users/Knoble/MonoProjects/Sequences/Sequence/SequenceHelper.cs:88
at Sequence.Test.Main () [0x0001f] in /Users/Knoble/MonoProjects/Sequences/Sequence/Test.cs:53 The application was terminated by a signal: SIGHUP


Solution

  • I am confused by the question and by all three answers; why are you messing about with expression trees if all you intend to do is compile them into a delegate? Just return the delegate directly!

    public static Func<double, double> CreatePolynomialFunction (params double[] coeff)
    {
        if (coeff == null) throw new ArgumentNullException("coeff");
        return x => 
        {
            double sum = 0.0;
            double xPower = 1;
            for (int power = 0; power < coeff.Length; power += 1)
            {
                sum += xPower * coeff[power];
                xPower *= x;
            }
            return sum;
        };
    }
    

    Done. No messing about with expression trees required.

    (I note that I assumed that the nth item in the array was the nth coefficient; apparently you list your coefficients backwards in your array. This seems prone to error, but if that's what you want then it is not difficult to modify this answer to run the loop down from Length-1 to zero.)