Wednesday 27 March 2019

The Differentiator

Fun With C# Expressions

Previously:
Differentiating f(x)^g(x)
Note: The latest code can be found at https://github.com/dogbiscuituk/Sid/.

Take a seat with Visual Studio and create a new solution - Console, Winforms, whatever - your choice. Now add a new class library FormulaBuilder, and add to that a static class Differentiator. This is going to let us manipulate objects of type System.Linq.Expression, representing real-valued functions of a single real variable, and ultimately to plot the graph of any such function, as well as its nth derivatives. It will use an easy-to-read fluent interface, while illustrating some key design tenets I'd like to highlight.

Back to the class, and here come our first three requirements. We will need ways to:
  1. represent constants and variables, and combine these fluently into compound expressions;
  2. display any such expression as an easy-to-read formula, and evaluate it for given values of x; and
  3. verify correctness of these combinations and conversions.
Leaving aside the whole "combine these fluently into compound expressions" thing for just a moment, let's open with a ParameterExpression for x, and the following starter set of extension methods:

using System;
using System.Diagnostics;
using System.Linq.Expressions;

namespace FormulaBuilder
{
    public static class Differentiator
    {
        public static ParameterExpression x = Expression.Variable(typeof(double));

        public static ConstantExpression Constant(double c) => Expression.Constant(c);

        public static string AsString(this Expression e, string variableName = "x") =>
            e.ToString().Replace("Param_0", variableName);

        public static Func<double, double> AsFunction(this Expression e) =>
            Expression.Lambda<Func<double, double>>(e, x).Compile();

        public static double AsDouble(this Expression e, double x) => AsFunction(e)(x);

        public static void Check(double expected, double actual) =>
            Check(expected.ToString(), actual.ToString());

        public static void Check(string expected, string actual)
        {
            if (actual == expected)
                Debug.WriteLine("OK: " + actual);
            else
                Debug.WriteLine("*** Comparison failed. Expected: {0}, Actual: {1}.", expected, actual);
        }

On first look, the expression display requirement (1) seems to be met by the built-in ToString() method, available to every type; but that spits out an ugly Param_0 instead of a nice tidy x, so we supply a new AsString() method to tidy that up.

The expression evaluation/iteration requirement (2) means converting our expression into a LambdaExpression, compiling that into a function, and finally executing that function using our chosen values of x. For efficiency I've split this process into two steps. The conversion to a compiled LambdaExpression is performed by the ToFunction() method. Once we have this function, we can call it a million times with different x values, without having to perform a runtime lambda recompilation every time. The ToDouble() method is also provided for those out-of-loop test cases, where we just want to obtain a single value using a single method call.

As for testing requirement (3), the overloaded Check methods provide a line of debug output for each checked operation, beginning with either OK or *** Comparison failed.

Fluency First

Now back to those compounds. Actually, the System.Linq.Expression class already has some fine methods for compounding expressions into BinaryExpressions, but I'm going to deviate a little from these. The target is a fluent syntax for building expressions on the fly, and to that end, I find the names Plus, Minus, Times, Over a little easier to scan than Add, Subtract, Multiply, Divide. So let's add these to the Differentiator class:

        public static BinaryExpression Plus(this Expression f, Expression g) => Expression.Add(f, g);
        public static BinaryExpression Minus(this Expression f, Expression g) => Expression.Subtract(f, g);
        public static BinaryExpression Times(this Expression f, Expression g) => Expression.Multiply(f, g);
        public static BinaryExpression Over(this Expression f, Expression g) => Expression.Divide(f, g);
        public static BinaryExpression Power(this Expression f, Expression g) => Expression.Power(f, g);

Actually I also considered replacing Power with ToThe, but maybe that's just a colloquial idiom too far!

The Constant() syntax is quite clumsy, almost the opposite of the fluent design goal. It would be great to have an implicit operator for converting constants such as 2.0 or Math.PI into ConstantExpressions as required - just as it would be equally great to be able to overload the standard arithmetic operators for Expressions, and be finished with fluent method calls! Sadly, C# restrictions on overloaded operators make both of these dreams impossible, but at least in the Constant() case, what we can do instead is overload the calling methods at the point(s) of invocation. For example, we'd like to be able to write x.Power(2) or x.Power(2.0) instead of x.Power(Constant(2)). This can be achieved by overloading the Power() method, supplying a double in place of an Expression for its second argument, and similarly for the other BinaryExpression methods:

