2
votes

Quelle est la précision du péché de numpy (x)? Comment le découvrir? [besoin de résoudre numériquement x = a * sin (x)]

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.


13 commentaires

"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 remplie


Si tel est votre plan, ne vous inquiétez pas. numpy.sin sera bien plus précis que ce que vous obtiendrez avec votre incré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 flotte, mais IIRC, tout cela est assez sommaire


C'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".


5 Réponses :


0
votes

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 ).


9 commentaires

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) y = 0.0 et alors vous avez autant de solutions ie x = n * pi 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..



4
votes

La réponse

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()

Sortie:

Ainsi, il est raisonnable de dire que les deux les erreurs relatives et absolues de np.sin ont une limite supérieure de 2e-16.

Une meilleure réponse

Il y a un excellente chance que si vous faites un 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()

Résultat:

 entrez la description de l'image ici

Vous devrez modifier le initial_guess en fonction de votre valeur de a . initial_guess doit être au moins aussi grand que le nombre réel de solutions.


0 commentaires

2
votes

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

Explication :

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 .


5 commentaires

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 .



1
votes

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

    li>
  • 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,

  • Démarrez la liste racine avec la racine 0 .
  • dans le cas abs (a) , il n'y a plus de racines, retour. On pourrait également utiliser -pi / 2 .
  • dans le cas 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 :

    • Calculez phi = arccos (1 / a) .
    • Ensuite, pour tout entier positif 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.
    • Ajoutez la racine trouvée à la liste. Revenir avec la liste après la fin de la boucle.

<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();
  }
});


0 commentaires

1
votes

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.

 entrez la description de l'image ici


0 commentaires