numpy的sin(x)有多精确?我如何找到? [需要用数值方法求解x = a * sin(x)]

时间:2019-01-15 21:50:34

标签: python numpy precision trigonometry numerical-methods

我正在尝试用python数值求解方程x=a*sin(x),其中a是常数。我已经尝试过首先用符号方式求解方程式,但是似乎这种特殊形式的表达式没有在sympy中实现。我也尝试过使用sympy.nsolve(),但是它只给我遇到的第一个解决方案。

我的计划如下:

x=0
a=1
rje=[]
while(x<a):
    if (x-numpy.sin(x))<=error_sin:
        rje.append(x)
    x+=increment

print(rje)

我不想浪费时间或冒险错过解决方案,所以我想知道如何找出设备上numpy窦的精确度(这将成为error_sin)。

edit:我尝试使error_sin和增量都等于设备的机器epsilon,但是a)需要花费很多时间,b)sin(x)的精确度不及x,因此我得到很多非解(或者说是重复的解,因为sin(x)的增长远慢于x)。因此是一个问题。

edit2:能否请我回答有关numpy.sin(x)精度的问题?我纯粹出于上下文提供了有关目的的信息。

5 个答案:

答案 0 :(得分:4)

答案

np.sin通常会尽可能精确,因为double(即64位float)变量的精度是输入,输出和中间值存储。通过将其与np.sinsin的任意精度版本进行比较,可以合理地衡量mpmath的精度:

import matplotlib.pyplot as plt
import mpmath
from mpmath import mp

# set mpmath to an extremely high precision
mp.dps = 100
x = np.linspace(-np.pi, np.pi, num=int(1e3))

# numpy sine values
y = np.sin(x)

# extremely high precision sine values
realy = np.array([mpmath.sin(a) for a in x])

# the end results are arrays of arbitrary precision mpf values (ie abserr.dtype=='O')
diff = realy - y
abserr = np.abs(diff)
relerr = np.abs(diff/realy)

plt.plot(x, abserr, lw=.5, label='Absolute error')
plt.plot(x, relerr, lw=.5, label='Relative error')
plt.axhline(2e-16, c='k', ls='--', lw=.5, label=r'$2 \cdot 10^{-16}$')
plt.yscale('log')
plt.xlim(-np.pi, np.pi)
plt.ylim(1e-20, 1e-15)
plt.xlabel('x')
plt.ylabel('Error in np.sin(x)')
plt.legend()

输出:

enter image description here

因此,可以合理地说np.sin的相对误差和绝对误差都具有2e-16的上限。

更好的答案

很有可能,如果将increment缩小到足以使方法准确的程度,则算法在实际使用中会太慢。标准方程求解方法对您不起作用,因为您没有标准函数。相反,您有一个隐式的多值函数。这是获取此类方程式所有解的通用方法的一个障碍:

import matplotlib.pyplot as plt
import numpy as np
import scipy.optimize as spo

eps = 1e-4

def func(x, a):
    return a*np.sin(x) - x

def uniqueflt(arr):
    b = arr.copy()
    b.sort()
    d = np.append(True, np.diff(b))
    return b[d>eps]

initial_guess = np.arange(-9, 9) + eps
# uniqueflt removes any repeated roots
roots = uniqueflt(spo.fsolve(func, initial_guess, args=(10,)))
# roots is an array with the 7 unique roots of 10*np.sin(x) - x == 0:
#     array([-8.42320394e+00, -7.06817437e+00, -2.85234190e+00, -8.13413225e-09,
#            2.85234189e+00,  7.06817436e+00,  8.42320394e+00])

x = np.linspace(-20, 20, num=int(1e3))
plt.plot(x, x, label=r'$y = x$')
plt.plot(x, 10*np.sin(x), label=r'$y = 10 \cdot sin(x)$')
plt.plot(roots, 10*np.sin(roots), '.', c='k', ms=7, label='Solutions')

plt.ylim(-10.5, 20)
plt.gca().set_aspect('equal', adjustable='box')
plt.legend()

