Fortran 嵌套 WHERE 语句

5

我有一份Fortran 90源代码,其中包含嵌套的WHERE语句。存在问题,但似乎很难理解到底发生了什么。我想将其转换为DO-IF结构以进行调试。对我来说不清楚的是如何翻译嵌套的WHERE语句。

所有数组大小相同。

WHERE (arrayA(:) > 0)
    diff_frac(:) = 1.5 * arrayA(:)
    WHERE (diff_frac(:) > 2)
        arrayC(:) = arrayC(:) + diff_frac(:)
    ENDWHERE
ENDWHERE

我的选择A:

DO i=1, SIZE(arrayA)
  IF (arrayA(i) > 0) THEN
    diff_frac(i) = 1.5 * arrayA(i)
    DO j=1, SIZE(diff_frac)
      IF (diff_frac(j) > 2) THEN
          arrayC(j) = arrayC(j) + diff_frac(j)
      ENDIF
    ENDDO
  ENDIF
ENDDO

我的选择是B:
DO i=1, SIZE(arrayA)
  IF (arrayA(i) > 0) THEN
    diff_frac(i) = 1.5 * arrayA(i)        
    IF (diff_frac(i) > 2) THEN
        arrayC(i) = arrayC(i) + diff_frac(i)
    ENDIF        
  ENDIF
ENDDO

谢谢你


看起来(通过测试推断),内部的 where 实际上是在外部 where 掩码所识别的单个元素上操作的 if 块。这应该使转换为 do 很容易,而且似乎你的“选项 B”是正确的。为什么标准允许这种模棱两可的结构而不直接使用 if 是一个好问题。 - agentp
3个回答

4
根据comp.lang.fortran中的帖子"嵌套WHERE结构(特别是Ian的回复),似乎问题中的第一段代码翻译为以下内容:
do i = 1, size( arrayA )
    if ( arrayA( i ) > 0 ) then
        diff_frac( i ) = 1.5 * arrayA( i )
    endif
enddo

do i = 1, size( arrayA )
    if ( arrayA( i ) > 0 ) then
        if ( diff_frac( i ) > 2 ) then
            arrayC( i ) = arrayC( i ) + diff_frac( i )
        endif
    endif
enddo

这与Mark的答案几乎相同,除了第二个掩码部分(见下文)。 F2008 文档中的关键摘录如下:

7.2.3 掩码数组赋值 - WHERE (第161页)

7.2.3.2 掩码数组赋值的解释 (第162页)

... 2. WHERE 构造中的每个语句按顺序执行。

... 4. mask-expr 最多计算一次。

... 8. 在执行作为 where-body-construct 的 WHERE 语句时,控制掩码被确定为具有值 m_c.AND.mask-expr

... 10. 如果 where-assignment-stmtexprvariable 中或 mask-expr 中出现元素操作或函数引用,并且不在非元素函数引用的参数列表中,则该操作仅对控制掩码的 true 值所对应的元素执行或评估该函数。

如果我正确理解上述线程/文档,那么条件diff_frac( i ) > 2arrayA( i ) > 0之后被评估,因此对应于双重IF块(如果我假设Fortran中的A .and. B不指定评估顺序)。
然而,正如上述线程中所提到的,实际行为可能取决于编译器...例如,如果我们使用gfortran5.2、ifort14.0或Oracle fortran 12.4(没有选项)编译以下代码。
integer, dimension(4) :: x, y, z
integer :: i

x = [1,2,3,4]
y = 0 ; z = 0

where ( 2 <= x )
   y = x
   where ( 3.0 / y < 1.001 )  !! possible division by zero
      z = -10
   end where
end where

print *, "x = ", x
print *, "y = ", y
print *, "z = ", z

它们都给出了预期的结果:

x =            1           2           3           4
y =            0           2           3           4
z =            0           0         -10         -10

但是如果我们使用调试选项进行编译

gfortran -ffpe-trap=zero
ifort -fpe0
f95 -ftrap=division  (or with -fnonstd)

当在掩码表达式中求解y(i) = 0时,gfortran和ifort通过浮点异常而中断,而f95则毫无怨言地运行。(根据链接的线程,Cray的行为类似于gfortran/ifort,而NAG/PGI/XLF类似于f95。)


作为一个附注,当我们在WHERE语句中使用“非元素”函数时,控制掩码不适用,所有元素都会在函数评估中使用(根据上述草案第7.2.3.2节第9句)。例如,以下代码:
integer, dimension(4) :: a, b, c

a = [ 1, 2, 3, 4 ]
b = -1 ; c = -1

where ( 3 <= a )
    b = a * 100
    c = sum( b )
endwhere

提供

a =  1 2 3 4
b =  -1 -1 300 400
c =  -1 -1 698 698

这意味着从b的所有元素中获得sum(b) = 698,两个语句按顺序执行。

1
我从网络上收集了信息(对于这篇长帖子感到抱歉!)如果解释有误请告诉我。 - roygvib

2
为什么不呢?
WHERE (arrayA(:) > 0)
    diff_frac(:) = 1.5 * arrayA(:)
ENDWHERE

WHERE (diff_frac(:) > 2 .and. arrayA(:) > 0)
    arrayC(:) = arrayC(:) + diff_frac(:)
ENDWHERE

我不会说使用嵌套的where不能完成,但我不明白为什么必须这样做。如果你一定要转换成do循环,那么翻译就非常简单了。

你自己的尝试表明你认为where是一种循环结构,我认为最好将其视为掩码赋值(这是语言标准中的解释),在其中每个单独的赋值同时发生。现在你可能会考虑将其转换为do concurrent结构。


我绝对喜欢你的方法。与我发布的那个相比,它们完全一样吗?我的观点是嵌套的WHERE是否也被评估。 - rgrun
我也认为它是一样的。 我最后一句话的意思是,即使arrayA为负,diff_frac也将被检查(> 2)。 这就是我的问题。 - rgrun

0

抱歉有点偏题了,但这很有趣。我不确定嵌套的where会如何编译。它甚至可能是那些推动极限的情况之一。

我同意High Performance Mark的观点,即最好将where视为掩码操作,然后不清楚(对我来说)你的“A”或“B”将产生什么结果。我认为他的解决方案应该与您的嵌套where相同。

我的观点:由于这甚至难以辨别,您能否从头开始编写新代码而不是翻译它?不是为了翻译它,而是删除它,忘记它,并编写执行任务的代码。

如果您确切地知道此代码片段需要执行的内容、前置条件和后置条件,则不应该难以实现。如果您不知道,则算法可能太过复杂,因此无论如何都应该重写。在此代码所预期的内容和实际执行的内容之间可能存在微妙差别。您说您已经在调试此代码了。

再次抱歉转换话题,但我认为这可能是那种情况之一,代码最好通过完全重写来实现。


如果你想保留它并仅编写循环以进行调试:为什么不编写它们并比较输出呢?
使用where运行它,然后分别使用"A"和"B"运行它。打印值。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接