【问题标题】:Calculating the angle between two vectors using a needle-like triangle使用针状三角形计算两个向量之间的角度
【发布时间】:2021-11-25 22:09:31
【问题描述】:

我实现了一个函数 (angle_between) 来计算两个向量之间的角度。它利用针状三角形,基于Miscalculating Area and Angles of a Needle-like Trianglethis related question

该功能在大多数情况下似乎都可以正常工作,除了一种我不明白发生了什么的奇怪情况:

import numpy as np
vectorA = np.array([0.008741225033460295, 1.1102230246251565e-16], dtype=np.float64)
vectorB = np.array([1, 0], dtype=np.float64)
angle_between(vectorA, vectorB)  # is np.nan

深入我的函数,np.nan 是通过取负数的平方根产生的,负数似乎是该方法提高准确性的结果:

foo = 1.0                  # np.linalg.norm(vectorA)
bar = 0.008741225033460295 # np.linalg.norm(vectorB)
baz = 0.9912587749665397   # np.linalg.norm(vectorA- vectorB)

# algebraically equivalent ... numerically not so much
order1 = baz - (foo - bar)
order2 = bar - (foo - baz)

assert order1 == 0
assert order2 == -1.3877787807814457e-17

根据 Kahan 的论文,这意味着三元组 (foo, bar, baz) 实际上并不代表三角形的边长。然而,考虑到我是如何构造三角形的,这应该——事实上——是这种情况(参见代码中的 cmets)。

从这里开始,我有点迷失在哪里寻找错误的根源。有人可以向我解释发生了什么吗?


为了完整起见,这里是我的函数的完整代码:

import numpy as np
from numpy.typing import ArrayLike

def angle_between(
    vec_a: ArrayLike, vec_b: ArrayLike, *, axis: int = -1, eps=1e-10
) -> np.ndarray:
    """Computes the angle from a to b

    Notes
    -----
    Implementation is based on this post:
    https://scicomp.stackexchange.com/a/27694
    """

    vec_a = np.asarray(vec_a)[None, :]
    vec_b = np.asarray(vec_b)[None, :]

    if axis >= 0:
        axis += 1

    len_c = np.linalg.norm(vec_a - vec_b, axis=axis)
    len_a = np.linalg.norm(vec_a, axis=axis)
    len_b = np.linalg.norm(vec_b, axis=axis)

    mask = len_a >= len_b
    tmp = np.where(mask, len_a, len_b)
    np.putmask(len_b, ~mask, len_a)
    len_a = tmp

    mask = len_c > len_b
    mu = np.where(mask, len_b - (len_a - len_c), len_c - (len_a - len_b))

    numerator = ((len_a - len_b) + len_c) * mu
    denominator = (len_a + (len_b + len_c)) * ((len_a - len_c) + len_b)

    mask = denominator > eps
    angle = np.divide(numerator, denominator, where=mask)
    np.sqrt(angle, out=angle)
    np.arctan(angle, out=angle)
    angle *= 2
    np.putmask(angle, ~mask, np.pi)
    return angle[0]

编辑:问题肯定与float64有关,并且在使用更大的浮点数执行计算时消失:

import numpy as np

vectorA = np.array([0.008741225033460295, 1.1102230246251565e-16], dtype=np.float128)
vectorB = np.array([1, 0], dtype=np.float128)
assert angle_between(vectorA, vectorB) == 0

【问题讨论】:

  • 回想一下,如果三个边满足强三角形不等式,则构成一个三角形,即两个短边之和必须严格大于长边。但自从bar + baz == 1 == foo 之后,你就不是这样了。
  • @user2640045 我猜bar + baz == 1 == foo 来自浮点不准确? vectorAvectorBvectorA - vectorB 三个向量的长度应该总是形成一个有效的三角形,不是吗?除此之外,函数应该正确处理vectorA == vectorBvectorA == -vectorB 这两种退化情况。前者len_c为0,后者为np.putmask(angle, ~mask, np.pi)
  • 不,当vectorA 和vectorB 是彼此的倍数时,也会出现这种情况。这里几乎就是这种情况。如果我将1.1102230246251565e-16 替换为零。他们将是。我想1.1102230246251565e-16 与零的差异不足以避免这个问题。
  • @user2640045 我刚刚尝试了将vectorB 设置为vectorA 的倍数的情况,有趣的是,它有时会产生nan,有时会产生0,有时它会失败并产生一个小的幅度角1e-8 ...任何想法为什么?

标签: python numpy geometry numeric floating-accuracy


【解决方案1】:

我刚刚尝试了将 vectorB 设置为 vectorA 的倍数的情况 - 有趣的是 - 它有时会产生 nan,有时会产生 0,有时会失败并产生 1e-8 大小的小角度......有什么想法吗?

是的,我认为这就是您的问题归结为的原因。这是您一直在使用的the berkeley paper due to Kahan 的公式。 假设a≥ba≥c(只有这样公式才有效)和b+c≈a。 如果我们暂时忽略mu 并查看平方根下的所有其他内容,它一定都是正数,因为a 是最长的边。而muc-(a-b),即0 ± a small error。如果该错误为零,您将得到零,即顺便说一句。正确的结果。如果误差为负,则平方根给你 nan,如果误差为正,你得到一个小角度。

请注意,当b+c-a 不为零但小于错误时,相同的参数有效。

【讨论】:

  • @wikikikitiki 我们昨天才刚刚谈到这个是不是很奇怪?
  • 嗯,在这种情况下,避免nan 的首选方法是什么?想到的两种策略是:(1)将mu 剪辑为0,或(2)将其设置为0,如果eps 接近它。但是,我对两者都没有很好的动机,除了他们觉得很自然。
  • 如何检查强三角不等式是否满足。如果不是,即 a≈b+c,如果 vec_a 和 vec_b 在 pi 上指向相同方向,则返回 0;如果它们指向相反方向,则返回 180°。您可以确定它们是否与点积指向相同的方向。如果为正,则它们指向相同的方向,如果为负,则它们指向相反的方向。
  • @FirefoxMetzger 顺便说一句。您还可以检查向量是否是线性独立的(这是等效的)。也许这样说会更有道理:我的向量是线性相关的,它们必须形成 0 或 pi/180° 的角度。编辑:但考虑一下。前者可能更好,因为如果 a,b,c 满足强三角不等式,您知道您的公式有效。既然论文这么说,那么您就不必担心数字了:)。
猜你喜欢
  • 2011-01-09
  • 1970-01-01
  • 1970-01-01
  • 2017-03-20
  • 2011-07-17
  • 1970-01-01
  • 2011-05-16
  • 1970-01-01
  • 2018-08-18
相关资源
最近更新 更多