【问题标题】:Generic maximum/minimum function for complex numbers复数的通用最大/最小函数
【发布时间】:2021-02-18 13:23:29
【问题描述】:

在 julia 中,可以在实数集合上找到(据说)min/minimummax/maximum 的有效实现。 由于这些概念不是为复数唯一定义的,我想知道这些函数的参数化版本是否已经在某处实现。

我目前正在对感兴趣的数组的元素进行排序,然后取最后一个值,据我所知,这比找到具有最大绝对值(或其他)的值要昂贵得多。

这主要是为了在复杂数组上重现 max 函数的 Matlab 行为。

这是我当前的代码

a = rand(ComplexF64,4)
b = sort(a,by  = (x) -> abs(x))
c = b[end]

可能的函数调用如下所示

c = maximum/minimum(a,by=real/imag/abs/phase)

编辑使用提供的解决方案在 Julia 1.5.3 中进行一些性能测试

function maxby0(f,iter)
    b = sort(iter,by  = (x) -> f(x))
    c = b[end]
end

function maxby1(f, iter)
    reduce(iter) do x, y
        f(x) > f(y) ? x : y
    end
end

function maxby2(f, iter; default = zero(eltype(iter)))
    isempty(iter) && return default
    res, rest = Iterators.peel(iter)
    fa = f(res)
    for x in rest
        fx = f(x)
        if fx > fa
            res = x
            fa = fx
        end
    end

    return res
end

compmax(CArray) = CArray[ (abs.(CArray) .== maximum(abs.(CArray))) .& (angle.(CArray) .== maximum( angle.(CArray))) ][1]


Main.isless(u1::ComplexF64, u2::ComplexF64) = abs2(u1) < abs2(u2)

function maxby5(arr)
    arr_max = arr[argmax(map(abs, arr))]
end

a = rand(ComplexF64,10)

using BenchmarkTools
@btime maxby0(abs,$a)
@btime maxby1(abs,$a)
@btime maxby2(abs,$a)
@btime compmax($a)
@btime maximum($a)
@btime maxby5($a)

长度为 10 的向量的输出:

>841.653 ns (1 allocation: 240 bytes)
>214.797 ns (0 allocations: 0 bytes)
>118.961 ns (0 allocations: 0 bytes)
>Execution fails
>20.340 ns (0 allocations: 0 bytes)
>144.444 ns (1 allocation: 160 bytes)

长度为 1000 的向量的输出:

>315.100 μs (1 allocation: 15.75 KiB)
>25.299 μs (0 allocations: 0 bytes)
>12.899 μs (0 allocations: 0 bytes)
>Execution fails
>1.520 μs (0 allocations: 0 bytes)
>14.199 μs (1 allocation: 7.94 KiB)

长度为 1000 的向量的输出(与 abs2 进行的所有比较):

>35.399 μs (1 allocation: 15.75 KiB)
>3.075 μs (0 allocations: 0 bytes)
>1.460 μs (0 allocations: 0 bytes)
>Execution fails
>1.520 μs (0 allocations: 0 bytes)
>2.211 μs (1 allocation: 7.94 KiB)

一些备注:

  • 清晰排序(如预期)会减慢操作速度
  • 使用abs2 可以节省大量性能(也是预期的)

总结:

  • 由于内置函数将在 1.7 中提供此功能,因此我将避免使用额外的 Main.isless 方法,尽管它被认为是性能最好的方法,但不要修改我的 julia 的核心
  • maxby1maxby2 不分配任何内容
  • maxby1 感觉更具可读性

因此获胜者是 Andrej Oskin!

编辑 n°2 使用更正的 compmax 实现的新基准

julia> @btime maxby0(abs2,$a)
  36.799 μs (1 allocation: 15.75 KiB)
julia> @btime maxby1(abs2,$a)
  3.062 μs (0 allocations: 0 bytes)
julia> @btime maxby2(abs2,$a)
  1.160 μs (0 allocations: 0 bytes)
julia> @btime compmax($a)
  26.899 μs (9 allocations: 12.77 KiB)
julia> @btime maximum($a)
  1.520 μs (0 allocations: 0 bytes)
julia> @btime maxby5(abs2,$a)
2.500 μs (4 allocations: 8.00 KiB)

