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

#c# #lambda #reflection

#c# #лямбда #отражение

Вопрос:

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

 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);
    }
  

Как создать firstExpression с параметром (paramX epsilon)?

Комментарии:

1. как только это функция<double, double>, это может стать немного непрактичным … может быть, вам нужно выражение <Func<double, double>>, а затем более внимательно посмотрите на дерево выражений

2. @DarkSquirrel42 уверен. Исправлены подписи.

3. итак, ваша проблема в математике или в том, как разобрать дерево выражений?

4. Вы уже узнали что-нибудь о построении деревьев выражений? Эти выражения являются одними из самых простых в построении. Большая часть этого включает в себя Expression.Add/Subtract/Divide и Expression.Call ( f.Invoke() , не f() ), и где-то вы должны определить eps . Как только вы попробуете это, уточните вопрос.

5. Я пробовал это, но не могу понять, как построить лямбда-выражение с параметром (x eps). Это основная проблема.

Ответ №1:

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

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

Сначала вы хотите добавить 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);