简体   繁体   English

在数值模拟中使用 std::valarray

[英]Using std::valarray in numerical simulation

I posted a simple n-body class that I have written in C++ here in Code Review.我在 Code Review 的C++中发布了一个简单的 n 体 class 。

There I was told to use std::valarray instead of plain std::array with the goal that I can rewrite some code that looks currently like this有人告诉我使用std::valarray而不是普通的std::array ,目的是我可以重写一些当前看起来像这样的代码

void Particle::update_position() {
    for (unsigned int d = 0; d < DIM; ++d) {
        position[d] += dt*(velocity[d] + a*force_new[d]);
        force_old[d] = force_new[d];
    }
}

to this对此

void Particle::update_position() {
    position += 0.1*(velocity + force_new);
    force_old = force_new;
}

Since I am a beginner in C++ I wrote a short program that uses the std::valarray such that I can learn how to use this data structure.由于我是 C++ 的初学者,因此我编写了一个使用std::valarray的简短程序,以便我可以学习如何使用此数据结构。 The compiler, however, throws a lot of errors and I do not know why.但是,编译器会引发很多错误,我不知道为什么。 I hope you can help me with this.我希望你能帮我解决这个问题。 Here is the small program I wrote:这是我写的小程序:

#include <iostream>
#include <vector>
#include <array>
#include <valarray>

constexpr unsigned int DIM = 2;

struct Particle{
    std::array<std::valarray<double>, DIM> position; 
    std::array<std::valarray<double>, DIM> velocity;
    std::array<std::valarray<double>, DIM> force_new;
    std::array<std::valarray<double>, DIM> force_old;
    void update_position();
};

void Particle::update_position() {
    position += 0.1*(velocity + force_new);
    force_old = force_new;
}

void compute_position(std::vector<Particle>& particles) {
    for (auto& particle: particles) { 
        particle.update_position();
    }
}

void print_data(const std::vector<Particle>& particles) {
    for (const auto& particle: particles) {
        for (const auto& x: particle.position) std::cout << x << " ";
        for (const auto& v: particle.position) std::cout << v << " ";
        for (const auto& F: particle.position) std::cout << F << " ";
        std::cout << std::endl;
    }
}

void init_data(std::vector<Particle>& particles) {
    for (auto& particle: particles) {
        for (const auto& p: particle) {
            p.position = 1.0
            p.velocity = 2.0
            p.force_new = 3.0
            p.force_old = 4.0
        }
    }
}

int main() { 
    const unsigned int n = 10;
    std::vector<Particle> particles(n);
    init_data(particles);
    compute_position(particles);
    print_data(particles); 
    return 0;
}

When I try to compile this code, I get the following errors:当我尝试编译此代码时,出现以下错误:

so.cpp: In member function ‘void Particle::update_position()’:
so.cpp:17:31: error: no match for ‘operator+’ (operand types are ‘std::array<std::valarray<double>, 2>’ and ‘std::array<std::valarray<double>, 2>’)
     position += 0.1*(velocity + force_new);

so.cpp: In function ‘void print_data(const std::vector<Particle>&)’:
so.cpp:29:58: error: no match for ‘operator<<’ (operand types are ‘std::ostream’ {aka ‘std::basic_ostream<char>’} and ‘const std::valarray<double>’)
         for (const auto& x: particle.position) std::cout << x << " ";


