使用表达式从lambda函数获取导数

时间:2019-03-15 06:06:21

标签: c# lambda reflection

假设您在这样的输入中具有lambda函数

Function<double, double> f = x => x*x +2

您想计算点x0的导数。结果方法的签名将为:

Expression<Function<double, double>> GetDerivative(Expression<Function<double, double>> f)

因此,使用此方法将获得一个新的表达式,将其编译并将x0用作参数即可获得结果。  公式是

df(x0) = (f(x0 + eps) - f(x0)) /eps

我现在拥有的是:

public static Expression<Func<double, double>> GetDerivative(Expression<Func<double, double>> func)
    {
        var eps = 1e-5;
        var paramX = Expression.Parameter(typeof(double), "x");
        var epsilon = Expression.Constant(eps);
        var secondExpression = Expression.Lambda(func, paramX);
        //var firstExpression = ..
        var expression =  Expression.Divide(Expression.Subtract(firstExpression, secondExpression), epsilon);
        return Expression.Lambda<Func<double, double>>(expression, paramX);
    }

如何使用参数(paramX + epsilon)创建firstExpression?

1 个答案:

答案 0 :(得分:1)

您有一个好的开始,并且代码肯定清除了一些内容。

我将逐步构建它。您想向外构建表达式,以免在中间迷路。

首先,您要添加x0eps。您已经有x0参数和epsilon常数。我正在重命名一些内容,以便在显示时显示给他们。

ParameterExpression x0Parameter = Expression.Parameter(typeof(double), "x0");
ConstantExpression epsilonConstant = Expression.Constant(1e-5);

添加它们是一个简单的表达式:

Expression.Add(x0Parameter, epsilonConstant)

现在,您要将其传递给f(即func)。为此,您需要做一些事情。首先,您需要一个代表。作为表达式,没有要定位的方法,因此您必须对其进行编译。然后,您必须获取类型及其Invoke方法。您还需要使编译后的函数可用作调用的目标。

Func<double, double> funcInstance = func.Compile();
Type funcType = typeof(Func<double, double>);
System.Reflection.MethodInfo invokeMethod = funcType.GetMethod("Invoke");
ConstantExpression funcConstant = Expression.Constant(funcInstance, typeof(Func<double, double>));

现在,您可以调用它,并合并您已经构建的表达式。

Expression.Call(funcConstant, invokeMethod, Expression.Add(x0Parameter, epsilonConstant))

下一个表达式是从第一个f(x0)中减去的表达式。当然,这更简单。您可以重用到目前为止定义的大部分内容。

Expression.Call(funcConstant, invokeMethod, x0Parameter)

现在您要减去这两个表达式。

Expression.Subtract(
    Expression.Call(funcConstant, invokeMethod, Expression.Add(x0Parameter, epsilonConstant)),
    Expression.Call(funcConstant, invokeMethod, x0Parameter)
    )

最后,您想将其除以eps

Expression.Divide(
    Expression.Subtract(
        Expression.Call(funcConstant, invokeMethod, Expression.Add(x0Parameter, epsilonConstant)),
        Expression.Call(funcConstant, invokeMethod, x0Parameter)
        ),
        epsilonConstant
    )

将它们放在一起,看起来像这样:

public static Expression<Func<double, double>> GetDerivative(Expression<Func<double, double>> func)
{
    ParameterExpression x0Parameter = Expression.Parameter(typeof(double), "x0");
    ConstantExpression epsilonConstant = Expression.Constant(1e-5);

    Func<double, double> funcInstance = func.Compile();
    Type funcType = typeof(Func<double, double>);
    System.Reflection.MethodInfo invokeMethod = funcType.GetMethod("Invoke");
    ConstantExpression funcConstant = Expression.Constant(funcInstance, typeof(Func<double, double>));

    BinaryExpression body = Expression.Divide(
        Expression.Subtract(
            Expression.Call(funcConstant, invokeMethod, Expression.Add(x0Parameter, epsilonConstant)),
            Expression.Call(funcConstant, invokeMethod, x0Parameter)
            ),
            epsilonConstant
        );

    return Expression.Lambda<Func<double, double>>(body, x0Parameter);
}

更新:@ckuri指出,您可以使用Expression.Invoke来调用func,而无需进行所有反思。

public static Expression<Func<double, double>> GetDerivative(Expression<Func<double, double>> func)
{
    ParameterExpression x0Parameter = Expression.Parameter(typeof(double), "x0");
    ConstantExpression epsilonConstant = Expression.Constant(1e-5);

    BinaryExpression body = Expression.Divide(
        Expression.Subtract(
            Expression.Invoke(func, Expression.Add(x0Parameter, epsilonConstant)),
            Expression.Invoke(func, x0Parameter)
            ),
            epsilonConstant
        );

    return Expression.Lambda<Func<double, double>>(body, x0Parameter);
}

回到现实世界,定义您的函数,获取派生函数,将派生函数编译为委托,然后调用委托:

Expression<Func<double, double>> f = x => x * x + 2;

Expression<Func<double, double>> df = GetDerivative(f);

Func<double, double> dfFunc = df.Compile();

double result = dfFunc(someInput);