输出:

enter image description here

您必须根据initial_guess的值来调整ainitial_guess必须至少与实际解决方案数量一样。

答案 1 :(得分:2)

对于给定参数sin()估算cos()x的准确性的简单方法是:

eps_trig = np.abs(1 - (np.sin(x)**2 + np.cos(x)**2)) / 2

您可能只想放倒2只是为了站在“安全的一面”(嗯,有些x的值对此近似值的适用性不是很好,特别是对于{{ 1}}靠近x)。我建议在-90 deg

附近进行测试

说明:

此方法背后的基本思想如下...假设我们的x=pi/4sin(x)与准确值的差异为单个“错误值” {{1 }}。即cos(x)(与eps相同)。另外,我们称exact_sin(x) = sin(x) + eps为与Pythagorean trigonometric identity的测量偏差:

cos(x)

对于确切的功能,delta应该为零:

delta = 1 - sin(x)**2 - cos(x)**2

或者,不精确的函数:

delta

忽略最后一项1 - exact_sin(x)**2 - exact_cos(x)**2 == 0 (假设有小错误):

1 - (sin(x) + eps)**2 - (cos(x) + eps)**2 == 0 =>
1 - sin(x)**2 - cos(x)**2 = delta = 2*eps*(sin(x) + cos(x)) + 2*eps**2

如果我们选择2*eps**2使得2*eps*(sin(x)+cos(x)) = 1 - sin(x)**2 - cos(x)**2 徘徊在1(或x范围内的某处)附近,我们可以粗略估计sin(x)+cos(x)

答案 2 :(得分:1)

精确地说,您已经得到了很好的答案。对于任务本身,您可以通过投资一些微积分来提高速度。

  • 首先,从正弦的范围中您知道任何解都必须在区间[-abs(a),abs(a)]中。如果abs(a)\le 1,则[-1,1]中唯一的根是x=0

  • 除了包含零的间隔之外,您还知道cos(x)=1/a的根(a*sin(x)-x的极值)之间的任何间隔中都只有一个根。设置phi=arccos(1/a) in [0,pi],则这些根为-phi+2*k*piphi+2*k*pi

  • 如果k=0,则1<a<0.5*pi的间隔可能包含3个根。对于积极的根源,人们知道x/a=sin(x)>x-x^3/6,因此x^2>6-6/a

  • 最后,问题是对称的,如果x是根,-x也是根,那么您要做的就是找到正根。