【问题讨论】:

    标签: julia


    【解决方案1】:

    在 Julia 1.7 中,您可以使用 argmax

    julia> a = rand(ComplexF64,4)
    4-element Vector{ComplexF64}:
      0.3443509906876845 + 0.49984979589871426im
      0.1658370274750809 + 0.47815764287341156im
      0.4084798173736195 + 0.9688268736875587im
     0.47476987432458806 + 0.13651720575229853im
    
    julia> argmax(abs2, a)
    0.4084798173736195 + 0.9688268736875587im
    

    由于到 1.7 需要一些时间,您可以使用以下模拟

    maxby(f, iter) = reduce(iter) do x, y
                       f(x) > f(y) ? x : y
                     end
    julia> maxby(abs2, a)
    0.4084798173736195 + 0.9688268736875587im
    

    UPD:找到这样的最大值稍微更有效的方法是使用类似

    function maxby(f, iter; default = zero(eltype(iter)))
        isempty(iter) && return default
        res, rest = Iterators.peel(iter)
        fa = f(res)
        for x in rest
            fx = f(x)
            if fx > fa
                res = x
                fa = fx
            end
        end
    
        return res
    end
    

    【讨论】:

    • 只是为了澄清一点。你的意思是argmax 目前还没有将函数作为参数处理的方法?
    • 另外,将dims 选项传递给maxby 的正确方法是什么?
    • 是的,1.5 中没有函数作为参数(我认为是 1.6)。我认为最简单的 dims 方法是遍历 CartesianIndices。自己从来没有做过,对不起。
    【解决方案2】:

    根据 octave 的文档(可能模仿 matlab 的行为):

     For complex arguments, the magnitude of the elements are used for
     comparison.  If the magnitudes are identical, then the results are
     ordered by phase angle in the range (-pi, pi].  Hence,
    
          max ([-1 i 1 -i])
              => -1
    
     because all entries have magnitude 1, but -1 has the largest phase
     angle with value pi.
    

    因此,如果您想准确地模拟 matlab/octave 功能,那么基于此逻辑,我将为复数构造一个“max”函数:

    function compmax( CArray )
        Absmax   = CArray[   abs.(CArray) .== maximum(  abs.(CArray)) ]
        Totalmax = Absmax[ angle.(Absmax) .== maximum(angle.(Absmax)) ]
        return Totalmax[1]
    end
    

    (根据需要添加适当的类型)。

    例子:

    Nums0 = [ 1, 2, 3 + 4im, 3 - 4im, 5 ];   compmax( Nums0 )
    # 1-element Array{Complex{Int64},1}:
    #  3 + 4im
    
    Nums1 = [ -1, im, 1, -im ];   compmax( Nums1 )
    # 1-element Array{Complex{Int64},1}:
    #  -1 + 0im
    

    【讨论】:

    • 虽然您的实现在您的示例上运行良好,但它似乎不适用于随机选择的复数...
    • @BamboOo 你是对的,我在逻辑中有一个错误(将最大角度应用于整个数组,而不是应用于已经是最大幅度的子集)。现在修好了。虽然很明显,根据公认的答案,如果您不关心与 matlab 兼容的行为,那么遵循这个逻辑并不是特别重要。 :)
    • 假设我希望有一个默认行为,但能够在需要时更改它。在 matlab 中,实际上并没有提到此类比较中的阶段,尽管 matlab 可能也有这种行为。
    • @BamboOo 是的,octave 在他们的文档中更清楚地说明了这一点。 Matlab'暗示'这就是它在它提供的示例中所做的,但并没有太明确地表明这是他们的规范......
    • 刚加的,比别人效率不高但比我自己的好:)
    【解决方案3】:

    如果这是我的计算代码,我会通过以下方式让我的生活变得更简单:

    julia> Main.isless(u1::ComplexF64, u2::ComplexF64) = abs2(u1) < abs2(u2)
    
    julia> maximum(rand(ComplexF64, 10))
    0.9876138798492835 + 0.9267321874614858im
    

    这为Main 中的现有方法添加了一个新实现。因此,对于库代码来说,这不是一个优雅的想法,但它会让您以最少的努力到达您需要的地方。

    【讨论】:

      【解决方案4】:

      复数的“大小”由其模数的大小决定。您可以为此使用 abs。或者像Andrej Oskin 所说的那样得到1.7。

      julia> arr = rand(ComplexF64, 10)
      10-element Array{Complex{Float64},1}:
       0.12749588414783353 + 0.09918182087026373im
        0.7486501790575264 + 0.5577981676269863im
        0.9399200789666509 + 0.28339836191094747im
        0.9695470502095325 + 0.9978696209350371im
        0.6599207157942191 + 0.0999992072342546im
       0.30059521996405425 + 0.6840859625686171im
       0.22746651600614132 + 0.33739559003514885im
        0.9212471084010287 + 0.2590484924393446im
          0.74848598947588 + 0.41129443181449554im
        0.8304447441317468 + 0.8014240389454632im
      
      julia> arr_max = arr[argmax(map(abs, arr))]
      0.9695470502095325 + 0.9978696209350371im
      
      julia> arr_min = arr[argmin(map(abs, arr))]
      0.12749588414783353 + 0.09918182087026373im
      

      【讨论】:

      • 虽然复数的“大小”(即其范数)是唯一定义的,但对复数进行排序却不是
      • 您也可以使用广播代替地图。出于性能原因,abs2 比 abs 好,因为取平方根很昂贵。 `arr[argmax(abs2.(arr))]`
      • @BamboOo 老实说,为什么不呢?在我看来,按大小/大小/绝对值对常规旧数字进行排序的自然方式也适用于复数。这不是真的有数学上的原因吗?您是否暗示“按字典顺序”对复杂进行排序会更合适?
      • @blorgon 我也倾向于使用复数的大小。然而,dsp.stackexchange.com/questions/22807/… 的讨论让我深信不疑。而且由于实现一个功能可能同样困难,为什么不让用户根据需要进行排序
      • 如果在您的用例中,按abs 对实数进行排序是有意义的,那么按范数对复数进行排序就可以了。不过,您可能对排序的稳定性感兴趣 (en.wikipedia.org/wiki/Category:Stable_sorts)。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-01-27
      • 2015-10-29
      • 1970-01-01
      • 2013-09-28
      相关资源
      最近更新 更多