Использование выражений для получения производной от лямбда-функции - PullRequest
0 голосов
/ 15 марта 2019

Представьте, что у вас есть лямбда-функция на входе, как это

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)?

1 Ответ

1 голос
/ 15 марта 2019

Вы хорошо начали, и код определенно прояснил некоторые вещи.

Я собираюсь построить это поэтапно.Вы хотите построить выражение наружу, чтобы не потеряться в середине.

Сначала вы хотите добавить x0 и eps.У вас уже был параметр x0 и константа эпсилон.Я переименовываю некоторые вещи, поэтому я покажу их так, как они есть у меня.

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);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...