如何编写尺寸无关的代码?


19

我经常发现自己为给定操作/算法的一维,二维和三维版本编写了非常相似的代码。维护所有这些版本可能会变得乏味。简单的代码生成效果很好,但是似乎认为必须有更好的方法。

有没有一种相对简单的方法可以一次编写一个操作并将其推广到更高或更低的维度?

一个具体的例子是:假设我需要计算频谱空间中速度场的梯度。在三个方面,Fortran循环看起来像:

do k = 1, n
  do j = 1, n
    do i = 1, n
      phi(i,j,k) = ddx(i)*u(i,j,k) + ddx(j)*v(i,j,k) + ddx(k)*w(i,j,k)
    end do
  end do
end do

其中ddx阵列被适当地限定。(也可以使用矩阵乘法来执行此操作。)二维流的代码几乎完全相同,除了:第三维从循环,索引和组件数中删除。有没有更好的表达方式?

另一个例子是:假设我在三维网格上逐点定义了流体速度。为了将速度插值到任意位置(即不对应于网格点),可以在所有三个维度上连续使用一维Neville算法(即降维)。给定简单算法的一维实现,是否存在一种简单的方法来进行降维?

Answers:


13

您看一下Deal.II(http://www.dealii.org/)是如何做到的–维度独立性是库的核心,并且被建模为大多数数据类型的模板参数。例如,请参阅第4步教程程序中与尺寸无关的Laplace求解器:

http://www.dealii.org/developer/doxygen/deal.II/step_4.html

也可以看看

https://github.com/dealii/dealii/wiki/Frequently-Asked-Questions#why-use-templates-for-the-space-dimension


我非常同意。我没有找到比Deal.II更好的方法。他们以一种非常有趣的方式使用模板来解决此问题。
Eldila'3

1
一个很好的资源,但是如果您不熟悉C ++模板,那将非常吓人。
meawoppl 2012年

@Wolfgang Bangerth:Deal.ii是否也使用模板定义迭代器?
马修·埃米特

@MatthewEmmett:是的。
Wolfgang Bangerth 2012年

@meawoppl:其实没有。我会定期讲授交易课程。II,一开始只是简单地告诉学生说ClassWhatever <2>在2d中,ClassWhatever <3>在3d中以及ClassWhatever <dim>在dim-d中的所有内容。我将在第3周的某个地方上模板课程,虽然学生可能在此之前不了解它是如何工作的,但他们还是可以使用它的全部功能。
Wolfgang Bangerth 2012年

12

这个问题突出表明,大多数“普通”编程语言(至少是C,Fortran)不允许您简洁地执行此操作。一个附加的约束是您想要符号上的便利良好的性能。

因此,与其编写特定于维度的代码,不如考虑编写生成特定于维度的代码的代码。该生成器与维度无关,即使计算代码并非如此。换句话说,您可以在符号和表示计算的代码之间添加一层推理。C ++模板等同于同一件事:好的方面,它们是直接内置在该语言中的。不利的一面是,它们编写起来有些麻烦。这减少了如何实际实现代码生成器的问题。

OpenCL使您可以在运行时相当干净地进行代码生成。它还可以在“外部控制程序”和“内部循环/内核”之间进行非常清晰的划分。外部生成程序的性能受到的限制要小得多,因此最好使用像Python这样的舒适语言编写。这是我对PyOpenCL的使用方式的希望-对不起,对于新的无耻插件。


安德烈亚斯!欢迎来到scicomp!很高兴能邀请您访问该网站,如果您有任何疑问,我想您知道如何与我联系。
阿隆·艾玛迪亚

2
+10000用于自动代码生成,以解决此问题,而不是C ++魔术。
杰夫

9

可以使用以下粗略的心理原型以任何语言完成此操作:

  1. 创建每个维度的范围的列表(类似于我认为的MATLAB中的shape())
  2. 在每个维度中创建您当前位置的列表。
  3. 在每个维度上编写一个循环,其中包含一个循环,该循环基于外部循环来改变尺寸。

从那里开始,就是要与某种语言的语法作斗争,以使代码与代码保持兼容。

编写了n维流体动力学求解器后,我发现使用支持将像对象这样的列表作为函数参数解压缩的语言会很有帮助。即a =(1,2,3)f(a *)-> f(1,2,3)。另外,高级的迭代器(例如numpy中的ndenumerate)使代码更干净。


用于执行此操作的Python语法看起来很简洁。我想知道是否有很好的方法可以使用Fortran做到这一点……
Matthew Emmett 2012年

1
在Fortran中处理动态内存有点痛苦。可能是我对语言的主要抱怨。
meawoppl 2012年

5

n1×n2×n3nj=1


因此,要与维度无关,需要为maxdim + 1维度编写代码,其中maxdim是用户可能遇到的最大可能维度。假设maxdim = 100。结果代码有多有用?
杰夫

4

如果要保持Fortran的速度,明确的答案是使用具有适当代码生成能力的语言,例如Julia或C ++。已经提到了C ++模板,因此在这里我将提及Julia的工具。Julia的生成函数使您可以使用其元编程通过类型信息按需构建函数。所以本质上您可以在这里做

@generated function f(x)
   N = ndims(x)
   quote
     # build the code for the function
   end
end

然后使用N来以编程方式构建要执行的代码,因为它是N维度的。然后,可以为维函数轻松构建Julia的笛卡尔库Einsum.jl表达式之类的包N

Julia在这里的好处是,此函数是针对您使用的每个新维数组进行静态编译和优化的,因此它的编译量不会超过您所需的数量,但它将为您提供C / Fortran速度。最后,这类似于使用C ++模板,但是它是一种高级语言,带有许多使它变得更容易的工具(足够容易,对于本科生来说这是一个不错的作业问题)。

另一种对此有益的语言是像Common Lisp这样的Lisp。它易于使用,因为像Julia一样,它为您提供了带有许多内置自省工具的已编译AST,但与Julia不同的是,它不会自动编译(在大多数发行版中)。


1

我在同一艘(Fortran)船上。一旦有了1D,2D,3D和4D(我有射影几何)元素,便会为每种类型创建相同的运算符,然后用高级方程式编写逻辑,以清楚地说明发生了什么。它并没有您想像的那样慢,每个操作都有单独的循环和大量的内存副本。我让编译器/处理器进行优化。

例如

interface operator (.x.)
    module procedure cross_product_1x2
    module procedure cross_product_2x1
    module procedure cross_product_2x2
    module procedure cross_product_3x3
end interface 

subroutine cross_product_1x2(a,b,c)
    real(dp), intent(in) :: a(1), b(2)
    real(dp), intent(out) :: c(2)

    c = [ -a(1)*b(2), a(1)*b(1) ]
end subroutine

subroutine cross_product_2x1(a,b,c)
    real(dp), intent(in) :: a(2), b(1)
    real(dp), intent(out) :: c(2)

    c = [ a(2)*b(1), -a(1)*b(1) ]
end subroutine

subroutine cross_product_2x2(a,b,c)
    real(dp), intent(in) :: a(2), b(2)
    real(dp), intent(out) :: c(1)

    c = [ a(1)*b(2)-a(2)*b(1) ]
end subroutine

subroutine cross_product_3x3(a,b,c)
    real(dp), intent(in) :: a(3), b(3)
    real(dp), intent(out) :: c(3)

    c = [a(2)*b(3)-a(3)*b(2), a(3)*b(1)-a(1)*b(3), a(1)*b(2)-a(2)*b(1)]
end subroutine

用于方程式

m = e .x. (r .x. g)  ! m = e×(r×g)

其中,erg可以使数学意义上的维度。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.