        public static BinaryExpression Plus(this Expression f, double g) => f.Plus(Constant(g));
        public static BinaryExpression Minus(this Expression f, double g) => f.Minus(Constant(g));
        public static BinaryExpression Times(this Expression f, double g) => f.Times(Constant(g));
        public static BinaryExpression Over(this Expression f, double g) => f.Over(Constant(g));
        public static BinaryExpression Power(this Expression f, double g) => f.Power(Constant(g));

This almost gets us where we want to go, but we still have to write e.g. Constant(3).Times(x), when we'd prefer just 3.Times(x). Here it's the first argument of the Times() extension method that needs overloading, and there's a spanner thrown in by C#'s rules of method resolution. Sure, we can supply alternate methods with a double in that position, but where previously we could rely on an int argument being promoted to double, no such luck this time. If we insist (and we do!) upon being able to write 3.Times(x), as opposed to workarounds like x.Times(3), 3.0.Times(x), or worst of all ((double)3).Times(x) - which seems hardly an improvement! - then we must supply separate double and int versions.

        public static BinaryExpression Plus(this double f, Expression g) => Constant(f).Plus(g);
        public static BinaryExpression Minus(this double f, Expression g) => Constant(f).Minus(g);
        public static BinaryExpression Times(this double f, Expression g) => Constant(f).Times(g);
        public static BinaryExpression Over(this double f, Expression g) => Constant(f).Over(g);
        public static BinaryExpression Power(this double f, Expression g) => Constant(f).Power(g);

        public static BinaryExpression Plus(this int f, Expression g) => Constant(f).Plus(g);
        public static BinaryExpression Minus(this int f, Expression g) => Constant(f).Minus(g);
        public static BinaryExpression Times(this int f, Expression g) => Constant(f).Times(g);
        public static BinaryExpression Over(this int f, Expression g) => Constant(f).Over(g);
        public static BinaryExpression Power(this int f, Expression g) => Constant(f).Power(g);

        public static UnaryExpression Negate(this Expression e) => Expression.Negate(e);
        public static BinaryExpression Reciprocal(this Expression e) => e.Power(-1);
        public static BinaryExpression Squared(this Expression e) => e.Power(2);
        public static BinaryExpression Cubed(this Expression e) => e.Power(3);

        public static void TestCompoundExpression()
        {
            var f = x.Squared().Plus(3.Times(x)).Minus(5);    // f(x) = x²+3x-5
            Check("(((x ^ 2) + (3 * x)) - 5)", f.AsString()); // Check the built expression formula
            Check(Math.Pow(7, 2) + 3 * 7 - 5, f.AsDouble(7)); // Check the expression value at x = 7 (should be 65)
        }

With the addition of these few minor utilities, we now have the means to build polynomials and other arithmetic expressions in x. Give your app a project reference to FormulaBuilder, add it to your using clauses, then add a call to Differentiator.TestCompoundExpression() somewhere in your main code and run it. You should see the following two lines of reassurance in your debug output:
OK: (((x ^ 2) + (3 * x)) - 5)
OK: 65
These show that the quadratic expression x² + 3x - 5 was built successfully, and that it correctly evaluates to 65 at x = 7.

Function Calls

Arithmetic operations are one way of building out our expressions, but what about invoking functions? What use a differentiator that can't tell you the derivative of sin is cos, while that of cos is -sin?

Elementary functions in the System.Linq.Expression hierarchy are represented by the MethodCallExpression class. The Function() code below generates such an expression from the correspondingly named method of the Math class, obtained via reflection. Below it are listed a generous dozen or so concrete functions, all which happen to be implemented in Math; and below these, a further group showing how functions "missing" from Math, such as the reciprocal trigonometrics and hyperbolics, as well as their inverses, can be built out of expressions in the elementary ones.

Naturally, the input to any functional expression can itself be an arbitrary expression tree, maybe containing further nested function calls, etc.

        public static MethodCallExpression Function(string functionName, Expression e) =>
            Expression.Call(typeof(Math).GetMethod(functionName, new[] { typeof(double) }), e);

        public static MethodCallExpression Abs(this Expression e) => Function("Abs", e);
        public static MethodCallExpression Sqrt(this Expression e) => Function("Sqrt", e);
        public static MethodCallExpression Exp(this Expression e) => Function("Exp", e);
        public static MethodCallExpression Log(this Expression e) => Function("Log", e);
        public static MethodCallExpression Log10(this Expression e) => Function("Log10", e);
        public static MethodCallExpression Sin(this Expression e) => Function("Sin", e);
        public static MethodCallExpression Cos(this Expression e) => Function("Cos", e);
        public static MethodCallExpression Tan(this Expression e) => Function("Tan", e);
        public static MethodCallExpression Asin(this Expression e) => Function("Asin", e);
        public static MethodCallExpression Acos(this Expression e) => Function("Acos", e);
        public static MethodCallExpression Atan(this Expression e) => Function("Atan", e);
        public static MethodCallExpression Sinh(this Expression e) => Function("Sinh", e);
        public static MethodCallExpression Cosh(this Expression e) => Function("Cosh", e);
        public static MethodCallExpression Tanh(this Expression e) => Function("Tanh", e);

        public static BinaryExpression Sec(this Expression e) => Reciprocal(Cos(e));
        public static BinaryExpression Csc(this Expression e) => Reciprocal(Sin(e));
        public static BinaryExpression Cot(this Expression e) => Reciprocal(Tan(e));
        public static MethodCallExpression Asec(this Expression e) => Acos(Reciprocal(e));
        public static MethodCallExpression Acsc(this Expression e) => Asin(Reciprocal(e));
        public static MethodCallExpression Acot(this Expression e) => Atan(Reciprocal(e));
        public static BinaryExpression Sech(this Expression e) => Reciprocal(Cosh(e));
        public static BinaryExpression Csch(this Expression e) => Reciprocal(Sinh(e));
        public static BinaryExpression Coth(this Expression e) => Reciprocal(Tanh(e));
        public static MethodCallExpression ArcSinh(this Expression e) => Log(e.Plus(Sqrt(e.Squared().Plus(1))));
        public static MethodCallExpression ArcCosh(this Expression e) => Log(e.Plus(Sqrt(e.Squared().Minus(1))));
        public static BinaryExpression ArcTanh(this Expression e) => Log(1.Plus(e).Over(1.Minus(e))).Over(2);
        public static MethodCallExpression ArcSech(this Expression e) => Log(1.Plus(Sqrt(1.Minus(e.Squared()))).Over(e));
        public static MethodCallExpression ArcCsch(this Expression e) => Log(1.Plus(Sqrt(1.Plus(e.Squared()))).Over(e));
        public static BinaryExpression ArcCoth(this Expression e) => Log(e.Plus(1).Over(e.Minus(1))).Over(2);