所以要计算根,

  • 从根0开始根列表。
  • abs(a)<=1的情况下,没有其他根,返回。也可以使用-pi/2<=a<=1
  • 1<a<pi/2的情况下,将选择的包围方法应用于区间[sqrt(6-6/a), pi/2],将根添加到列表中,然后返回。
  • 在其余情况下,abs(a)>=0.5*pi

    • 计算phi=arccos(1/a)
    • 然后对于任何正整数k,将包围曝光方法应用于区间[2 *(k-1)* pi + phi,2 * k * pi-phi]和[2 * k * pi-phi ,2 * k * pi-phi,使得(k-0.5)*pi < abs(a) [(k-0.5)*pi, (k+0.5)*pi]只要下区间边界小于abs(a)并且函数在区间上有符号变化。
    • 将找到的根添加到列表中。循环结束后返回列表。

let a=10;

function f(x) { return x - a * Math.sin(x); }

findRoots();

//-------------------------------------------------

function findRoots() {
    log.innerHTML = `<p>roots for parameter a=${a}`;
    rootList.innerHTML = "<tr><th>root <i>x</i></th><th><i>x-a*sin(x)</i></th><th>numSteps</th></tr>";
    rootList.innerHTML += "<tr><td>0.0<td>0.0<td>0</tr>";

    if( Math.abs(a)<=1) return;

    if( (1.0<a) && (a < 0.5*Math.PI) ) {
        illinois(Math.sqrt(6-6/a), 0.5*Math.PI);
        return;
    }

    const phi = Math.acos(1.0/a);
    log.innerHTML += `phi=${phi}<br>`;
    let right = 2*Math.PI-phi;
    for (let k=1; right<Math.abs(a); k++) { 
        let left = right;
        right = (k+2)*Math.PI + ((0==k%2)?(-phi):(phi-Math.PI));
        illinois(left, right);
    }
}

function illinois(a, b) {
  log.innerHTML += `<p>regula falsi variant illinois called for interval [a,b]=[${a}, ${b}]`;
  let fa = f(a);
  let fb = f(b);
  let numSteps=2;
  log.innerHTML += ` values f(a)=${fa}, f(b)=${fb}</p>`;

  if (fa*fb > 0) return;

  if (Math.abs(fa) < Math.abs(fb)) { var h=a; a=b; b=h; h=fa; fa=fb; fb=h;}
  
  while(Math.abs(b-a) > 1e-15*Math.abs(b)) {
    let c = b - fb*(b-a)/(fb-fa);
    let fc = f(c); numSteps++;
    log.innerHTML += `step ${numSteps}: midpoint c=${c}, f(c)=${fc}<br>`;

    if ( fa*fc < 0 ) {
      fa *= 0.5;
    } else {
      a = b; fa = fb;
    }
    b = c; fb = fc;
  }
  rootList.innerHTML += `<tr><td>${b}<td>${fb}<td>${numSteps}</tr>`;
}

aInput.addEventListener('change', () => {
  let a_new = Number.parseFloat(aInput.value);
  if( isNaN(a_new) ) {
      alert('Not a number '+aInput.value);
  } else if(a!=a_new) {
     a = a_new;
     findRoots();
  }
});
<p>Factor <i>a</i>: <input id="aInput" value="10" /></p>
<h3>Root list</h3>
<table id="rootList" border = 1>
</table>
<h3>Computation log</h3>
<div id="log"/>

答案 3 :(得分:1)

这里正弦函数的精度不太重要,您最好对方程进行研究。

如果以sin x / x = sinc x = 1 / a的形式编写,您将立即看到解数是主正弦与水平线的交点数。此数字取决于后者的极坐标。

x cos x - sin x = 0x = tan x处找到极值,并且对应的值为cos x。这再次是一个超越方程,但它是无参数的,您可以一次求解。还要注意,随着x值的增加,解越来越接近(k+1/2)π

现在对于给定值1 / a,您可以在上下找到所有极值,这将为您提供寻找根的起始间隔。割线方法将很方便。

enter image description here

答案 4 :(得分:0)

解决方案应精确到machine epsilon

>>> from numpy import sin as sin_np
>>> from math import sin as sin_math
>>> x = 0.0
>>> sin_np(x) - x
0.0
>>> sin_math(x) - x
0.0
>>> 

您可以考虑使用scipy.optimize来解决此问题:

>>> from scipy.optimize import minimize
>>> from math import sin
>>> a = 1.0

然后将目标定义如下:

>>> def obj(x):
...     return abs(x - a*sin(x))
... 

您可以通过以下方式以数字方式解决此问题:

>>> sol = minimize(obj, 0.0)
>>> sol
      fun: array([ 0.])
 hess_inv: array([[1]])
      jac: array([ 0.])
  message: 'Optimization terminated successfully.'
     nfev: 3
      nit: 0
     njev: 1
   status: 0
  success: True
        x: array([ 0.])

现在让我们尝试使用新值a

>>> a = .5
>>> sol = minimize(obj, 0.0)
>>> sol
      fun: array([ 0.])
 hess_inv: array([[1]])
      jac: array([ 0.5])
  message: 'Desired error not necessarily achieved due to precision loss.'
     nfev: 315
      nit: 0
     njev: 101
   status: 2
  success: False
        x: array([ 0.])
>>> 

如果您想找到一个解决这个问题的简单方法,则需要迭代地将x0更改为大于零且小于零的值。另外,通过在scipy.optimize.minimize中设置x来最小化bounds的边界,您将能够从-infty到+ infty(或很大的数字)。