Trovare approssimazioni polinomiali di un'onda sinusoidale


16

Voglio approssimare l'onda sinusoidale data da applicando un wavehaper polinomiale a una semplice onda triangolare , generata dalla funzionesin(πx)

T(x)=14|12mod(12x+14, 1)|

dove è la parte frazionaria di :mod(x,1)x

mod(x,y)y(xyxy)

Una serie di Taylor potrebbe essere usata come un wavehaper.

S1(x)=πx2πx233!+πx255!πx277!

Date le funzioni sopra, S1(T(x)) darà un'approssimazione decente di un'onda sinusoidale. Ma dobbiamo raggiungere la settima potenza della serie per ottenere un risultato ragionevole, e i picchi sono un po 'bassi e non avranno una pendenza esattamente pari a zero.

Invece della serie Taylor, potremmo usare un polinomio wavehaper seguendo alcune regole.

  • Deve passare attraverso -1, -1 e + 1, + 1.
  • La pendenza a -1, -1 e + 1, + 1 deve essere zero.
  • Deve essere simmetrico.

Una semplice funzione che soddisfa i nostri requisiti:

S2(x)=3x2x32

I grafici di S2(T(x)) e sin(πx) sono piuttosto vicini, ma non così vicini alla serie Taylor. Tra le cime e gli incroci di zero si scostano visibilmente un po '. Una funzione più pesante e accurata che soddisfa i nostri requisiti:

S3(x)=x(x25)216

Questo è probabilmente abbastanza vicino per i miei scopi, ma mi chiedo se esiste un'altra funzione che si avvicina maggiormente all'onda sinusoidale ed è più computazionalmente più economica. Ho una buona conoscenza di come trovare le funzioni che soddisfano i tre requisiti di cui sopra, ma non sono sicuro di come trovare funzioni che soddisfino tali requisiti e che corrispondano maggiormente a un'onda sinusoidale.

Quali metodi esistono per trovare polinomi che imitano un'onda sinusoidale (quando applicati a un'onda triangolare)?


Per chiarire, non sono necessariamente alla ricerca solo di polinomi dispari-simmetrici, sebbene quelli siano la scelta più semplice.

Qualcosa come la seguente funzione potrebbe anche adattarsi alle mie esigenze:

S4(x)=3x2+x24+x44

Questo soddisfa i requisiti nella gamma negativa e una soluzione a tratti potrebbe essere utilizzata per applicarla anche alla gamma positiva; per esempio

3x2P(x,2)4P(x,4)4

dove è la funzione di alimentazione firmata .P

Sarei anche interessato a soluzioni che utilizzano la funzione di potenza firmata per supportare esponenti frazionari, in quanto ciò ci dà un'altra "manopola per torcere" senza aggiungere un altro coefficiente.

a0x +a1P(x, p1)

Date le costanti giuste, ciò potrebbe potenzialmente ottenere una precisione molto buona senza la pesantezza dei polinomi di quinto o settimo ordine. Ecco un esempio che soddisfa i requisiti qui descritti usando alcune costanti scelte a mano: .a0=1.666¯,a1=0.666¯,p1=2.5

5x2P(x, 52)3

In effetti, quelle costanti sono molto vicine a e ed . Collegare quelli in dà qualcosa che sembra estremamente vicino a un'onda sinusoidale. 1-ππ2 e1π2e

π2x +(1π2)P(x,e)

Per dirla in altro modo, sembra molto vicino a tra 0,0 e π / 2,1. Qualche idea sul significato di questo? Forse uno strumento come Octave può aiutare a scoprire le costanti "migliori" per questo approccio. sin(x)xxe6sin(x)


1
quindi qual è la definizione del termine di errore per "più vicino"? Per quanto ne so, la serie di Taylor che hai citato è approssimativa dell'errore L² minimo per il numero finito di coefficienti. (Penso.)
Marcus Müller il

2
A proposito, qual è il tuo obiettivo? Potrebbe davvero aiutare a dirci perché stai cercando uno shaper polinomiale, su quali basi tecnologiche e quali sono i tuoi obiettivi principali per l'approssimazione.
Marcus Müller,

