我正在Fortran 95中进行一些编码。我想知道使用子程序更改模块中定义的全局变量是否被认为是错误的编程习惯。我倾向于一般只使用纯子程序,但在这种情况下我不能使用“纯”,对吧。作为替代方案,我可以在子例程中定义变量,然后在该子例程内部的过程中使用这些变量,如下例所示。那可以接受吗?
subroutine test(X, Y)
implicit none
integer, parameter(dp) :: kind(0.d0)
integer, parameter(dp) :: r1d3 = 1._dp / 3._dp
real(dp), intent(in) :: X(20)
real(dp), intent(out) :: Y(20)
real(dp) :: f1, f2, f3, f4, f5, f6, f7, f8, f9, f10
real(dp), dimension(20) :: g1, g2, g3, g4, g5, g6, g7, DX
real(dp) :: res(20), jac(20,20)
f1 = exp(- norm2(X(7:12)))
g1 = X(1:6) - r1d3 * sum(X(1:3))
! code to calculate variables f1..., g1...
! functions of X
! f1 ... f10, g1 .. g7, are needed to compute both the residual and the jacobian
call residual(X, res)
condition = ( norm2(res) < tol )
! I do not want to calculate the jacobian if this is not needed. Should I?
if (condition) then
call jacobian(X, jac)
end if
DX = -res
call gesv(jac, DX)
! and so on
contains
pure subroutine residual(X, res)
....
end subroutine residual
pure subroutine jacobian(X, jac)
....
end subroutine jacobian
上面的代码是否写得体面?我可以在同一个子程序中包含残差和雅可比的计算,并在那里进行所有需要的f1 ... g7计算,避免将残差和雅可比定义为内部子程序,但我只想计算雅可比如果需要的话。你觉得怎么样?
我认为以下替代方案也可以起作用:
module EP_integration
implicit none
integer, parameter(dp) :: kind(0.d0)
real(dp), PRIVATE, SAVE :: f1, f2, f3, f4, f5, f6, f7, f8, f9, f10
real(dp), dimension(20), PRIVATE, SAVE :: g1, g2, g3, g4, g5, g6, g7
contains
pure subroutine calc_funcs(X, res)
! calculates f1 .. f10, g1 .. g10 as functions of X
! f1 ... f10, g1 .. g7, are needed to compute both the residual and the jacobian
....
end subroutine calc_funcs
pure subroutine residual(X, res)
....
end subroutine residual
pure subroutine jacobian(X, jac)
....
end subroutine jacobian
end module EP_integration
或者可能USE
主子程序中的模块,而不是使用属性SAVE
。
答案 0 :(得分:3)
我想知道使用子程序更改模块中定义的全局变量是否被认为是错误的编程习惯。
它当然被广泛认为是不好的做法,但我打赌你知道这一点。与往常一样,有特殊情况的论据。就个人而言,我对pi
的全局值没有任何问题,但那时我的程序很少更新。
你的问题的其余部分提示你可能没有正确打包你的数据 - 很长的参数列表告诉我你可能没有定义数据类型来组织你正确级别的数据。
但是,除了那些广泛的陈词滥调之外,很难在你的问题中提供任何类型的好答案。
答案 1 :(得分:2)
全局变量本身并不是问题。问题是变量的可变性,当变量是全局变量时更是如此。
增加代码复杂性的是变量的时间依赖性。这在Pecquet的讲座中得到了很好的解释(如果你能读法语,这门课程就能很好地解释)在这段代码中:
a=b
! some code
a=c
变量a
具有在程序执行期间更改的值。通过使用副作用改变存储器的状态来进行这种改变,并且通常可以避免这种情况。例如,纯函数式编程语言通过禁止可变变量来消除这种复杂性,并且程序比命令式语言更受控制。
如果可以在另一个子例程中修改a
,则更难以了解状态a
。如果您处于多线程程序中,每个线程都可以修改a
,那么它可能会变成一场噩梦。
然而,大多数科学程序都使用了大多数子程序和函数必须使用的某些实体,这通常会引发您的问题。通常,您必须在代码中使用可变全局变量,因此您必须保持它们的一致性。在共轭梯度的情况下,迭代之间的全局变量是 mutable ,但在给定的迭代中它们是常量。全局常量(例如pi)不是问题,因为它们不依赖于时间。因此,您有两个不同的时间刻度:CPU指令的时间刻度和迭代的时间刻度。为了保持对代码的控制,您必须在定义明确的“检查点”(每次迭代结束)中改变全局数据,以便知道在迭代期间全局数据是恒定的
保持一致性的一个简单解决方案是为当前迭代A_current
创建一个全局变量,并为下一次迭代A_next
构建一个变量。在迭代结束时,您复制(或交换指针)A_next
和A_current
。这保证了对于给定的迭代,您可以知道全局状态。
对于更复杂的问题,您可以使用此GitBook中解释的隐含参数参考(IRP)策略 并且您可以使用IRPF90这是一个开源的Fortran代码生成器,我开发并使用我的所有代码来使用这种方法进行编程。
答案 2 :(得分:0)
通常,有很多全局变量会导致代码难以理解且难以维护。因此,有几十个(或数百个)全局变量可能被认为是软件设计不良的症状。
Fortran95有data types个TYPE
关键字。因此,您可以定义复合(和嵌套)data types(由&#34;较小的&#34;或&#34;更简单的&#34;组件构成)并使用它们(可能为abstract data types)。您可以使用一些函数来构建复合数据,以及对其进行操作的其他函数(并更改它们)。
对于固定的arity函数(非变量函数),经验法则是使它们接受少于5到8个正式参数(出于可读性原因,以及人类的cognitive limitations)。 您正在编码不仅适用于计算机,还适用于适用于人类同事(现在或未来,甚至您自己在几个月内)谁需要了解您的源代码。
一些旧的Fortran77代码具有几十个正式参数的函数,但这使得它们不可读。
如果您的管理层允许,我强烈建议至少使用更新版本的Fortran,例如: Fortran2008(有些数字代码今天在C99或C++11编码,可能包含OpenCL或CUDA或OpenMP或{{3非常好的理由;我甚至知道一些数值科学家在Ocaml中编写一些代码...因此可能会考虑转换为更好的语言。)
顺便说一下,如果你没有正式的编程教育,我相信学习一些基础知识是值得的,即使是数值科学家也是如此。阅读OpenACC(并使用一些Scheme实现)将扩大 lot 您的想法,并将改进您的日常Fortran编码。此外,如果您不了解它,请阅读SICP,这与每位数字科学家编写数字代码相关。学习编程需要http://floating-point-gui.de/(或其他类似数学分析的东西),所以请耐心等待。