so.cpp: In function ‘void init_data(std::vector<Particle>&)’:
so.cpp:38:29: error: ‘begin’ was not declared in this scope
         for (const auto& p: particle) {
                             ^~~~~~~~
so.cpp:38:29: note: suggested alternative:
In file included from so.cpp:4:
/usr/include/c++/8/valarray:1211:5: note:   ‘std::begin’
     begin(const valarray<_Tp>& __va)
     ^~~~~
so.cpp:38:29: error: ‘end’ was not declared in this scope
         for (const auto& p: particle) {

Your Particle members are std::array of std::valarray of double 's , not just a simple std::valarray .您的Particle成员是std::array of std::valarray of double ,而不仅仅是一个简单的std::valarray Meaning, the standard library only provides the operator+ , operator* , [...] for the std::valarray , the rest you need to provide yourself.意思是,标准库只为std::valarray提供operator+operator*[...]您需要自己提供的 rest 。

For instance, the following will solves the first compiler error from the line position += 0.1*(velocity + force_new);例如,以下将解决position += 0.1*(velocity + force_new); ( See live online ) 见在线直播

template<typename ValArray, std::size_t N>
auto operator+(std::array<ValArray, N> lhs, const std::array<ValArray, N>& rhs)
{
   for (std::size_t idx{}; idx < N; ++idx)
      lhs[idx] += rhs[idx];
   return lhs;
}


template<typename ValArray, std::size_t N, typename T>
auto operator*(T constant, std::array<ValArray, N> rhs)
{
   for (std::size_t idx{}; idx < N; ++idx)
      rhs[idx] *= constant;
   return rhs;
}

Or wrap the std::array<std::valarray<double>, DIM> to a class and provide necessary operators, to work on.或者将std::array<std::valarray<double>, DIM>包装到 class 并提供必要的运算符以进行操作。 ( See live online ) 见在线直播

#include <array>
#include <valarray>    

template<typename Type, std::size_t N>
class ValArray2D final
{
   std::valarray<std::valarray<Type>> _arra2D;
public:
   explicit constexpr ValArray2D(const std::valarray<Type>& valArray) noexcept
      : _arra2D{ valArray , N }
   {}

   ValArray2D& operator+=(ValArray2D rhs) noexcept
   {
      _arra2D += rhs._arra2D;
      return *this;
   }

   ValArray2D& operator*(Type constant) noexcept
   {
      for(auto& valArr: this->_arra2D)
         valArr = valArr * constant;
      return *this;
   }

   void operator=(Type constant) noexcept
   {
      for (auto& valArr : this->_arra2D)
         valArr = constant;
   }

   friend ValArray2D operator+(ValArray2D lhs, const ValArray2D& rhs) noexcept
   {
      lhs += rhs;
      return lhs;
   }

   friend std::ostream& operator<<(std::ostream& out, const ValArray2D& obj) noexcept
   {
      for (const std::valarray<Type>& valArray : obj._arra2D)
         for (const Type element : valArray)
            out << element << " ";
      return out;
   }
};

template<typename Type, std::size_t N>
ValArray2D<Type, N> operator*(Type constant, ValArray2D<Type, N> lhs)
{
   return lhs = lhs * constant;
}

First of all, when you write or change code, always start with a first working version, and ensure the code is compiling between each steps.首先,当您编写或更改代码时,请始终从第一个工作版本开始,并确保代码在每个步骤之间进行编译。 That would make isolating miscompiling code much easier.这将使隔离错误编译的代码变得更加容易。

Why Am I telling you this?我为什么要告诉你这些? It's because there are parts of your code that has never compiled correctly.这是因为您的代码中的某些部分从未正确编译过。 No matter before the introduction of valarray or after.无论是在引入 valarray 之前还是之后。

For example, this:例如,这个:

for (auto& particle : particles) {
    for (const auto& p: particle) {
        p.position = 1.0
        p.velocity = 2.0
        p.force_new = 3.0
        p.force_old = 4.0
    }
}

A single particle is not an iterable type, and there is no semicolon at the end of the lines.单个粒子不是可迭代类型,行尾没有分号。

Just take it step by step, and ensure the code is compiling between each steps.只需一步一步地进行,并确保代码在每个步骤之间进行编译。


Second, I don't think valarray is what you're looking for.其次,我不认为 valarray 是你要找的。 Unless you want each particle to have a dynamic number of dimension for each property, which would be very surprising.除非您希望每个粒子的每个属性都具有动态的维数,否则这将是非常令人惊讶的。

I'd suggest you to introduce a vec type that would have the component you need to do your simplation.我建议您引入一种 vec 类型,该类型将具有您进行简化所需的组件。 Such vec type can be found in libraries such as glm provide a vec2 class that has operators such as +-/* and more.这种 vec 类型可以在glm等库中找到,它提供了一个vec2 class,它具有+-/*等运算符。

Even without a library, a simple vector type can be created.即使没有库,也可以创建简单的矢量类型。

Here's an example of your code using vectors (in the matematical sense) instead of std::valarray :这是使用向量(在数学意义上)而不是std::valarray的代码示例:

struct Particle{
    glm::vec2 position; 
    glm::vec2 velocity;
    glm::vec2 force_new;
    glm::vec2 force_old;
    void update_position();
};

void Particle::update_position() {
    position += 0.1*(velocity + force_new);
    force_old = force_new;
}

void compute_position(std::vector<Particle>& particles) {
    for (auto& particle: particles) { 
        particle.update_position();
    }
}

void print_data(const std::vector<Particle>& particles) {
    for (const auto& particle : particles) {
        std::cout << particle.position.x << ", " << particle.position.y << " ";
        std::cout << particle.velocity.x << ", " << particle.velocity.y << " ";
        std::cout << particle.force_new.x << ", " << particle.force_new.y << " ";
        std::cout << std::endl;
    }
}

void init_data(std::vector<Particle>& particles) {
    for (auto& particle : particles) {
        particle.position = {1, 2};
        particle.velocity = {2, 24};
        particle.force_old = {1, 5};
        particle.force_new = {-4, 2};
    }
}

int main() { 
    const unsigned int n = 10;
    std::vector<Particle> particles(n);
    init_data(particles);
    compute_position(particles);
    print_data(particles); 
    return 0;
}

Live example with custom (imcomplete) vec2 type具有自定义(不完整)vec2 类型的实时示例

Okay, let's explain some concepts so you can understand the reason for the errors.好的,让我们解释一些概念,以便您了解错误的原因。

std::valarray vs plain c++ array vs std::array std::valarray 与普通 c++ 数组与 std::array

When we talk about plain c++ array we refer to something like the follow当我们谈论普通的 c++ 阵列时,我们指的是如下内容

double myArray[DIM];

this is just the native form of an array in c++.这只是 c++ 中数组的原生形式。 Then we have std::array然后我们有 std::array

std::array<double, DIM> myStdArry;

this is just a wrapper class around a plain c++ array with the difference that you have access to some utility functions for easier manipulation of the data ie这只是一个简单的 c++ 数组周围的包装器 class ,不同之处在于您可以访问一些实用程序函数以便更轻松地操作数据,即

myStdArry.size() //get the number of elements
myStdArry.begin() & myStdArry.end() //iterators to the begin and end of the array
myStdArry.fill(1.4) //replace all the values of the array to 1.4

For both plain and std array you will have to use subscript operator ([]) in order to access each element of the array ie对于普通数组和标准数组,您必须使用下标运算符 ([]) 才能访问数组的每个元素,即

for (size_t i = 0; i < DIM /*or myStdArry.size()*/; ++i;) {
  myArray[i] = ...;
} 

//Or with range-based for loop

for (auto& element : myArray) {
  element = ...;
}

Due to this, you can't use arithmetic operators directly on the containers (array) since they are not overloaded for them.因此,您不能直接在容器(数组)上使用算术运算符,因为它们没有为它们重载。 This is when valarray comes into the picture since it is was specifically designed for this kind of operation.这是 valarray 出现的时候,因为它是专门为这种操作设计的。 Valarray is just another type of array which overloads arithmetic operators by applying them to each element of the array. Valarray 只是另一种类型的数组,它通过将算术运算符应用于数组的每个元素来重载算术运算符。

ie let's suppose that we would like to square each element of an array.即假设我们想要对数组的每个元素求平方。 With the plain and std array we could achieve it by doing:使用 plain 和 std 数组,我们可以通过以下方式实现:

for (auto& element : myStdArray) {
  element *= element;
}

But with valarray we can simply do:但是使用 valarray 我们可以简单地做到:

myValArray *= myValArray;

and we get the same result.我们得到相同的结果。

Other major difference is that while plain and std array are both fixed size (you have to set its size at compile time) val array can grow in size dynamically, and that's why you don't have to specify its size at declaration but until construction or later另一个主要区别是,虽然普通数组和标准数组都是固定大小的(你必须在编译时设置它的大小),但 val 数组可以动态增长,这就是为什么你不必在声明时指定它的大小,但直到构造或以后

myValArray{DIM} //Or
myValArray.resize(DIM)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM