【问题标题】:Partial Derivatives偏导数
【发布时间】:2011-09-05 06:27:21
【问题描述】:

我正在尝试编写一个算法来执行 N 维混合偏导数。我知道我需要能够实现什么,但我似乎无法提出实现 N 维案例所需的正确循环/递归

这是前 4 个维度的模式:

| 1D  wzyx  | 2D           | 3D           | 4D           |
----------------------------------------------------------
| dx (0001) | dx    (0001) | dx    (0001) | dx    (0001) |
|           | dy    (0010) | dy    (0010) | dy    (0010) |
|           | dyx   (0011) | dyx   (0011) | dyx   (0011) |
|           |              | dz    (0100) | dz    (0100) |
|           |              | dzx   (0101) | dzx   (0101) |
|           |              | dzy   (0110) | dzy   (0110) |
|           |              | dzyx  (0111) | dzyx  (0111) |
|           |              |              | dw    (1000) |
|           |              |              | dwx   (1001) |
|           |              |              | dwy   (1010) |
|           |              |              | dwyx  (1011) |
|           |              |              | dwz   (1100) |
|           |              |              | dwzx  (1101) |
|           |              |              | dwzy  (1110) |
|           |              |              | dxyzw (1111) |

每个维度的导数个数(因为它遵循二进制模式)是(2^dim)-1;例如,2^3 = 8 - 1 = 7。

dyx的导数是y维度上相邻点的dx值。这适用于所有混合部分。所以 dzyx 是 z 维中相邻点的 dyx。我不确定这一段是否是该问题的相关信息,只是为了完整起见,我想把它放在这里。

欢迎任何帮助指针建议。粗体部分是我需要实现的部分。

::EDIT::

我将通过提供我需要的示例来尝试更明确一点。这只是一个 2D 案例,但它在某种程度上体现了我认为的整个过程。

我需要帮助想出将在 dx、dy、dyx 等列中生成值的算法。人。

|  X  |  Y  | f(x, y) |  dx             |  dy       | dyx               |
-------------------------------------------------------------------------
|  0  |  0  |    4    |  (3-4)/2 = -0.5 |  (3-4)/2  | (-0.5 - (-2.0))/2 |
|  1  |  0  |    3    |  (0-4)/2 = -2.0 |  (2-3)/2  | (-2.0 - (-2.0))/2 |
|  2  |  0  |    0    |  (0-3)/2 = -1.5 | (-1-0)/2  | (-1.5 - (-1.5))/2 |
|  0  |  1  |    3    |  (2-3)/2 = -0.5 |  (0-4)/2  | (-0.5 - (-0.5))/2 |
|  1  |  1  |    2    | (-1-3)/2 = -2.0 | (-1-3)/2  | (-1.5 - (-2.0))/2 |
|  2  |  1  |   -1    | (-1-2)/2 = -1.5 | (-4-0)/2  | (-1.5 - (-1.5))/2 |
|  0  |  2  |    0    | (-1-0)/2 = -0.5 |  (0-3)/2  | (-0.5 - (-0.5))/2 |
|  1  |  2  |   -1    | (-4-0)/2 = -2.0 | (-1-2)/2  | (-2.0 - (-2.0))/2 |
|  2  |  2  |   -4    |(-4--1)/2 = -1.5 |(-4--1)/2  | (-1.5 - (-1.5))/2 |

f(x, y) 是未知的,只有它的值是已知的;所以解析微分没用,只能是数字。

欢迎任何帮助指针建议。粗体部分是我需要实现的部分。

::EDIT - AGAIN::

在这里开始一个要点:https://gist.github.com/1195522

【问题讨论】:

  • 你是问如何生成(0101 ...)的列表吗?
  • 我觉得这个问题适合codegolf.stackexchange.com
  • @Ryan,好吧,如果你用你的位串制作一个向量e,然后找到|f(x + e) - f(x)| / |e|,那是那个方向的部分——这就是你要找的吗?跨度>
  • 我羡慕你能解决像这样有趣的问题。
  • @amod0017 如果不是以可赢比赛的形式,那它真的不适合codegolf.se

标签: c++ algorithm math


【解决方案1】:

这个问题被函数式编程彻底解决了。实际上,\partial_{xy}f 是 \partial_y f 沿 x 的偏导数。

我假设您有一个黑盒函数(或函数对象)f,将其值作为指向内存缓冲区的指针。它的签名被假定为

double f(double* x);

现在,这里是获取 f 的(二阶有限差分)偏导数的代码:

template <typename F>
struct partial_derivative
{
    partial_derivative(F f, size_t i) : f(f), index(i) {}

    double operator()(double* x)
    {
        // Please read a book on numerical analysis to tweak this one
        static const double eps = 1e-4;

        double old_xi = x[index];
        x[index] = old_xi + eps;
        double f_plus = f(x);

        // circumvent the fact that a + b - b != a in floating point arithmetic
        volatile actual_eps = x[index];
        x[index] = old_xi - eps;
        actual_2eps -= x[index]
        double f_minus = f(x);

        return (f_plus - f_minus) / actual_2eps;
    }

private:
    F f;
    size_t index;
};