@ MarcusMüller Sono disposto a sacrificare l'accuratezza delle serie Taylor per qualcosa di significativamente più economico, se è indistinguibile da un'onda sinusoidale all'orecchio umano. Anche i picchi dell'approssimazione della serie Taylor mi danno fastidio. Sono interessato a trovare qualcosa di "più vicino" rispetto alle altre due funzioni che ho elencato. Sospetto che non sarà più economico di . S2
Ospite dal

1
"All'orecchio umano" è fondamentale qui :) Perché le cime ti "disturbano"? Ancora: dacci un'idea del perché / per quale scopo e con quali restrizioni lo stai facendo. Senza abbastanza background la tua domanda è semplicemente troppo ampia per ricevere una risposta adeguata!
Marcus Müller,

1
Perché inizi con un'onda triangolare? I generatori sinusoidali sono semplici e comuni, le onde quadrate sono banalmente filtrate per l'armonica fondamentale, ecc.
Carl Witthoft,

Risposte:


10

circa un decennio fa l'ho fatto per una società di sintetizzatori musicali senza nome che aveva attività di ricerca e sviluppo non troppo lontano dal mio appartamento a Waltham MA. (Non riesco a immaginare chi siano.) Non ho i coefficienti.

ma prova questo:

f(x)sin(π2x)for 1x+1=π2x(a0+a1x2+a2x4)

questo garantisce che .f(x)=f(x)

Per garantire che alloraf(x)|x=±1=0

f(x)=π2(a0+3a1x2+5a2x4)

(1)a0+3a1+5a2=0

Questo è il primo vincolo. Per garantire che , quindi|f(±1)|=1

(2)a0+a1+a2=2π

Questo è il secondo vincolo. Eliminare e risolvere gli Eq. (1) e (2) per in termini di (che è rimasto da regolare):a 2 a 1a0a2a1

a0=52π12a1

a2=12π12a1

Ora hai solo un coefficiente, , da modificare per ottenere le migliori prestazioni:a1

f(x)=π2x((52π12a1)+a1x2(12π+12a1)x4)

Questo è il modo in cui aggiungerei per le migliori prestazioni per un oscillatore a onda sinusoidale. Vorrei regolare usando quanto sopra e la simmetria dell'onda sinusoidale circa e posizionerei esattamente un intero ciclo in un buffer con una potenza di due numeri di punti (diciamo 128, non mi interessa) e eseguo la FFT su quello ciclo perfetto. x = 1a1x=1

Il cestino dei risultati FFT 1 sarà la forza del seno e dovrebbe essere di circa . Ora puoi regolare per aumentare o diminuire la terza distorsione armonica. Vorrei iniziare con modo che . Questo è nel bin 3 dei risultati FFT Ma la quinta distorsione armonica (valore nel bin 5) sarà consequenziale (salirà quando la terza armonica diminuirà). Vorrei regolare in modo che la forza del livello di armonica 5 ° è uguale al livello di armonica 3 °. Sarà di circa -70 dB dalla prima armonica (come ricordo). Questa sarà l'onda sinusoidale dal suono più gradevole da un polinomio economico, a 3 coefficienti, di 5 ° ordine, dispari-simmetrico.a 1 a 15N/2a1a01a1a15π2a01a1

Qualcun altro può scrivere il codice MATLAB. Come ti sembra?


non avrò sicuramente il tempo di fare il MATLABing per cercare ottimale in modo che la terza armonica sia uguale alla quinta armonica, circa 70 dB al di sotto della fondamentale (prima armonica). qualcun altro deve farlo. spiacente. a1
robert bristow-johnson,

Ottima risposta, ancora digeribile. In realtà cominciando a chiedermi se deve essere un polinomio a 3 coefficienti, di 5 ° ordine, dispari-simmetrico ... Il tuo f '(x) potrebbe essere effettivamente f (x) ed essere un affare a tratti attorno a 0? Schizzo approssimativo qui . Forse questo è ciò che Ced ha in mente? Vi raggiungo ancora ragazzi.
Ospite dal