        public static void TestTrigonometricExpression()
        {
            var f = Sin(x).Squared().Plus(Cos(x).Squared());             // f(x) = sin²x+cos²x
            Check("((Sin(x) ^ 2) + (Cos(x) ^ 2))", f.AsString());        // Check the built expression formula
            double p3 = Math.PI / 3, s = Math.Sin(p3), c = Math.Cos(p3); // Check the expression value at x = π/3
            Check(Math.Pow(s, 2) + Math.Pow(c, 2), f.AsDouble(p3));      // (should be 1)
        }

In your app level code, add a call to Differentiator.TestTrigonometricExpression(), which builds the expression sin²x + cos²x: the LHS of the most famous trigonometric identity, whose RHS is 1 for all values of x. The new test method checks, first using AsString(), that the correct expression has in fact been built; then, using AsDouble(), that it evaluates to 1 as expected, in this case when supplied with π/3 as a parameter. At runtime, your debug window should display a further two comforting lines of verification:
OK: ((Sin(x) ^ 2) + (Cos(x) ^ 2))
OK: 1
Finally, A Derivative

Can't finish this article without a look at some actual differentiation, so let's add the function D() that differentiates expressions. It needs to handle just these four cases:
  • a constant;
  • x itself;
  • a function call;
  • an artithmetic operation (unary or binary).
The third and fourth cases here have potential for recursion. At long last, here is the code to differentiate any expression:

        public static Expression D(this Expression e)
        {
            if (e is ConstantExpression) return Constant(0);  // d(c)/dx = 0
            if (e is ParameterExpression) return Constant(1); // d(x)/dx = 1
            if (e is MethodCallExpression m)
            {
                var f = m.Arguments[0];
                return DifferentiateFunction(m.Method.Name, f).Times(D(f)); // Chain Rule
            }
            if (e is UnaryExpression u)
            {
                var f = D(u.Operand);
                return u.NodeType == ExpressionType.UnaryPlus ? f : Negate(f);
            }
            if (e is BinaryExpression b)
            {
                Expression f = b.Left, g = b.Right;
                switch (b.NodeType)
                {
                    case ExpressionType.Add:      // (f+g)' = f'+g'
                        return D(f).Plus(D(g));
                    case ExpressionType.Subtract: // (f-g)' = f'-g'
                        return D(f).Minus(D(g));
                    case ExpressionType.Multiply: // (fg)' = f'g+fg'
                        return D(f).Times(g).Plus(f.Times(D(g)));
                    case ExpressionType.Divide:   // (f÷g)' = (f'g-fg')÷(g^2) = f'÷g-fg'÷(g^2)
                        return D(f).Over(g).Minus(f.Times(D(g)).Over(g.Squared()));
                    case ExpressionType.Power:    // (f^g)' = (f^g)*(f'g÷f+g'Log(f)) = (f'g+fg'Log(f))f^(g-1)
                        return D(f).Times(g).Plus(f.Times(D(g)).Times(Log(f))).Times(f.Power(g.Minus(1)));
                }
            }
            throw new InvalidOperationException();
        }

        private static Expression DifferentiateFunction(string methodName, Expression x)
        {
            switch (methodName)
            {
                case "Abs": return x.Over(Abs(x));                            // d(|x|)/dx = x/|x|
                case "Sqrt": return x.Power(-0.5).Over(2);                    // d(√x)/dx = 1/(2√x)
                case "Exp": return Exp(x);                                    // d(eˣ)/dx = eˣ
                case "Log": return Reciprocal(x);                             // d(ln(x))/dx = 1/x
                case "Log10": return Reciprocal(x).Times(Math.Log10(Math.E)); // d(log₁₀(x))/dx = log₁₀(e)/x
                case "Sin": return Cos(x);                                    // d(sin(x))/dx = cos(x)
                case "Cos": return Negate(Sin(x));                            // d(cos(x))/dx = -sin(x)
                case "Tan": return Cos(x).Power(-2);                          // d(tan(x))/dx = sec²(x)
                case "Asin": return 1.Minus(x.Squared()).Power(-0.5);         // d(arcsin(x))/dx = 1/√(1-x²)
                case "Acos": return Negate(1.Minus(x.Squared()).Power(-0.5)); // d(arccos(x))/dx = -1/√(1-x²)
                case "Atan": return 1.Over(1.Plus(x.Squared()));              // d(arctan(x))/dx = 1/(1+x²)
                case "Sinh": return Cosh(x);                                  // d(sinh(x))/dx = cosh(x)
                case "Cosh": return Sinh(x);                                  // d(cosh(x))/dx = sinh(x)
                case "Tanh": return 1.Minus(Tanh(x).Squared());               // d(tanh(x))/dx = 1-tanh²(x)
            }
            throw new InvalidOperationException();
        }

DifferentiateFunction() provides the basic template for a function's derivative, without actually applying the chain rule to its argument Expression. That's why it has been implemented as a private helper function; calling it out of context could result in incorrect results if chain rule application was omitted by mistake.

Algebraic Simplification

Still we're not quite ready to supply the final batch of unit tests, stick a fork in it and call it done. While the D() method may be returning strictly correct results, good enough for numeric applications such as graphing, algebraically they are a bit of a mess. For example, differentiating any candidate containing a function call, even one as simple as Sin(x), results in a clumsy *1 becoming appended to the output expression. Some level of simplification is clearly needed to provide visually acceptable results. For this reason, D() gets called not directly, but from within a wrapper function Differentiate() which applies a layer of simplification before returning the resultant Expression:

        public static Expression Differentiate(this Expression e) => Simplify(D(e));

        public static Expression Simplify(this Expression e)
        {
            if (e is MethodCallExpression m) return SimplifyMethodCall(m);
            if (e is UnaryExpression u) return SimplifyUnary(u);
            if (e is BinaryExpression b) return SimplifyBinary(b);
            return e;
        }

        public static Expression SimplifyMethodCall(MethodCallExpression m)
        {
            var operand = Simplify(m.Arguments[0]);
            if (operand is ConstantExpression ce)
            {
                var c = (double)ce.Value;
                return Constant(Function(m.Method.Name, x).AsDouble(c));
            }
            return Function(m.Method.Name, operand);
        }

        public static Expression SimplifyUnary(UnaryExpression u)
        {
            var operand = Simplify(u.Operand);
            if (operand is ConstantExpression ce)
            {
                var c = (double)ce.Value;
                return Constant(u.NodeType == ExpressionType.UnaryPlus ? c : -c);
            }
            return u.NodeType == ExpressionType.UnaryPlus ? operand : Negate(operand);
        }

        public static Expression SimplifyBinary(BinaryExpression b)
        {
            b = Expression.MakeBinary(b.NodeType, Simplify(b.Left), Simplify(b.Right));
            if (b.Left is ConstantExpression ce1)
            {
                var c1 = (double)ce1.Value;
                if (b.Right is ConstantExpression ce2)
                    return Constant(b.NodeType, c1, (double)ce2.Value);
                switch (b.NodeType)
                {
                    case ExpressionType.Add:                   // c + x → x + c
                    case ExpressionType.Multiply:              // c * x → x * c
                        b = Expression.MakeBinary(b.NodeType, b.Right, b.Left);
                        break;
                    case ExpressionType.Subtract:
                        if (c1 == 0) return Negate(b.Right);   // 0 - x → -x
                        break;
                    case ExpressionType.Divide:
                        if (c1 == 0) return Constant(0);       // 0 ÷ x → 0
                        break;
                    case ExpressionType.Power:
                        if (c1 == 0) return Constant(0);       // 0 ^ x → 0
                        if (c1 == 1) return Constant(1);       // 1 ^ x → 1
                        break;
                }
            }
            if (b.Right is ConstantExpression ce3)
            {
                var c3 = (double)ce3.Value;
                switch (b.NodeType)
                {
                    case ExpressionType.Add:
                    case ExpressionType.Subtract:
                        if (c3 == 0) return b.Left;            // x ± 0 → x
                        break;
                    case ExpressionType.Multiply:
                        if (c3 == 0) return Constant(0);       // x * 0 → 0
                        if (c3 == 1) return b.Left;            // x * 1 → x
                        if (c3 == -1) return Negate(b.Left);   // x * -1 → -x
                        break;
                    case ExpressionType.Divide:
                        if (c3 == 0)
                            throw new DivideByZeroException();
                        if (c3 == 1) return b.Left;            // x ÷ 1 → x
                        if (c3 == -1) return Negate(b.Left);   // x ÷ -1 → -x
                        break;
                    case ExpressionType.Power:
                        if (c3 == 0) return Constant(1);       // x ^ 0 → 1
                        if (c3 == 1) return b.Left;            // x ^ 1 → x
                        break;
                }
                switch (b.NodeType)
                {
                    case ExpressionType.Add:                   // (x + c1) ± c2 → x + (c1 ± c2)
                    case ExpressionType.Subtract:              // (x - c1) ± c2 → x - (c1 ∓ c2)
                    case ExpressionType.Multiply:              // (x * c1) */ c2 → x * (c1 */ c2)
                    case ExpressionType.Divide:                // (x / c1) */ c2 → x / (c1 /* c2)
                        if (!(b.Left is BinaryExpression c && c.Right is ConstantExpression ce4))
                            break;
                        bool same = c.NodeType == b.NodeType, opps = c.NodeType == Invert(b.NodeType);
                        if (!(same || opps))
                            break;
                        var c4 = (double)ce4.Value;
                        var nodeType = c.NodeType == ExpressionType.Add || c.NodeType == ExpressionType.Subtract
                            ? ExpressionType.Add
                            : ExpressionType.Multiply;
                        if (opps) nodeType = Invert(nodeType);
                        return Expression.MakeBinary(c.NodeType, c.Left, Constant(nodeType, c4, c3));
                }
            }
            return b;
        }