template <typename F>
partial_derivative<F> partial(F f, index i)
{
    return partial_derivative<F>(f, i);
}

现在,要计算 \partial_{123}f,您可以:

boost::function<double(double*)> f_123 = partial(partial(partial(f, 0), 1), 2);

如果您需要全部计算:

template <typename F>
boost::function<double(double*)> mixed_derivatives(F f, size_t* i, size_t n_indices)
{
    if (n_indices == 0) return f;
    else return partial(mixed_derivatives(f, i + 1, n_indices - 1), i[0]);
}

所以你可以这样做:

size_t indices[] = { 0, 1, 2 };
boost::function<double(double*)> df_123 
    = mixed_derivatives(f, indices, sizeof(indices) / sizeof(size_t));

【讨论】:

  • 我对这个答案投了赞成票,因为我认为它有效。不过,我不太了解 boost::function,因此我不确定如何使它适用于未知数量的维度。另外,我不知道函数,所以double f(double* x) 的假设是无效的。我所拥有的只是一个代表函数输出的点数组。
【解决方案2】:

如果我理解正确,我认为以下方法可以工作:

function partial_dev(point, dimension):
    neighbor_selector = top_bit(dimension)
    value_selector = dimension XOR neighbor_selector
    prev_point = point_before(point,neighbor_selector)
    next_point = pointafter(point,neighbor_selector)
    if value_selector == 0:
        return (f[prev_point] - f[next_point])/2
    else:
        return ( partial_dev(prev_point, value_selector) -
                 partial_dev(next_point, value_selector) )/2

这个想法是:维度值的最高位是选择前后点的坐标。如果其余维度值为 0,则使用 f 值作为点的偏导数计算。如果不是,则得到其余位表示的偏导数来计算值。

如果您需要计算所有维度值的所有值,那么您根本不需要递归:只需将维度选择器用作数组索引,其中每个数组元素都包含为该维度设置的完整值。该数组被初始化为vals[0][coords] = f(coords)。然后你计算vals[1]vals[2],计算vals[3]时,你使用vals[1]作为值表而不是vals[0](因为3 = 0b11,其中邻居选择器是0b10,value_selector是0b01)。

【讨论】:

  • 我现在正在努力实际实现这一点。但我现在已将其标记为答案。我将使用实际实现的代码更新问题中链接的要点。
【解决方案3】:

您似乎可以只根据维度(二进制位数)进行循环,然后递归到下一个二进制数字。

粗略(非 C++)伪代码:

Function partialcalc(leadingdigit, dimension)

  If dimension > 1 {
    For i = 1 to dimension {
      //do stuff with these two calls
      partialcalc(0, i - 1)
      partialcalc(1, i - 1)
    }
  }
  Else {
    //partialcalc = 1D case
  }

return partialcalc

End Function

递归的工作方式是你有一个问题,它可以分解成与更大问题等效的子问题,只是更小。因此,由于您将所有二进制数字用于维度位置,因此您只需通过基于维度数字中的 0 和 1 值递归到两个子问题来对顶部维度进行计算。递归的底部是维度 = 1 级别。由于您强调您只需要弄清楚如何构造循环递归,并且已经弄清楚了数学,因此这种结构应该适合您。

【讨论】:

  • 如果有人想把它变成 C++ 形状,那就去吧。对我来说太久了。
  • Lance 对不起,我没有看到 leadingdigit 在这段代码中做了什么?此外,计算完成后,它如何知道从哪里获取数据以及将数据放在哪里?
  • @Ryan, leadingdigit 是维度级别的第一个(最左边)二进制数字,因此它首先求解所有 0 数字,向下递归,然后求解所有 1 数字,向下递归。 stuff 是您执行您已经弄清楚如何生成数据的工作的地方。虽然在 VBA 中我会使用重新定义数组来存储数据,但我想在 C++ 中你会使用某种类型的指针结构。
【解决方案4】:

嗯,从答案开始,我首先想到的是用切比雪夫多项式进行插值。然后可以很容易地对近似值进行微分(或积分)。

Gnu Scientific Library 有一个实现。

注意,我不是数值方法方面的专家,所以我无法解释这种方法的问题。但是,如果您想要局部近似值,它应该可以很好地工作。如果有人知道得更好,请随时投反对票。

【讨论】:

  • 我在物理研究中使用切比雪夫多项式,这对我来说似乎是一个不错的建议。
  • 我确信切比雪夫多项式非常擅长执行导数。但这并没有真正解决能够计算 N 维导数的问题。导数不是问题。
  • 切比雪夫多项式适用于一维函数。此外,“如果您想要局部近似,它应该工作得足够好”没有抓住重点:当您想要全局近似时,切比雪夫近似很有用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多