我正在尝试用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)精度的问题?我纯粹出于上下文提供了有关目的的信息。
答案 0 :(得分:4)
np.sin
通常会尽可能精确,因为double
(即64位float
)变量的精度是输入,输出和中间值存储。通过将其与np.sin
中sin
的任意精度版本进行比较,可以合理地衡量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()
输出:
因此,可以合理地说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()
输出:
您必须根据initial_guess
的值来调整a
。 initial_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/4
和sin(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*pi
和phi+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 = 0
或x = tan x
处找到极值,并且对应的值为cos x
。这再次是一个超越方程,但它是无参数的,您可以一次求解。还要注意,随着x值的增加,解越来越接近(k+1/2)π
。
现在对于给定值1 / a
,您可以在上下找到所有极值,这将为您提供寻找根的起始间隔。割线方法将很方便。
答案 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(或很大的数字)。