Questo è un approccio bellissimo. Mi chiedo se invece di prendere la FFT e risolverla in modo iterativo potresti formare i polinomi di Chebyshev di terzo e quinto ordine dalla tua , quindi equiparare i due e risolvere per ? a 1f(x)a1
Rapido

Deve essere stato mezzo addormentato quando ho postato che "schizzo", volevo fare qualcosa di simile questo , ma corretto a correre attraverso ± 1 e hanno zero pendenza (può solo prendere la derivata, intorno violino con esso, l'integrazione di nuovo). Non sono sicuro che ci sia qualche vantaggio rispetto al quinto ordine, solo qualcosa che non avevo ancora considerato.
Ospite

1
Questa è davvero una soluzione geniale, ci è voluto solo un po 'per affondare. Spero che contrassegnarla come corretta non impedirà a qualcun altro di venire avanti e scrivere il codice.
Ospite il

9

Ciò che di solito viene fatto è un'approssimazione che minimizza alcune norme dell'errore, spesso -norm (dove l'errore massimo è minimizzato), o -norm (dove l'errore quadratico medio è minimizzato). approssimazione di viene effettuata utilizzando l' algoritmo di scambio Remez . Sono sicuro che puoi trovare del codice open source che implementa tale algoritmo. Tuttavia, in questo caso penso che sia sufficiente un'ottimizzazione molto semplice (discreta) . Diamo un'occhiata ad alcuni codici Matlab / Octave e ai risultati: L 2 L l 2LL2Ll2

x = linspace (0, pi / 2.300); % griglia su [0, pi / 2]
x = x (:);
% sistema sovradeterminato di equazioni lineari
% (usando solo poteri dispari)
A3 = [x, x. ^ 3];
A5 = [x, x. ^ 3, x. ^ 5];
b = sin (x);
% risolto in senso l2
c3 = A3 \ b;
c5 = A5 \ b;
f3 = A3 * c3; % Approssimazione del 3 ° ordine
f5 = A5 * c5; % Approssimazione del 5 ° ordine

La figura seguente mostra gli errori di approssimazione per l'ordine e per le approssimazioni dell'ordine . Gli errori di approssimazione massima sono e , rispettivamente. 5 t h3rd5th8.8869e-031.5519e-04

inserisci qui la descrizione dell'immagine

I coefficienti ottimali sono

c3 =
   ,988720369237930
  -,144993929056091

e

c5 =
   ,99976918199047515
  -,16582163562776930
   ,00757183954143367

Quindi l'approssimazione del terzo ordine è

(1)sin(x)0.988720369237930x0.144993929056091x3,x[π/2,π/2]

e l'approssimazione del quinto ordine è

(2)sin(x)0.99976918199047515x0.16582163562776930x3+0.00757183954143367x5,x[π/2,π/2]

MODIFICARE:

Ho dato un'occhiata alle approssimazioni con la funzione di potenza firmata, come suggerito nella domanda, ma l'approssimazione migliore non è certo migliore dell'approssimazione del terzo ordine mostrata sopra. La funzione approssimativa è

(3)f(x)=x1p(π2)1pxp,x[0,π/2]

dove le costanti sono stati scelti in modo che e . La potenza stata ottimizzata per ottenere il massimo errore minimo nell'intervallo . Il valore ottimale per è risultato essere . La figura seguente mostra gli errori di approssimazione per l'approssimazione del terzo ordine e per la nuova approssimazione :f ( π / 2 ) = 0 p [ 0 , π / 2 ] p p = 2.774 ( 1 ) ( 3 )f(0)=1f(π/2)=0p[0,π/2]pp=2.774(1)(3)

inserisci qui la descrizione dell'immagine

L'errore di approssimazione massimo dell'approssimazione è , ma si noti che l'approssimazione del terzo ordine supera solo l'errore vicino a e che per la maggior parte l'errore di approssimazione è in realtà più piccolo di quello della funzione di potenza firmata .π / 2(3)4.5e-3π/2

EDIT 2:

Se non ti dispiace dividere potresti anche usare la formula di approssimazione sinusoidale di Bhaskara I , che ha un errore di approssimazione massimo di 1.6e-3:

(4)sin(x)16x(πx)5π24x(πx),x[0,π/2]

È molto utile, grazie. Questa è la prima volta che uso Octave. L'ho seguito per la maggior parte, ma come hai ottenuto i grafici degli errori di approssimazione e i valori massimi?
Ospite il

1
@Guest: gli errori sono giusti b-f3e b-f5, rispettivamente. Usa il plotcomando per tracciarli.
Matt L.

1
@Guest: E i massimi che ottieni da max(abs(b-f3))e max(abs(b-f5)).
Matt L.

@Guest: ho giocato con la funzione power firmata, ma il risultato non è significativamente migliore dell'approssimazione del terzo ordine che avevo prima. Guarda la mia risposta modificata. Per quanto riguarda la complessità, farebbe una differenza così grande?
Matt L.

Grazie per averci dato un'occhiata. La complessità non è un grosso problema, solo curioso di sapere quanto può essere precisa l'approssimazione con una complessità relativamente bassa. Non sono sicuro di come ti sia venuta (3), ma funziona bene. Dovrei usare 2.752 invece per p, poiché qualsiasi cosa sopra che invierà i picchi oltre 1 (ritaglio).
Ospite il

7

Inizia con un polinomio parametrico del 5 ° ordine altrimenti generico, simmetria dispari :

f(x)=a0x1+a1x3+a2x5=x(a0+a1x2+a2x4)=x(a0+x2(a1+a2x2))

Ora poniamo alcuni vincoli su questa funzione. L'ampiezza dovrebbe essere 1 ai picchi, in altre parole f(1)=1 . Sostituendo 1 con x ottiene:

(1)a0+a1+a2=1

Questo è un vincolo. La pendenza ai picchi dovrebbe essere zero, in altre parole f(1)=0 . La derivata di f(x) è

a0+3a1x2+5a2x4

e sostituendo 1 con x ottiene il nostro secondo vincolo:

(2)a0+3a1+5a2=0

Ora siamo in grado di utilizzare le nostre due vincoli per risolvere per a1 e a2 in termini di a0 .

(3)a1=522a0a2=a032

Tutto ciò che rimane è modificare a0 per ottenere una buona misura. Per inciso, a0 (e la pendenza all'origine) finisce per essere π2 , come possiamo vedere da undiagrammadella funzione.

Ottimizzazione dei parametri

Di seguito sono riportate alcune ottimizzazioni dei coefficienti, che danno luogo a queste ampiezze relative delle armoniche rispetto alla frequenza fondamentale (1a armonica):

Confronto di approssimazioni

Nella complessa serie di Fourier :

ΣK=-cKeio2πPKX,

di un vero P- forma d'onda periodica con P=4 ore simmetria intorno x=1 e con mezzo periodo definito dalla funzione dispari f(x) sopra 1x1, il coefficiente del kth esponenziale complesso armonico è:

ck=1P11+P({f(x)if x<1f(x2)if x1)ei2πPkxdx.

A causa della relazione 2cos(x)=eix+eix (vedi: formula di Eulero ), l'ampiezza di una vera armonica sinusoidale con k>0 è 2|ck|, che è il doppio di quello della grandezza del complesso esponenziale della stessa frequenza. Questo può essere massaggiato in una forma che semplifica alcuni software matematici simbolici per semplificare l'integrale:

2|ck|=24|13({f(x)if x<1f(x2)if x1)ei2π4kxdx|=12|11f(x)eiπ2kxdx13f(x2)eiπ2kxdx|=12|11f(x)eiπ2kxdx11f(x+22)eiπ2k(x+2)dx|=12|11f(x)eiπ2kxdx11f(x)eiπ2k(x+2)dx|=12|11f(x)(eiπ2kxeiπ2k(x+2))dx|=12|eiπ2x11f(x)(eiπ2kxeiπ2k(x+2))dx|=12|11f(x)(eiπ2k(x1)eiπ2k(x+1))dx|

Quanto sopra ne trae vantaggio |eix|=1 per x. reale . Per alcuni sistemi di algebra del computer è più semplice semplificare l'integrale assumendo che k sia reale e semplificare l'intero k alla fine. Wolfram Alpha può integrare termini individuali dell'integrale finale corrispondenti ai termini del polinomio f(x) . Per i coefficienti indicati in Eq. 3 otteniamo ampiezza:

=|48((1)k1)(16a0(π2k210)5×(5π2k248))π6k6|

5 ° ordine, derivata continua

Possiamo risolvere per il valore di a0 che dà uguale ampiezza 2|ck|della 3a e 5a armonica. Ci saranno due soluzioni corrispondenti alla terza e alla quinta armonica con fasi uguali o opposte. La soluzione migliore è quella che minimizza l'ampiezza massima della 3a e delle armoniche superiori ed equivalentemente la massima ampiezza relativa della 3a e delle armoniche superiori rispetto alla frequenza fondamentale (1a armonica):

a0=3×(132375π2130832)16×(15885π216354)1.569778813,a1=522a0=79425π2654168×(15885π2+16354)0.6395576276,a2=a032=15885π216×(15885π216354)0.06977881382.

Ciò fornisce la frequenza fondamentale all'ampiezza 1367961615885π616354π41.000071420e sia la 3a che la 5a armonica ad ampiezza relativa18906 o circa78.99 dBrispetto alla frequenza fondamentale. Unktharmonica ha ampiezza relativa(1(1)k)|8177k279425|142496k6.

7 ° ordine, derivata continua

Allo stesso modo, l'approssimazione polinomiale ottimale del 7 ° ordine con gli stessi vincoli iniziali e la 3a, 5a e 7a armonica al livello uguale più basso possibile è:

f(x)=a0x1+a1x3+a2x5+a3x7=x(a0+a1x2+a2x4+a3x7)=x(a0+x2(a1+x2(a2+a3x2)))

a0=2a2+4a3+321.570781972,a1=4a2+6a3+120.6458482979,a2=347960025π4405395408π216×(281681925π4405395408π2+108019280)0.07935067784,a3=16569525π416×(281681925π4405395408π2+108019280)0.004284352588.

This is the best of four possible solutions corresponding to equal/opposite phase combinations of the 3rd, 5th, and 7th harmonic. The fundamental frequency has amplitude 2293523251200281681925π8405395408π6+108019280π40.9999983752, and the 3rd, 5th, and 7th harmonics have relative amplitude 11555395123.8368 dB compared to the fundamental. A kth harmonic has relative amplitude (1(1)k)|1350241k450674426k2+347960025|597271680k8 compared to the fundamental.

5th order

If the requirement of a continuous derivative is dropped, the 5th order approximation will be more difficult to solve symbolically, because the amplitude of the 9th harmonic will rise above the amplitude of the 3rd, 5th, and the 7th harmonic if those are constrained to be equal and minimized. Testing 16 different solutions corresponding to different subsets of three harmonics from {3,5,7,9} being of equal amplitude and of equal or opposite phases, the best solution is:

f(x)=a0x1+a1x3+a2x5a0=1a1a21.570034357a1=3×(2436304π22172825π4)8×(1303695π41827228π2+537160)0.6425216143a2=1303695π416×(1303695π41827228π2+537160)0.07248725712

The fundamental frequency has amplitude 10804305921303695π61827228π4+537160π20.9997773320. The 3rd, 5th, and 9th harmonics have relative amplitude 726377791.52 dB, and the 7th harmonic has relative amplitude 7260833103310027392.6 dB compared to the fundamental. A kth harmonic has relative amplitude (1(1)k)|67145k42740842k2+19555425|33763456k6.

This approximation has a slight corner at the half-cycle boundaries, because the polynomial has zero derivative not at x=±1 but at x±1.002039940. At x=1 the value of the derivative is about 0.004905799828. This results in slower asymptotic decay of the amplitudes of the harmonics at large k, compared to the 5th order approximation that has a continuous derivative.

7th order

A 7th order approximation without continuous derivative can be found similarly. The approach requires testing 120 different solutions and was automated by the Python script at the end of this answer. The best solution is:

f(x)=a0x1+a1x3+a2x5+a3x7a0=1a1a2a31.5707953785726114835a1=5×(4374085272375π66856418226992π4+2139059216768π2)16×(2124555703725π63428209113496π4+1336912010480π2155807094720)0.64590724797262922190a2=2624451163425π63428209113496π416×(2124555703725π63428209113496π4+1336912010480π2155807094720)0.079473610232926783079a3=124973864925π616×(2124555703725π63428209113496π4+1336912010480π2155807094720)0.0043617408329090447344

The fundamental frequency has amplitude 169918012823961602124555703725π83428209113496π6+1336912010480π4155807094720π21.0000024810802368487. The largest relative amplitude of the harmonics above the fundamental is 502400688077133.627 dB. compared to the fundamental. A kth harmonic has relative amplitude (1(1)k)|162299057k6+16711400131k4428526139187k2+2624451163425|4424948250624k8.

Python source

from sympy import symbols, pi, solve, factor, binomial

numEq = 3 # Number of equations
numHarmonics = 6 # Number of harmonics to evaluate

a1, a2, a3, k = symbols("a1, a2, a3, k")
coefficients = [a1, a2, a3]
harmonicRelativeAmplitude = (2*pi**4*a1*k**4*(pi**2*k**2-12)+4*pi**2*a2*k**2*(pi**4*k**4-60*pi**2*k**2+480)+6*a3*(pi**6*k**6-140*pi**4*k**4+6720*pi**2*k**2-53760)+pi**6*k**6)*(1-(-1)**k)/(2*k**8*(2*pi**4*a1*(pi**2-12)+4*pi**2*a2*(pi**4-60*pi**2+480)+6*a3*(pi**6-140*pi**4+6720*pi**2-53760)+pi**6))

harmonicRelativeAmplitudes = []
for i in range(0, numHarmonics) :
    harmonicRelativeAmplitudes.append(harmonicRelativeAmplitude.subs(k, 3 + 2*i))

numCandidateEqs = 2**numHarmonics
numSignCombinations = 2**numEq
useHarmonics = range(numEq + 1)

bestSolution = []
bestRelativeAmplitude = 1
bestUnevaluatedRelativeAmplitude = 1
numSolutions = binomial(numHarmonics, numEq + 1)*2**numEq
solutionIndex = 0

for i in range(0, numCandidateEqs) :
    temp = i
    candidateNumHarmonics = 0
    j = 0
    while (temp) :
        if (temp & 1) :
            if candidateNumHarmonics < numEq + 1 :
                useHarmonics[candidateNumHarmonics] = j
            candidateNumHarmonics += 1
        temp >>= 1
        j += 1
    if (candidateNumHarmonics == numEq + 1) :
        for j in range(0,  numSignCombinations) :
            eqs = []
            temp = j
            for n in range(0, numEq) :
                if temp & 1 :
                    eqs.append(harmonicRelativeAmplitudes[useHarmonics[0]] - harmonicRelativeAmplitudes[useHarmonics[1+n]])
                else :
                    eqs.append(harmonicRelativeAmplitudes[useHarmonics[0]] + harmonicRelativeAmplitudes[useHarmonics[1+n]])
                temp >>= 1
            solution = solve(eqs, coefficients, manual=True)
            solutionIndex += 1
            print "Candidate solution %d of %d" % (solutionIndex, numSolutions)
            print solution
            solutionRelativeAmplitude = harmonicRelativeAmplitude
            for n in range(0, numEq) :                
                solutionRelativeAmplitude = solutionRelativeAmplitude.subs(coefficients[n], solution[0][n])
            solutionRelativeAmplitude = factor(solutionRelativeAmplitude)
            print solutionRelativeAmplitude
            solutionWorstRelativeAmplitude = 0
            for n in range(0, numHarmonics) :
                solutionEvaluatedRelativeAmplitude = abs(factor(solutionRelativeAmplitude.subs(k, 3 + 2*n)))
                if (solutionEvaluatedRelativeAmplitude > solutionWorstRelativeAmplitude) :
                    solutionWorstRelativeAmplitude = solutionEvaluatedRelativeAmplitude
            print solutionWorstRelativeAmplitude
            if (solutionWorstRelativeAmplitude < bestRelativeAmplitude) :
                bestRelativeAmplitude = solutionWorstRelativeAmplitude
                bestUnevaluatedRelativeAmplitude = solutionRelativeAmplitude                
                bestSolution = solution
                print "That is a new best solution!"
            print

print "Best Solution is:"
print bestSolution
print bestUnevaluatedRelativeAmplitude
print bestRelativeAmplitude

This is a variation on Robert's answer, and is the route I eventually took. I'm leaving it here in case it helps anyone else.
Guest

wow, solving it analytically. i woulda just used MATLAB and an FFT and sorta hunt around for the answer.
you did very well.
robert bristow-johnson

2
actually @OlliNiemitalo, i think -79 dB is good enough for the implementation of a digital synth sine wave oscillator. it can be driven by a triangle wave, which is generated easily from the abs value of a sawtooth, which is most easily generated with a fixed-point phase accumulator.
no one will hear a difference between that 5th-order polynomial sine wave and a pure sine.
robert bristow-johnson

1
Polynomials in general as f have the advantage that by increasing the order, the error can be made arbitrarily small. Rational functions have the same advantage, but a division is typically more costly to compute than multiplication. For example in Intel i7, a single thread can do 7-27 times as many multiplications and additions than divisions in the same time. Approximating some alternative f means decomposing it to elementary ops, typically multiplications and additions which always amount to polynomials. Those could be optimized to approximate sine directly versus via f.
Olli Niemitalo

1
@OlliNiemitalo, I see what you mean... if division is that much slower than multiplication (and I guess things like roots / fractional exponents will be even worse), then an approach like the above with a "good, fast f0" is going to wind up factoring out to a Taylor-series-like-polynomial anyway. I guess since it's an approximation anyway, some kind of cheap root approximation could potentially overtake the polynomial approach at some level of accuracy, but that's kinda off in the weeds for what was essentially supposed to be a math question.
Guest

5

Are you asking this for theoretical reasons or a practical application?

Usually, when you have an expensive to compute function over a finite range the best answer is a set of lookup tables.

One approach is to use best fit parabolas:

n = floor( x * N + .5 );

d = x * N - n;

i = n + N/2;

y = L_0 + L_1[i] * d + L_2[i] * d * d;

By finding the parabola at each point that meets the values for d being -1/2, 0, and 1/2, rather than using the derivatives at 0, you ensure a continuous approximation. You could also shift the x value, rather than the array index to deal with your negative x values.

Ced

=================================================

Followup:

The amount of effort, and the results, that have gone into finding good approximations is very impressive. I was curious as to how my boring and bland piecewise parabolic solution would compare. Not surprisingly, it does much better. Here are the results:

   Method    Minimum    Maximum     Mean       RMS
  --------   --------   --------   --------   --------
     Power   -8.48842    1.99861   -4.19436    5.27002
    OP S_3   -2.14675    0.00000   -1.20299    1.40854
     Bhask   -1.34370    1.63176   -0.14367    0.97353
     Ratio   -0.24337    0.22770   -0.00085    0.16244
     rbj 5   -0.06724    0.15519   -0.00672    0.04195
    Olli5C   -0.16367    0.20212    0.01003    0.12668
     Olli5   -0.26698    0.00000   -0.15177    0.16402
    Olli7C   -0.00213    0.00000   -0.00129    0.00143
     Olli7   -0.00005    0.00328    0.00149    0.00181
    Para16   -0.00921    0.00916   -0.00017    0.00467
    Para32   -0.00104    0.00104   -0.00001    0.00053
    Para64   -0.00012    0.00012   -0.00000    0.00006

The values represent 1000x the error between the approximation and the actual evaluated every .0001 from a scale of 0 to 1 (inclusive), so 10001 points in all. The scale is converted to evaluate the functions from 0 to π/2, except for Olli Niemitalo's equations which use the 0 to 1 scale. The columns values should be clear from the headers. The results don't change with a .001 spacing.

The "Power" line is the equation: xxe6.

The rbj 5 line is the same as Matt L's c5 solution.

The 16, 32, and 64 are the number of intervals that have parabolic fits. Of course there are insignificant discontinuities in the first derivative at each interval boundary. The values of the function are continuous though. Increasing the number of intervals only increases the memory requirements (and initialization time), it does not increase the amount of calculation needed for the approximation, which is less than any of the other equations. I chose powers of two because a fixed point implementation could save a division by using an AND in such cases. Also, I didn't want the count to be commensurate with the test sampling.

I did run Olli Niemitalo's python program and got this as part of the printout: "Candidate solution 176 of 120" I thought that was odd, so I am mentioning it.

If anybody wants me to include any of the other equations, please let me know in the comments.

Here is the code for the piecewise parabolic approximations. The entire test program is too long to post.

#=============================================================================
def FillParab( argArray, argPieceCount ):

#  y = a d^2 + b d + c

#  ym = a .25 - b .5 + c
#  y  =                c
#  yp = a .25 + b .5 + c

#  c = y
#  b = yp - ym
#  a = ( yp + ym - 2y ) * 2

#---- Calculate Lookup Arrays

        theStep = pi * .5 / float( argPieceCount - 1 )
        theHalf = theStep * .5

        theL0 = zeros( argPieceCount )
        theL1 = zeros( argPieceCount )
        theL2 = zeros( argPieceCount )

        for k in range( 0, argPieceCount ):
         x  = float( k ) * theStep

         ym = sin( x - theHalf )
         y  = sin( x )
         yp = sin( x + theHalf )

         theL0[k] = y
         theL1[k] = yp - ym
         theL2[k] = ( yp + ym - 2.0 * y ) * 2

#---- Do the Fill

        theN = len( argArray )

        theFactor = pi * .5 / float( theN - 1 )

        for i in range( 0, theN ):
         x  = float( i ) * theFactor

         kx = x / theStep
         k  = int( kx + .5 )
         d  = kx - k

         argArray[i] = theL0[k] + ( theL1[k] + theL2[k] * d ) * d

#=============================================================================

=======================================

Appendum

I have included Guest's S3 function from the original post as "OP S_3" and Guest's two parameter formula from the comments as "Ratio". Both are on the 0 to 1 scale. I don't think the Ratio one is suitable for either calculation at runtime or for building a lookup table. After all, it is significantly more computation for the CPU than just a plain sin() call. It is interesting mathematically though.


Good work! I fixed that bug ("176 of 120").
Olli Niemitalo

Nice update, this makes more sense to me now. The xxe6 probably doesn't need to be tested, I just threw it out there because I was trying to figure out the significance of e which seemed to keep popping up while I was playing with this. A better rational expression to test might be something like this: f0(x)=|x|asign(x) ; b=f0(1) ; f1(x)=f0(x)bx ; c=1f1(1) ; f2(x)=f1(x)c ... now a should be set to about 223...
Guest

...or f0(x) can be pretty much any other odd-symmetrical function; sigmoids seem to work well, like ax1ax+1 (but then the right value for a needs to be found, of course). Here's a plot... as Olli mentions, this probably isn't practical for on-the-fly computation, but I guess it could be useful for building a lookup table.
Guest

Or a more accurate 2-param version of that, a0xa1xa0x+a1x looks pretty good with a013 and a1109
Guest
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.