        public static ExpressionType Invert(this ExpressionType nodeType)
        {
            switch (nodeType)
            {
                case ExpressionType.Add: return ExpressionType.Subtract;
                case ExpressionType.Subtract: return ExpressionType.Add;
                case ExpressionType.Multiply: return ExpressionType.Divide;
                case ExpressionType.Divide: return ExpressionType.Multiply;
            }
            throw new InvalidOperationException();
        }

        public static ConstantExpression Constant(ExpressionType nodeType, double c, double d) =>
            Constant(Apply(nodeType, c, d));

        public static double Apply(ExpressionType nodeType, double c, double d)
        {
            switch (nodeType)
            {
                case ExpressionType.Add: return c + d;
                case ExpressionType.Subtract: return c - d;
                case ExpressionType.Multiply: return c * d;
                case ExpressionType.Divide:
                    if (d == 0)
                        throw new DivideByZeroException();
                    return c / d;
                case ExpressionType.Power:
                    if (c == 0 && d == 0)
                        throw new InvalidOperationException();
                    return Math.Pow(c, d);
            }
            throw new InvalidOperationException();
        }

This Simplify() method does little more than apply some arithmetic identity, associativity and commutativity results, to remove some of the redundancy littering the differentiator's output. That's why I've implemented it here as simple, direct logic. There are many more simplification rules to consider, and as such rules are added, it becomes clear that a rule application engine is needed, allowing extension of the list without spaghettification of the logic.

Now we can at least start writing an acceptable suite of unit tests. Here are some examples:

        public static void TestSimplify(Expression e, string expected) => Check(expected, Simplify(e).AsString());

        public static void TestSimplifications()
        {
            TestSimplify(x.Plus(6).Plus(2), "(x + 8)");
            TestSimplify(6.Plus(x).Plus(2), "(x + 8)");
            TestSimplify(6.Plus(x.Plus(2)), "(x + 8)");
            TestSimplify(x.Plus(6).Minus(2), "(x + 4)");
            TestSimplify(6.Plus(x).Minus(2), "(x + 4)");
            TestSimplify(x.Minus(6).Plus(2), "(x - 4)");
            TestSimplify(x.Minus(6).Minus(2), "(x - 8)");
            TestSimplify(x.Times(6).Times(2), "(x * 12)");
            TestSimplify(6.Times(x).Times(2), "(x * 12)");
            TestSimplify(6.Times(x.Times(2)), "(x * 12)");
            TestSimplify(x.Times(6).Over(2), "(x * 3)");
            TestSimplify(6.Times(x).Over(2), "(x * 3)");
            TestSimplify(x.Over(2).Times(8), "(x / 0.25)");
            TestSimplify(x.Over(2).Over(8), "(x / 16)");
        }

