"展开"递归函数?

Ble*_*der 13 python recursion cuda opencl

我正在用C++编写路径跟踪器,我想尝试将最耗费资源的代码实现到CUDA或OpenCL中(我不确定选择哪一个).

我听说我的显卡的CUDA版本不支持递归,这是我的路径追踪器大量使用的东西.

正如我在Python和C++中编写的那样,我将发布一些简化的Python代码以提高可读性:

def Trace(ray):
  hit = what_object_is_hit(ray)

  if not hit:
    return Color(0, 0, 0)

  newRay = hit.bouceChildRayOffSurface(ray)

  return hit.diffuse * (Trace(newRay) + hit.emittance)
Run Code Online (Sandbox Code Playgroud)

我试图手动展开的功能,并且存在明确的图案(ddiffuseeemittance):

Level 1:  d1 * e1 

Level 2:  d1 * d2 * e2
        + e1

Level 3:  d1 * d2 * d3 * e3
        + d1 * d2 * e2
        + e1

Level 4:  d1 * d2 * d3 * d4 * e4
        + d1 * d2 * d3 * e3
        + d1 * d2 * e2
        + e1
Run Code Online (Sandbox Code Playgroud)

我可能错了,不过......

我的问题是,我将如何在while循环中实现此代码?

我在考虑使用这种格式的东西:

total = Color(0, 0, 0)
n = 1

while n < 10:   # Maximum recursion depth
  result = magical_function()

  if not result:  break

  total += result
  n += 1
Run Code Online (Sandbox Code Playgroud)

我以前从未真正处理过拆解递归函数的任务,所以任何帮助都会非常感激.谢谢!

Hea*_*ink 16

在递归函数中,每次发生递归调用时,调用者的状态都会保存到堆栈中,然后在递归调用完成时恢复.要将递归函数转换为迭代函数,需要将挂起函数的状态转换为显式数据结构.当然,您可以在软件中创建自己的堆栈,但通常可以使用一些技巧来提高代码的效率.

此答案适用于此示例的转换步骤.您可以将相同的方法应用于其他循环.

尾递归转换

我们再来看一下你的代码吧:

def Trace(ray):
  # Here was code to look for intersections

  if not hit:
      return Color(0, 0, 0)

  return hit.diffuse * (Trace(ray) + hit.emittance)
Run Code Online (Sandbox Code Playgroud)

通常,递归调用必须返回到调用函数,因此调用者可以完成它正在做的事情.在这种情况下,呼叫者通过执行加法和乘法来"完成".这产生了类似的计算 d1 * (d2 * (d3 * (... + e3) + e2) + e1)).我们可以利用加法的分布规律和乘法和加法的联合律将计算转化为[d1 * e1] + [(d1 * d2) * e2] + [(d1 * d2) * d3) * e3] + ... .请注意,本系列中的第一项仅涉及迭代1,第二项仅涉及迭代1和2,依此类推.这告诉我们,我们可以动态计算这个系列.此外,这个系列包含了(d1, d1*d2, d1*d2*d3, ...)我们也可以动态计算的系列.把它放回代码中:

def Trace(diffuse, emittance, ray):
  # Here was code to look for intersections

  if not hit: return emittance                            # The complete value has been computed

  new_diffuse = diffuse * hit.diffuse                     # (...) * dN
  new_emittance = emittance + new_diffuse * hit.emittance # (...) + [(d1 * ... * dN) + eN]
  return Trace(new_diffuse, new_emittance, ray)
Run Code Online (Sandbox Code Playgroud)

尾部递归消除

在新循环中,调用者在被调用者完成后无需做任何工作; 它只是返回被调用者的结果.调用者无需完成工作,因此无需保存任何状态!我们可以覆盖旧参数而不是调用,而是回到函数的开头(不是有效的Python,但它说明了这一点):

def Trace(diffuse, emittance, ray):
  beginning:
  # Here was code to look for intersections

  if not hit: return emittance                            # The complete value has been computed

  new_diffuse = diffuse * hit.diffuse                     # (...) * dN
  new_emittance = emittance + new_diffuse * hit.emittance # (...) + [(d1 * ... * dN) + eN]
  (diffuse, emittance) = (new_diffuse, new_emittance)
  goto beginning
Run Code Online (Sandbox Code Playgroud)

最后,我们将递归函数转换为等效循环.剩下的就是用Python语法表达它.

def Trace(diffuse, emittance, ray):
  while True:
    # Here was code to look for intersections

    if not hit: break

    diffuse = diffuse * hit.diffuse                 # (...) * dN
    emittance = emittance + diffuse * hit.emittance # (...) + [(d1 * ... * dN) + eN]

  return emittance
Run Code Online (Sandbox Code Playgroud)