使用表达式模板自动微分 C++

2024-01-14

介绍

我正在尝试了解表达式模板,因为它似乎是一种适用于各种计算的非常强大的技术。我在网上查看了不同的例子(例如维基百科 https://en.wikipedia.org/wiki/Expression_templates),我编写了一堆执行不同计算的小程序。然而,我发现了一个无法解决的问题;即,将任意表达式分配给变量、其“惰性求值”以及将其重新分配给另一个任意表达式。

一个可以用auto,为了将一个表达式分配给一个变量,但是重新分配给另一个表达式是不可能的(你可以看看整个库sadET https://github.com/dkaramit/sadET/tree/simplificationRules,以便充分理解我正在尝试做的事情)。此外,分配和重新分配可以使用CRTP https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern和超载operator=。然而,表达式是在赋值过程中计算的,我们基本上丢失了关于表达式是什么的所有信息。

因此,我尝试使用多态克隆+CRTP(例如参见this https://katyscode.wordpress.com/2013/08/22/c-polymorphic-cloning-and-the-crtp-curiously-recurring-template-pattern/),这种方法有效,但是当我尝试将变量重新分配给包含相同变量的表达式时,出现段错误。

Code

在下面的代码中,我展示了如何实现加法表达式模板的简化版本。在下面的代码中,msg是一个宏std::cout<<typeid(*this).name()<<std::endl;,以便跟踪正在评估的内容。

这是所有表达式的(纯)基类,它允许我将变量分配给通用表达式(使用多态克隆):

struct BaseExpression{
    BaseExpression()=default;
    virtual double evaluate()const =0 ;
};

这是一个所有表达式都继承自的类,并且允许我使用 CRTP

template<typename subExpr>
struct GenericExpression:BaseExpression{
    const subExpr& self() const {return static_cast<const subExpr&>(*this);}
    subExpr& self() {return static_cast<subExpr&>(*this);}    
    double evaluate() const { msg; return self().evaluate(); };
};

这个想法是所有表达式都由数字、基本函数和运算符组成。所以,我写了一个Number类如下

class Number: public GenericExpression<Number>{
    double val;
    public:
    Number()=default;

    Number(const double &x):val(x){}
    Number(const Number &x):val(x.evaluate()){}
    
    double evaluate()const  { msg; return val;}
    double& evaluate() { msg; return val;}
};

遵循表达式模板的思想,那么,相加就是

template<typename leftHand,typename rightHand>
class Addition:public GenericExpression<Addition<leftHand,rightHand>>{
    const leftHand &LH;
    const rightHand &RH;

    public:
    Addition(const leftHand &LH, const rightHand &RH):LH(LH),RH(RH){}

    double evaluate() const {msg; return LH.evaluate() + RH.evaluate();}
};

template<typename leftHand,typename rightHand>
Addition<leftHand,rightHand> 
operator+(const GenericExpression<leftHand> &LH, const GenericExpression<rightHand> &RH){
    return Addition<leftHand,rightHand>(LH.self(),RH.self()); 
}

为了能够利用BaseExpression,我还写了一个Expression将用于分配实例的类GenericExpression to Expression变量。

class Expression: public GenericExpression<Expression>{
    public:
    BaseExpression *baseExpr;

    Expression()=default;
    Expression(const Expression &E){baseExpr = E.baseExpr;};

    double evaluate() const {msg;  return baseExpr->evaluate();}
    
    template<typename subExpr>
    void assign(const GenericExpression<subExpr> &RH){        
        baseExpr = new subExpr(RH.self());
    }

};

我这个类,重要的一点是指针baseExpr允许改变evaluate函数到GenericExpression当我们调用分配时。

一些例子

为了测试这是否有效,我声明了以下变量:

    Number x(3.2);
    Number y(-2.3);
    Expression z,w;

然后,我们可以看到以下事情起作用了

    //assignment to Number
    z.assign(x);
    cout<<z.evaluate()<<endl;


    //assignment to Addition<Number,Number>
    z.assign(x+y);
    cout<<z.evaluate()<<endl;

    
    //assignment to Addition<Expression,Number>
    w.assign(z+y);
    cout<<w.evaluate()<<endl;

但是,当我执行以下操作时,运行时会出现无限递归z.evaluate() since z.baseExpr指向自身。

    // Segmentation fault of z.evaluate() due to infinite recursion between
    // LH.evaluate() (in Addition<Expression,Number>::evaluate()) and 
    // baseExpr->evaluate() (in Expression::evaluate())
    z.assign(z+x);
    cout<<z.evaluate()<<endl;

重现我描述的行为的整个代码:

#include<iostream>
#include<cmath>


// message to print during evaluation, in order to track the evaluation path.
#define msg std::cout<<typeid(*this).name()<<std::endl;


struct BaseExpression{
    BaseExpression()=default;
    virtual double evaluate()const =0 ;
};

template<typename subExpr>
struct GenericExpression:BaseExpression{
    const subExpr& self() const {return static_cast<const subExpr&>(*this);}
    subExpr& self() {return static_cast<subExpr&>(*this);}
    
    double evaluate() const { msg; return self().evaluate(); };
};


class Number: public GenericExpression<Number>{
    double val;
    public:
    Number()=default;

    Number(const double &x):val(x){}
    Number(const Number &x):val(x.evaluate()){}
    
    double evaluate()const  { msg; return val;}
    double& evaluate() { msg; return val;}
};

template<typename leftHand,typename rightHand>
class Addition:public GenericExpression<Addition<leftHand,rightHand>>{
    const leftHand &LH;
    const rightHand &RH;

    public:
    Addition(const leftHand &LH, const rightHand &RH):LH(LH),RH(RH){}

    double evaluate() const {msg; return LH.evaluate() + RH.evaluate();}
};

template<typename leftHand,typename rightHand>
Addition<leftHand,rightHand> 
operator+(const GenericExpression<leftHand> &LH, const GenericExpression<rightHand> &RH){
    return Addition<leftHand,rightHand>(LH.self(),RH.self()); 
}


class Expression: public GenericExpression<Expression>{
    public:
    BaseExpression *baseExpr;

    Expression()=default;
    Expression(const Expression &E){baseExpr = E.baseExpr;};
    // Expression(Expression *E){baseExpr = E->baseExpr;};

    double evaluate() const {msg;  return baseExpr->evaluate();}


    template<typename subExpr>
    void assign(const GenericExpression<subExpr> &RH){
        
        baseExpr = new subExpr(RH.self());
    }

};


using std::cout;
using std::endl;


int main(){
    Number x(3.2);
    Number y(-2.3);
    Expression z,w;

    // works fine!
    z.assign(x);
    cout<<z.evaluate()<<endl;
    // works fine!
    z.assign(x+y);
    cout<<z.evaluate()<<endl;

    
    // works fine!
    w.assign(z+y);
    cout<<w.evaluate()<<endl;

    // Segmentation fault of z.evaluate() infinite recursion in between
    // LH.evaluate() (in Addition<Expression,Number>::evaluate()) and 
    // baseExpr->evaluate() (in Expression::evaluate())
    z.assign(z+x);
    cout<<z.evaluate()<<endl;

    return 0;
}

转向自动差异

自动微分的实现与上面的例子没有什么不同。最重要的变化是引入了Constant类和一个derivative派生自的所有类的成员函数GenericExpression,返回任意表达式。也就是说,我们添加

class Constant: public GenericExpression<Constant>{
    double val;
    public:
    Constant()=default;

    Constant(const double &x):val(x){}
    Constant(const Constant &x):val(x.evaluate()){}
    
    double evaluate()const  { msg; return val;}
    double& evaluate() { msg; return val;}
    
    auto derivative(){return Constant(0);}
};

The derivative其他类中的成员应如下所示:

auto Number::derivative(){return Constant(1);}

template<typename leftHand,typename rightHand>
auto Addition<left,right>::derivative(){return LH.derivative() + RH.derivative();}

问题

即使我们找到一种方法来避免(或忽略)我之前展示的段错误,我也看不出有什么办法可以使derivative的虚拟成员函数BaseExpression,因为它返回一种不同类型的表达式,原则上,我们只知道何时调用它(因此auto).

最后一个问题

在我试图描述的上下文中,有什么办法可以做到以下几点

    Number x(5);
    Expression z;
    
    z.assign(x);
    z.assign(z+x);
    
    cout<<z.evaluate()<<endl;
    cout<<z.derivative().evaluate()<<endl;

最好没有段错误?

我将感谢社区的意见或见解!

Edit

我简化了代码,以使事情变得更容易,并避免潜在的危险指针。

在这个例子中,我创建了成员函数evaluate指向 lambda 的函数指针。这样,如果我理解正确的话,我直接复制...::evaluate to Expression::evaluate。然而,我仍然遇到段错误......

奇怪的是,当使用递归函数对向量求和时,我遇到了段错误。

新代码:

#include<iostream>
#include<functional>
#include<vector>


// message to print during evaluation, in order to track the evaluation path.
#define msg std::cout<<typeid(*this).name()<<std::endl


class Expression{
    public:
    std::function<double(void)> evaluate;
    
    template<typename T>
    Expression(const T &RH){evaluate = RH.evaluate;}

    template<typename T>
    Expression& operator=(const T &RH){evaluate = RH.evaluate; return *this;}
};




class Number{ 
    double val;
    public:
    Number()=default;

    std::function<double()> evaluate;

    Number(const double &x):val(x){ evaluate = [this](){msg; return this->val;};  }
    Number(const Number &x):val(x.evaluate()){ evaluate = [this](){msg; return this->val;}; }
};

template<typename leftHand,typename rightHand>
class Addition{ 
    const leftHand &LH;
    const rightHand &RH;

    public:
    std::function<double()> evaluate;

    Addition(const leftHand &LH, const rightHand &RH):LH(LH),RH(RH)
    {evaluate = [this](){msg; return this->LH.evaluate() + this->RH.evaluate();};}
};

template<typename leftHand,typename rightHand>
auto operator+(const leftHand &LH, const rightHand &RH){return Addition<leftHand,rightHand>(LH,RH); }



using std::cout;
using std::endl;


inline Expression func (std::vector<Expression> x, int i){
    cout<<i<<endl;

    if (i==0){return static_cast<Expression>(x[0]);}    

    return static_cast<Expression>( x[i] + func(x,i-1) ) ;
};


int main(){
    Number y(-2.);
    Number x(1.33);

    Expression z(y+x);
    Expression w(x+y+x);
    
    // works
    z =x;
    cout<<z.evaluate()<<endl;
    cout<<(z+z+z+z).evaluate()<<endl;

    // Segfault due to recusion 
    // z =z+x;
    // cout<<z.evaluate()<<endl;

    // Unkown Segfault 
    // z = x+y ;
    // cout<<(z+z).evaluate()<<endl;
    // cout<<typeid(z+z).name()<<endl;

    // Unkown Segfault 
    // z = w+y+x+x;
    // cout<<z.evaluate()<<endl;

    
    
    // Unkown Segfault 
    // std::vector<Expression> X={x,y,x,y,x,y,x,y};

    // cout << typeid(func(X,X.size()-1)).name()  << endl;
    // cout << (func(X,X.size()-1)).evaluate()  << endl;
    return 0;
}

Update

大约一年后,我重新审视这个问题,我想我已经找到了解决方案;使用纯粹的抽象类,每个表达式都从中派生。那么一切(从变量,到运算符的返回类型和函数)都是这个抽象类的指针。我目前正在处理的代码是here https://github.com/dkaramit/sadCpp。但请注意,我没有在新设计中使用表达式模板,但我希望在不久的将来的某个时候这样做。


None

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用表达式模板自动微分 C++ 的相关文章

  • 无法将匿名方法转换为类型“System.Windows.Threading.DispatcherPriority”,因为它不是委托类型

    谁能解释我需要做什么才能克服这个错误 无法将匿名方法转换为类型 System Windows Threading DispatcherPriority 因为它不是委托类型 private void Test object sender ba
  • .Net Core 中 String 默认不可序列化吗?

    我正在查看其他的 Fortify 静态分析安全测试 SAST 扫描报告 以识别和抑制误报 应用程序框架是C NET Core SAST 报告部分内容如下 Method1 在第 111 行将不可序列化的对象存储为 HttpSessionSta
  • 请求的资源不支持 HTTP 方法“GET”

    我的路线配置正确 并且我的方法具有装饰标签 我仍然收到 请求的资源不支持 HTTP 方法 GET 消息 System Web Mvc AcceptVerbs GET POST System Web Mvc HttpGet public st
  • System.MissingMethodException:找不到方法?

    以前工作的 ASP NET WebForms 应用程序现在抛出此错误 System MissingMethodException 找不到方法 The DoThis方法位于同一个类上 它应该可以工作 我有一个这样的通用处理程序 public
  • 如何在函数中将结构成员作为指针传递?

    问题是我有一个结构是另一个 主要 结构的成员 我编写了一个函数来清除第一个结构 它需要一个指向结构的指针 我想使用该函数来清除主要结构内的结构 但我不确切知道哪种方法是正确的 为了更好地解释它 这里有一些代码 我有一个结构 定义为 type
  • 如何使用 saxon 将文档类型参数传递给 xslt?

    对于发送原子数据类型将使用类似 transformer SetParameter new QName customXml new XdmAtomicValue true 如何将 XML Node 作为参数从 C 传递给 XSLT 你能帮我么
  • 将 Visual Studio 2012 C++ 单元测试项目链接到 exe 会导致访问冲突

    我从现有的整体 exe 本机 Visual Studio 2012 项目开始 我想添加一个本机单元测试项目 根据http msdn microsoft com en us library hh419385 aspx objectRef ht
  • 实体框架 5 不清除导航属性

    我在 Entity Framework 5 中遇到了这个奇怪的问题 我在其中一个实体中有一个导航属性 我想将其设置为null 但由于某种原因 该属性只有在我第二次调用该属性时才会被清除 using var db new Entities v
  • 将 Uploadify 与 Sharepoint 和 .net 结合使用

    我在共享点页面上有一些由 JQuery 生成的 html 我想在这个 html 中使用 uploadify 将文件上传到服务器 亚历山大 https stackoverflow com users 25427 alexander gyosh
  • 如何在 asp .net mvc 2 中对不直接属于我的模型的对象使用 DisplayFor()?

    我确信我在这里遗漏了一些非常简单的东西 我创建了一个自定义日期时间显示模板 使用以下方法时效果很好 但是 我遇到了这样的情况 在部分控件内 我在 for 循环中迭代模型中的对象 我想要一个 DateTime 属性来使用显示模板 但我不知道如
  • DLL 需要访问其应用程序的符号

    在 C 中 DLL 是否可以访问加载它的应用程序的某些符号 我有一个加载插件 dll 的应用程序 这些插件需要访问该应用程序的某些API 是否可以在不创建共享此 API 的新 DLL 的情况下实现此目的 函数指针结构适合这种情况吗 示例 主
  • 使用 microsoft word.interop 删除 Word 文档中的空白页

    我创建了一个Word文档 它使用以下命令生成动态内容词互操作 它有一些分页符之间使用 我面临的问题是 此分页符会创建我不想向用户显示的空白页面 在某些情况下 我需要在那里添加这些分页符以维护页面布局 因此我无法考虑删除这些分页符 但我想要的
  • 如何解决素数函数的大O表示法?

    我正在尝试理解 Big O 表示法 很抱歉 如果我问的问题太明显了 但我似乎无法理解这一点 我有以下 C 代码函数 我正在尝试为其计算 Big O 表示法 for i 2 i lt 100 i for j 2 j lt i j j if i
  • MSBuild 将动态生成的文件复制为项目依赖项的一部分

    我有一个自定义 msbuild 任务 它正在生成一些输出文件到 ProjectA 的输出目录 TargetDir 当前的代码是这样的
  • 那里有更好的 DateTime.Parse 吗? [关闭]

    Closed 此问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 有谁知道有一个库 付费或免费 能够处理比 DateTime Parse 使用的更常见的日期时间格式 能够
  • 为什么 C# 编译的正则表达式比等效的字符串方法更快?

    每次我必须对字符串执行简单的包含或替换操作 其中我正在搜索的术语是固定值 时 我发现如果我获取示例输入并对其进行一些分析 则使用编译的正则表达式是几乎 总是比使用 String 类中的等效方法更快 我尝试过比较多种方法 hs是要搜索的 干草
  • “sizeof”对不完整类型列表结构 C 的无效应用

    我正在尝试实现一种处理页面错误的替换算法 因此 我尝试使用 malloc 创建一个循环链表 但出现以下错误 无效的应用程序sizeof to incomplete typepageInMemory 以下是代码 typedef struct
  • 是否可以从.NET Core中间件检索控制器的操作结果?

    public class UsersController APIControllerBase public UsersController public Client Get return new Client ClientID 1 Las
  • 正则表达式基于组的不同替换?

    所以我对正则表达式比较陌生 并且做了一些练习 我正在玩一个简单的 混淆器 它只是寻找 dot or dot or at or at 不区分大小写 并且在匹配项之前或之后有或没有任意数量的空格 这是针对通常情况的 someemail AT d
  • 为 C++ 类播种 rand()

    我正在开发一个 C 类 它使用rand 在构造函数中 我真的希望这个班级在几乎所有方面都能照顾好自己 但我不知道在哪里播种rand 如果我播种rand 在构造函数中 每次构造我的对象类型的新实例时都会对其进行播种 因此 如果我按顺序创建 3

随机推荐