        public static void TestDerivative(Expression e, string expected) => Check(expected, Differentiate(e).AsString());

        public static void TestFunctionDerivatives()
        {
            TestDerivative(Abs(x), "(x / Abs(x))");                       // d(|x|)/dx = x/|x|
            TestDerivative(Sqrt(x), "((x ^ -0.5) / 2)");                  // d(√x)/dx = 1/(2√x)
            TestDerivative(Exp(x), "Exp(x)");                             // d(eˣ)/dx = eˣ
            TestDerivative(Log(x), "(x ^ -1)");                           // d(ln(x))/dx = 1/x
            TestDerivative(Log10(x), "((x ^ -1) * 0.434294481903252)");   // d(log₁₀(x))/dx = log₁₀(e)/x
            TestDerivative(Sin(x), "Cos(x)");                             // d(sin(x))/dx = cos(x)
            TestDerivative(Cos(x), "-Sin(x)");                            // d(cos(x))/dx = -sin(x)
            TestDerivative(Tan(x), "(Cos(x) ^ -2)");                      // d(tan(x))/dx = sec²(x)
            TestDerivative(Asin(x), "((1 - (x ^ 2)) ^ -0.5)");            // d(arcsin(x))/dx = 1/√(1-x²)
            TestDerivative(Acos(x), "-((1 - (x ^ 2)) ^ -0.5)");           // d(arccos(x))/dx = -1/√(1-x²)
            TestDerivative(Atan(x), "(1 / ((x ^ 2) + 1))");               // d(arctan(x))/dx = 1/(x²+1)
            TestDerivative(Sinh(x), "Cosh(x)");                           // d(sinh(x))/dx = cosh(x)
            TestDerivative(Cosh(x), "Sinh(x)");                           // d(cosh(x))/dx = sinh(x)
            TestDerivative(Tanh(x), "(1 - (Tanh(x) ^ 2))");               // d(tanh(x))/dx = 1-tanh²(x)
        }

        public static void TestPolynomialDerivative()
        {
            TestDerivative(x.Power(4).Minus(3.Times(x.Cubed())).Plus(6.Times(x.Squared())).Minus(3.Times(x)).Plus(1),
                "(((((x ^ 3) * 4) - ((x ^ 2) * 9)) + (x * 12)) - 3)");    // d(x⁴-3x³+6x²-3x+1)/dx = 4x³-9x²+12x-3
        }

        public static void TestChainRule()
        {
            TestDerivative(Exp(x.Squared()), "(Exp((x ^ 2)) * (x * 2))"); // d(exp(x²))/dx = exp(x²)*2x
            TestDerivative(Log(Sin(x)), "((Sin(x) ^ -1) * Cos(x))");      // d(ln(sin(x)))/dx = cot(x)
            TestDerivative(Tan(x.Cubed().Plus(8.Times(x))),               // d(tan(x³+8x))/dx = sec²(x³+8x)*(3x²+8)
                "((Cos(((x ^ 3) + (x * 8))) ^ -2) * (((x ^ 2) * 3) + 8))");
        }

        public static void TestAll()
        {
            TestSimplifications();
            TestCompoundExpression();
            TestTrigonometricExpression();
            TestFunctionDerivatives();
            TestPolynomialDerivative();
            TestChainRule();
        }
    }
}

Next time, by overwhelming public demand (one request has flooded in), I'll be exercising these extension methods in a simple differential graphing application.

Acknowledgements
  • Thanks are due to my colleague Stuart Stein for his code review with useful comments, particularly the suggestion that I should embrace the expression body syntax.
  • Thanks also to Pavel Vladov for the free use of his C# Syntax Highlighter to format the code snippets in this article.

No comments:

Post a Comment