J'essaye de résoudre numériquement l'équation x = a * sin (x)
, où a
est une constante, en python. J'ai déjà essayé d'abord de résoudre l'équation symboliquement, mais il semble que cette forme d'expression particulière ne soit pas implémentée en sympy. J'ai aussi essayé d'utiliser sympy.nsolve (), mais cela ne me donne que la première solution qu'il rencontre.
Mon plan ressemble à ceci:
x=0 a=1 rje=[] while(x<a): if (x-numpy.sin(x))<=error_sin: rje.append(x) x+=increment print(rje)
Je ne veux pas perdre de temps ou risquer de manquer des solutions, je veux donc savoir comment savoir avec quelle précision numpy sinus est sur mon appareil (qui deviendrait error_sin).
edit: J'ai essayé de rendre à la fois error_sin et l'incrément égal à la machine epsilon de mon appareil mais cela a) prend beaucoup de temps, et b) sin ( x) est moins précis que x et donc j'obtiens beaucoup de non-solutions (ou plutôt des solutions répétées car sin (x) croît beaucoup plus lentement que x). D'où la question.
edit2: Pouvez-vous simplement m'aider à répondre à la question sur la précision de numpy.sin (x)? J'ai fourni des informations sur le but uniquement pour le contexte.
5 Réponses :
La solution doit être précise jusqu'à machine epsilon
>>> 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.]) >>>
Vous pourriez envisager d'utiliser scipy.optimize
pour ce problème:
>>> 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.])
Définissez ensuite votre objectif comme suit:
>>> def obj(x): ... return abs(x - a*sin(x)) ...
Et vous pouvez continuer et résoudre ce problème numériquement en:
>>> from scipy.optimize import minimize >>> from math import sin >>> a = 1.0
Essayons maintenant avec une nouvelle valeur de
>>> 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 >>>
Si vous souhaitez trouver une solution non triviale à ce problème, vous devez modifier x0
de manière itérative en valeurs supérieures à zéro et également inférieures à. De plus, gérez les limites de x
dans minimiser en définissant limites
dans scipy.optimize.minimize, vous seriez capable de marcher de -infty à + infty (ou de très grands nombres ).
le problème est que j'obtiens 1 solution, pour un plus grand, disons ~ 1000, je devrais m'attendre à ~ 100 solutions
La seule solution à ce problème est n * pi
, n = 0 ou x = 0
non ce n'est pas le cas, dessinez-le si vous ne me croyez pas, geogebra a une belle édition en ligne. $ a $ est une constante de valeur arbitraire, c'est juste 1 dans mon code car il doit être / some / number.
Voulez-vous dire alors une solution pour y = a sin (x)
où y = 0.0
et alors vous avez autant de solutions ie x = n * pi code> où
n = 0 à l'infini
. Si c'est x = a * sin (x)
alors x - a sin (x) = 0.0
la seule vraie solution est 0.0.
Je ne vais plus te répondre puisque tu es visiblement à la traîne
Je comprends de l'image ce que vous recherchez. Voir modifier. :) Je vois que vous essayiez de trouver une solution non triviale à ce problème. Ce n'était pas clair pour moi.
merci pour la modification, mais je ne sais pas du tout comment définir correctement ces limites. Je passe les bords d'un intervalle comme bounds = [low, high], mais j'obtiens toujours une ValueError: length of x0! = Length of bounds error
Pouvez-vous partager l'appel pour minimiser?
J'ai réussi à le faire fonctionner, mais cela donne de mauvais résultats; c'est à dire. il «trouve» des racines qu'il avait déjà fondées. disons, pour scipy.optimize.minimize (f, 4 * eps, bounds = [(4 * eps, 10)]), cela donne le résultat 8.8817842e-16 même s'il avait déjà trouvé la racine 0..
Sortie: Ainsi, il est raisonnable de dire que les deux les erreurs relatives et absolues de Il y a un excellente chance que si vous faites un Résultat: Vous devrez modifier le np.sin
sera en général aussi précise que possible, compte tenu de la précision du double
(ie float ) variables dans lesquelles les valeurs d'entrée, de sortie et intermédiaires sont stockées. Vous pouvez obtenir une mesure raisonnable de la précision de
np.sin
en la comparant à la version de précision arbitraire de sin
à partir de mpmath
: p > 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()
np.sin
ont une limite supérieure de 2e-16
. Une meilleure réponse
incrément
suffisamment petit pour que votre approche soit précise, votre algorithme sera trop lent pour une utilisation pratique. Les approches de résolution d'équations standard ne fonctionneront pas pour vous, car vous n'avez pas de fonction standard. Au lieu de cela, vous avez une fonction implicite à valeurs multiples. Voici un aperçu d'une approche générale pour obtenir toutes les solutions à ce type d'équation: 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()
initial_guess
en fonction de votre valeur de a
. initial_guess
doit être au moins aussi grand que le nombre réel de solutions.
Un moyen simple d'estimer la précision de sin ()
ET cos ()
pour un argument donné x
serait:
2*eps*(sin(x)+cos(x)) = 1 - sin(x)**2 - cos(x)**2
Vous voudrez peut-être supprimer le dernier 2
juste pour être du "bon côté" (enfin, il y a des valeurs de x
pour lesquelles cette approximation ne tient pas très bien, en particulier pour x
proche de -90 deg
). Je suggérerais de tester à environ x=pi/4
L'idée de base derrière cette approche est la suivante ... Disons que notre sin (x)
et cos (x)
s'écarte des valeurs exactes par une seule "valeur d'erreur" eps
. Autrement dit, exact_sin (x) = sin (x) + eps
(idem pour cos (x)
). Appelons également delta
pour être l'écart mesuré par rapport à l ' identité trigonométrique pythagoricienne a>:
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
Pour les fonctions exactes, delta
doit être zéro:
1 - exact_sin(x)**2 - exact_cos(x)**2 == 0
ou , aller à des fonctions inexactes:
delta = 1 - sin(x)**2 - cos(x)**2
Négliger le dernier terme 2 * eps ** 2
(supposer de petites erreurs):
eps_trig = np.abs(1 - (np.sin(x)**2 + np.cos(x)**2)) / 2
Si nous choisissons x
de telle sorte que sin (x) + cos (x)
plane autour de 1 (ou, quelque part dans la plage 0.5-2
), nous pouvons estimer grossièrement que eps = | 1 - sin (x) ** 2 - cos (x) ** 2 | / 2
.
Bonne idée. Deux questions: à quoi sert le facteur 2 ** -. 5
? Et comment utiliseriez-vous cela pour déterminer l'erreur dans np.sin
par lui-même?
@tel On ne peut pas utiliser cette approche pour estimer l'erreur de sin ()
lui-même (ou "seul") c'est un mélange des les deux sin (x) < / code> ET
cos (x)
. Le sqrt (2)
était une erreur (quand j'expérimentais, j'avais simplement 2
mais quand je tapais ici, je pensais à autre chose).
Je ne comprends toujours pas à quoi sert le 2
dans le dénominateur. N'essayez-vous pas simplement de mesurer les écarts par rapport à l'identité pythagoricienne (c'est-à-dire sin ** 2 + cos ** 2 - 1 == 0
)? Ou l'idée de mesurer environ la moitié de l'erreur est-elle due à chacun des sin ** 2
et cos ** 2
?
@tel, merci pour la modification: j'ai oublié d'écrire 2
devant eps ** 2
.
L'explication rend votre approche beaucoup plus claire, merci. Étant donné que vos hypothèses tiennent raisonnablement bien, il semble être valable de supposer que eps
est l'erreur absolue dans sin
(et cos
ainsi que). Cependant, je peux confirmer que l'erreur absolue à la fois dans np.sin
et np.cos
descend à leurs nœuds respectifs, vous ne pouvez donc probablement pas vous en tirer en supposant eps_sin == eps_cos
.
Pour la précision, vous avez déjà de bonnes réponses. Pour la tâche elle-même, vous pouvez être plus rapide en investissant un peu de calcul.
Premièrement, à partir des limites du sinus, vous savez que toute solution doit être dans l'intervalle [-abs (a), abs (a)]
. Si abs (a) \ le 1
alors la seule racine dans [-1,1]
est x=0
Outre l'intervalle contenant zéro, vous savez également qu'il y a exactement une racine dans l'un des intervalles entre les racines de cos (x) = 1 / a
qui sont les extrema de a * sin (x) -x
. Définissez phi = arccos (1 / a) dans [0, pi]
, puis ces racines sont -phi + 2 * k * pi
et phi + 2 * k * pi
.
L'intervalle pour k = 0
peut contenir 3 racines si 1 . Pour la racine positive, on connaît
x / a = sin (x)> xx ^ 3/6
de sorte que x ^ 2> 6-6 / a
.
Et enfin, le problème est symétrique, si x
est une racine, il en va de même pour -x
donc tout ce que vous avez à faire est de trouver les racines positives.
Donc, pour calculer les racines,
0
. abs (a) , il n'y a plus de racines, retour. On pourrait également utiliser -pi / 2 .
1 , appliquez la méthode de bracketing choisie à l'intervalle [sqrt (6-6 / a), pi / 2]
, ajoutez la racine à la liste et revenez.
Dans les cas restants où abs (a)> = 0.5 * pi
:
phi = arccos (1 / a)
. k
appliquez la méthode de bracketing aux intervalles [2 * (k-1) * pi + phi, 2 * k * pi-phi] et [2 * k * pi-phi, 2 * k * pi-phi de sorte que (k-0,5) * pi [(k-0,5) * pi, (k + 0,5) * pi]
tant que la limite inférieure de l'intervalle est plus petite que abs (a)
et que la fonction a un changement de signe sur l'intervalle.
<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"/>
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(); } });
La précision de la fonction sinus n'est pas si pertinente ici, vous feriez mieux d'effectuer l'étude de l'équation.
Si vous l'écrivez sous la forme sin x / x = sinc x = 1 / a
, vous voyez immédiatement que le nombre de solutions est le nombre d'intersections du sinus cardinal avec une horizontale. Ce nombre dépend des ordonnées des extrema de ce dernier.
Les extrema se trouvent où x cos x - sin x = 0
ou x = tan x
, et les valeurs correspondantes sont cos x
. C'est encore une équation transcendantale, mais elle est sans paramètre et vous pouvez la résoudre une fois pour toutes. Notez également que pour des valeurs croissantes de x, les solutions se rapprochent de plus en plus de (k + 1/2) π
.
Maintenant, pour une valeur donnée de 1 / a
, vous pouvez trouver tous les extrema ci-dessous et ci-dessus et cela vous donnera des intervalles de départ où chercher les racines. La méthode sécante sera pratique.
"précis" comme en précision à virgule flottante?
De plus, je ne comprends pas votre bloc de code comme la condition
tandis que
ne serait jamais remplieSi tel est votre plan, ne vous inquiétez pas.
numpy.sin
sera bien plus précis que ce que vous obtiendrez avec votreincrément
. Ce que vous faites est essentiellement une recherche de grille. Jetez plutôt un œil aux solutions existantes bien testées et bien implémentées comme celle-ci: docs.scipy.org/doc/scipy-0.14.0/reference/generated/...@PaulH désolé pour le bloc while, c'était une faute de frappe. Oui, je parle de précision en virgule flottante.
eh bien, numpy a des flotteurs 64 bits.
Puisque vous l'évaluez en scalaires, utilisez
math.sin
.@Lagerbaer fsolve () n'est pas bon car il me donne également la première solution. L'idée est précisément de rendre l'incrément aussi précis que possible, mais l'utilisation de la machine epsilon prend trop de temps.
@hpaulj Je ne l'évalue pas aux scalaires?
Qu'est-ce que
x
? Un seul numéro, une liste ou un tableau?np.sin (anArray)
,math.sin (aNumber)
.@hpaulj oh, oui c'est un scalaire, mais numpy.sin () fonctionne bien avec les scalaires. Je ne vois ni l'enjeu ni sa pertinence.
@PaulH eh bien, pour être pédant,
numpy
prend en charge divers types de virgule flottante. Il peut s'agir de float16, float32 et float64 (demi, simple et double précision). Il prend même en charge la précision étendue a > flotte, mais IIRC, tout cela est assez sommaireC'est plus une question de vitesse. La précision est probablement la même.
@ juanpa.arrivillaga Je le sais. comme ses flotteurs de précision étendue sont sommaires, j'ai recommandé les flotteurs 64 bits comme "les plus précis".