0

我过去经常使用ExprTk库,以便进一步处理用 C 语言使用 Mathematica(包含数学表达式)生成的大型输出文件。直到现在,我专门使用这个库来处理产生 type 值的表达式,<double>例如该库通过定义类型完美地工作

typedef exprtk::symbol_table<double> symbol_table_t;
typedef exprtk::expression<double>     expression_t;
typedef exprtk::parser<double>             parser_t;

并将“一切”存储在结构中

struct readInExpression 
{
  double a, b;

  symbol_table_t symbol_table;

  expression_t expression;
};

读取包含变量以及用户定义函数的a文本b文件

double my_function(double a, double b) {
    return a+b;
} 

可以通过以下方式实现

void readInFromFile(readInExpression* f, parser_t* p) {
      std::string file = "xyz.txt";

      std::ifstream ifs(file);
      std::string content( (std::istreambuf_iterator<char>(ifs) ),
                           (std::istreambuf_iterator<char>()    ) );

      f->symbol_table.add_variable("a",f->a);
      f->symbol_table.add_variable("b",f->b);

      f->symbol_table.add_function("my_function",my_function);

      f->expression.register_symbol_table(f->symbol_table);

      p->compile(content,f->expression);
}

然后可以评估读入表达式的任意值,ab使用

double evaluateFile(readInExpression* f, double a, double b) {
    f->a = a;
    f->b = b;

    return f->expression.value();
}

最近,我在尝试处理包含复数和返回类型复数值的函数的文本文件时遇到了问题std::complex<double>。更具体地说,我有一个.txt包含表单表达式的文件

2*m*A0(m*m) + Complex(1.0,2.0)*B0(M*M,0.0,m*m)

其中A0(a)B0(a,b,c)是由高能物理中(张量)环积分的 Passarino-Veltman 约简产生的标量环积分。这些可以在 C 中使用LoopTools进行数值计算,需要注意的是,它们对 、 和 的某些值采用ab数值c。在上面的 typedef 中简单地替换<double>bystd::complex<double> 会在编译时引发大量错误。我不确定 ExprTk 库是否能够处理复数——我知道它不能处理自定义类,但据我了解,它应该能够处理本机数据类型(正如我在这里找到的那​​样, ExprTk 至少能够处理向量,但是考虑到我需要处理的表达式的复杂性,我认为不可能以某种方式以向量的形式重写所有内容,特别是由于代数与复数和向量)。请注意,我也不能将表达式分成实部和虚部,因为我必须针对变量的许多不同值评估表达式。

尽管我之前在文本文件中处理过复数和提到的函数A0(a)B0(a,b,c)但我通过简单地.txt在 C 中包含文件来解决这个问题,使用#include "xyz.txt",在相应的函数中实现,但是,考虑到手头的文本文件的大小,这似乎是不可能的(如果我尝试这样做,编译器会抛出错误)。

有人知道 ExprTk 是否以及如何处理复数?(非常感谢 MWE。)如果不是这样,这里的任何人都可以建议一个不同的数学解析器,它对用户友好并且可以处理 type 的复数std::complex<double>,同时允许定义自己返回的自定义函数如此复杂的价值观?

一个 MWE:

/************/
/* Includes */
/************/

#include <iostream> // Input and Output on the console
#include <fstream> // Input and Output to files

#include <string> // In order to work with strings -- needed to loop through the Mathematica output files

#include "exprtk.hpp" // Parser to evaluate string expressions as mathematical/arithmetic input

#include <math.h> // Simple Math Stuff
#include <gsl/gsl_math.h> // GSL math stuff
#include <complex> // Complex Numbers

/**********/
/* Parser */
/**********/

// Type definitions for the parser
typedef exprtk::symbol_table<double> symbol_table_t; // (%)
typedef exprtk::expression<double>     expression_t; // (%)
typedef exprtk::parser<double>             parser_t; // (%)

/* This struct is used to store certain information of the Mathematica files in
order to later evaluate them for different variables with the parser library. */
struct readInExpression
{
  double a,b; // (%)

  symbol_table_t symbol_table;

  // Instantiate expression
  expression_t expression;
};

/* Global variable where the read-in file/parser is stored. */
readInExpression file;
parser_t parser;

/*******************/
/* Custom function */
/*******************/

double my_function(double a, double b) {
  return a+b;
}

/***********************************/
/* Converting Mathematica Notation */
/***********************************/

/* Mathematica prints complex numbers as Complex(x,y), so we need a function to convert to C++ standard. */
std::complex<double> Complex(double a, double b) { // (%)
  std::complex<double> c(a,b);
  return c;
}

/************************************/
/* Processing the Mathematica Files */
/************************************/

double evaluateFileDoubleValuedInclude(double a, double b) {
    return
    #include "xyz.txt"
    ;
}

std::complex<double> evaluateFileComplexValuedInclude(double a, double b) {
    return
    #include "xyzC.txt"
    ;
}

void readInFromFile(readInExpression* f, parser_t* p) {
  std::string file = "xyz.txt"; // (%)

  std::ifstream ifs(file);
  std::string content( (std::istreambuf_iterator<char>(ifs) ),
                       (std::istreambuf_iterator<char>()    ) );

  // Register variables with the symbol_table
  f->symbol_table.add_variable("a",f->a);
  f->symbol_table.add_variable("b",f->b);

  // Add custom functions to the evaluation list (see definition above)
  f->symbol_table.add_function("my_function",my_function); // (%)
  // f->symbol_table.add_function("Complex",Complex); // (%)

  // Register symbol_table to instantiated expression
  f->expression.register_symbol_table(f->symbol_table);

  // Compile the expression with the instantiate parser
  p->compile(content,f->expression);
}

std::complex<double> evaluateFile(readInExpression* f, double a, double b) { // (%)
  // Set the values of the struct to the input values
  f->a = a;
  f->b = b;

  // Evaluate the result for the upper values
  return f->expression.value();
}

int main() {

    exprtk::symbol_table<std::complex<double> > st1; // Works
    exprtk::expression<std::complex<double> >     e1; // Works
    // exprtk::parser<std::complex<double> >             p1; // Throws an error

    double a = 2.0;
    double b = 3.0;

    std::cout << "Evaluating the text file containing only double-valued functions via the #include method: \n" << evaluateFileDoubleValuedInclude(a,b) << "\n \n";
    std::cout << "Evaluating the text file containing complex-valued functions via the #include method: \n" << evaluateFileComplexValuedInclude(a,b) << "\n \n";

    readInFromFile(&file,&parser);
    std::cout<< "Evaluating either the double-valued or the complex-valued file [see the necessary changes tagged with (%)]:\n" << evaluateFile(&file,a,b) << "\n";

    return 0;
}

xyz.txt

a + b * my_function(a,b)

xyzC.txt

2.0*Complex(a,b) + 3.0*a

要让 MWE 工作,请将exprtk.hpp文件放在您编译的同一文件夹中。

请注意,evaluateFile(...)函数的返回类型可以是 /is std::complex<double>,即使只返回双值类型。// (%)尝试复值文件时,标记为的行可能会发生变化xyzC.txt

实例化exprtk::parser<std::complex<double> >投掷(等等)

./exprtk.hpp:1587:10: error: no matching function for call to 'abs_impl'
                                                              exprtk_define_unary_function(abs  )

而所有其他需要的类型似乎都没有抱怨 type std::complex<double>

4

2 回答 2

1

...这里的任何人都可以建议一个不同的数学解析器,它是用户友好的并且可以处理 std::complex(double) 类型的复数,同时允许定义自己返回此类复杂值的自定义函数?

我只遇到了一个处理复数的数学解析器,Foreval: https ://sourceforge.net/projects/foreval/

实现为 dll 库。您可以以任何形式将“double”和“extended”类型的实数和复数变量连接到 Foreval,并传递变量的地址。具有复杂变量的内置标准函数。此外,您可以将外部函数与复杂变量连接起来。但只有函数中传递和返回的复杂变量的类型是自己的、内部的,并且与 std::complex(double) 不同。源代码中有 GCC 的示例。

有两个缺点: 只有一个 32 位版本的 Foreval.dll(只能连接 32 位程序)。并且仅适用于操作系统 Windows。

但也有优点:Foreval.dll 是一个编译器,可以生成机器代码(快速计算)。有一个带有浮点的实数类型 - “扩展”(“双”也是),也适用于复数。

于 2021-10-05T20:28:01.440 回答
1

我实际上对 ExprTk 几乎一无所知(只是我刚刚在它的文档和它的一些代码中读到的——编辑:现在它的代码更多了),但在我看来你不太可能完成什么你想要不做一些大手术的包裹。

正如您在问题中所展示的,其 API 的基础是专门针对单一数据类型的模板对象。文档说该类型“......可以是任何浮点类型。这包括......任何符合与标准浮点类型兼容的接口(原文如此)的自定义类型。” 不幸的是,它并没有说明他们认为标准浮点类型的接口是什么。如果它包含所有可以接受浮点参数的标准库函数,那么它确实是一个非常大的接口。但是,该发行版包括用于为 MPFR 包创建兼容接口的适配器,它提供了一些必要的概念。

但这里的问题是我怀疑你不想要一个只能处理复数的评估器。在我看来,您希望能够同时处理实数和复数。例如,有些表达式对于实数是明确的,而对于复数则有些随意;这些包括a < bmax(a, b),它们都没有在 C++ 中为复杂类型实现。但是,它们在实值表达式​​中非常常用,因此仅从评估语言中删除它们似乎有点武断。[注 1] ExprTK 确实假设数字类型是出于某些目的而排序的,包括其不正确的 (imho) delta 相等运算符,并且这些比较是导致您收到的许多错误消息的原因。

ExprTK 标头确实正确地确定了这std::complex<double>是一个复杂类型,并对其进行了标记。但是,没有为任何在复数上调用的标准函数提供实现,即使 C++ 包含其中许多的实现。这种缺失基本上是触发其余错误的原因,包括您提到的错误,abs这是 C++为复数实现的数学函数的一个示例。[笔记2]

这并不意味着没有解决方案;只是解决方案将涉及一定数量的工作。因此,第一步可能是根据上面链接的 MPFR 适配器填写复杂类型的实现(尽管并非所有内容都通过_impl方法,如上面关于有序比较运算符所述)。

一种选择是编写自己的“真实或复杂”数据类型;在一个简单的实现中,您可以使用有区别的联合,例如:

template<typename T>
class RealOrComplex {
  public:
    RealOrComplex(T a = 0)
        :  is_complex_(false), value_.real(a) {}
    RealOrComplex(std::complex<T> a)
        : is_complex_(true), value_.cplx(a) {}

    // Operator implementations, omitted

  private:
    bool is_complex_;
    union {
      T real;
      std::complex<T> cmplx;
    } value_;
};

一种可能更简单但更有问题的方法是让实数简单地成为虚部为 0 的复数,然后为任何缺少的标准库数学函数编写垫片。但是您可能还需要填充 Loop 库的大部分内容。

因此,虽然所有这些都是可行的,但实际上这样做,恐怕,对于一个 SO 答案来说,工作量太大了。

由于在 ExprTK 源代码中有一些迹象表明作者知道复数的存在,您可能想直接联系他们以询问未来实现的可能性。


笔记

  1. 如果在复杂参数上尝试这些操作,Mathematica 似乎会 抛出错误。另一方面,MatLab 做出了任意选择:有序比较只查看实部,但最大值(不一致地)通过转换为极坐标然后逐个比较来处理。

  2. 在我看来,强制标准接口被显式配置似乎是一个奇怪的实现选择。当然,允许标准函数作为默认实现会更好,这也将避免不必要的填充,例如显式重新实现log1p和其他标准数学函数。(也许这些对应于某些数学库中已知的不足之处。)

于 2021-05-07T01:59:30.833 回答