Wielomian w pythonie

0

Witam mam za zadanie napisanie wielomianu Lagaraga w pythonie.

Stworzylem takie cos:

#!/usr/bin/python

xs = [0.43, 0.48, 0.55, 0.62, 0.70, 0.75 ];
ys = [1.63597, 1.73234, 1.87686, 2.03045, 2.22846, 2.35973];
t = 0.0;
y = 0.0;
k = 0;

def lagrange(xs, ys,  x ):
    
    for k in range(len(xs)):
        t = 1.0;
        for j in range(len(xs)):
                if(j != k ):
                    t=t*((x-xs[j])/(xs[k]-xs[j]));
    y += t*ys[k];
    
    print y

Problem w tym ze pydev nie zwraca mi ani żadnego błędu ani wyniku. Mógłbym prosić o pomoc bardziej zaawansowanych ?

1

@balu, ja widzę, że istnieje sobie funkcja lagrange... a wywołujesz ją w ogóle? Bo z kodu to raczej nie wynika...

1
  1. dość trywialne pytanie - definiujesz tutaj funkcję, natomiast jej nigdzie nie wywołujesz. W pełnym kodzie jeśli chcesz żeby się coś działo musisz zrobić coś w stylu:
lagrange(xs, ys, 2)
y += t*ys[k];

Spowoduje błąd UnboundLocalError - zmienna lokalna y jest czytana przed zapisaniem do niej jakiejś wartości. musisz zrobić albo global y albo y = 0 na początku funkcji.

  1. Poza tym poprawności kodu nie sprawdzałem.
0

Fakt. Początki pythona dla mnei to jest niezła jazda:


#!/usr/bin/python

x = [0.43, 0.48, 0.55, 0.62, 0.70, 0.75 ];
y = [1.63597, 1.73234, 1.87686, 2.03045, 2.22846, 2.35973];
t = 0.0;
y = 0.0;
k = 0;

def lagrange(xs =[],ys=[],x ):
    global y 
    for k in range(len(xs)):
        t = 1.0;
        for j in range(len(xs)):
                if(j != k ):
                    t=t*((x-xs[j])/(xs[k]-xs[j]));
    y += t*ys[k];
    return y;
    
y2 =  lagrange(x,y,6);
print(y2)

Powiedz mi co teraz robię źle ?

def lagrange(xs =[],ys=[],x ):
SyntaxError: non-default argument follows default argument

2
  1. To co pisał @msm, przenieś to y=0.0 do funkcji
  2. Nie możesz definiować funkcji w ten sposób, że na liście argumentów masz najpierw argument z domyślną wartością xs =[], a po nim taki, któremu wartość musisz dopiero nadać x

Nie sprawdzałem pod kątem poprawności, co i jak ma liczyć, ale te dwa punkty wystarczą, żeby program nie rzucał błędami...

#!/usr/bin/python
 
x = [0.43, 0.48, 0.55, 0.62, 0.70, 0.75 ];
y = [1.63597, 1.73234, 1.87686, 2.03045, 2.22846, 2.35973];
t = 0.0;

k = 0;
 
def lagrange(xs,ys,x ):
    y = 0.0;
    for k in range(len(xs)):
        t = 1.0;
        for j in range(len(xs)):
                if(j != k ):
                    t=t*((x-xs[j])/(xs[k]-xs[j]));
    y += t*ys[k];
    return y;
 
y2 =  lagrange(x,y,6);
print(y2)

auć... to for k in range(len(xs)): boli :(

0

No dobra zaraz sobie policzę czy ok bo mam analogiczny program w javie.

Co do tego co napisałeś auć... to for k in range(len(xs)): boli :(

to co dać zwykłe n

bo mam stała liczbę tych kroków równa np 6 wiec jak radzisz ?

1

Ok... Taka konstrukcja pętli jakiej użyłeś jest może nie tyle całkiem błedna co trochę dziwna w pythonie.

x = [0.43, 0.48, 0.55, 0.62, 0.70, 0.75 ]
for k in range(len(x)):
   print k

Oczywiście wypisze wartości z listy x, ale..

x = [0.43, 0.48, 0.55, 0.62, 0.70, 0.75 ]
for k in x:
   print k

Taki zapis zrobi dokładnie to samo i jest bardziej "elegancki" - nie musisz iterować po kolejnych elementach listy wg ich indeksu (robisz to używając range(len(x)), ale iterujesz bezpośrednio po elementach listy.

0

Ten zapis po ktorym podales to nie chce iterowac, po pisze ze elementy musza byc intami takze chyba zostane przy tym len.

0

A teraz mi powiedz madmike jeszcze taką ciekawostkę. W ten sposób uzyskuje gotowy wynik. A co zmienić bo się zastanawiam żeby otrzymywać taki ciąg np:

1/2*x^3 + 1/4*x^2 itd itd bo ten program w pewnej fazie to robi a potem mi to wylicza. Mógłbyś jeszcze mnie na to naprowadzić

Ne pewno to się dzieje w t, kombinuje kombinuje i nic

1

Ok, najpierw jedno - jeśli potrzebujesz z listy daną i jej indeks stosujesz sobie enumerate (sprawdz i w dokumentacji i na przykładzie jak poniżej)

lista = [0.43, 0.48, 0.55, 0.62, 0.70, 0.75 ]
for indeks, dane in enumerate(lista):
   print indeks, dane

da taki wynik:

0 0.43
1 0.48
2 0.55
3 0.62
4 0.7
5 0.75

a każdym "przejściem" pętli indeks (czyli pozycja) i dana (kolejna z listy) zostaną wypisane - to np. kiedy potrzebujesz porównać czy np. dane nie leżą na "skrzyżowaniu" tabeli, coś w rodzaju tego j != k

Powyżej masz normalne działanie, jedynie dochodzi potęgowanie - zobacz http://docs.python.org/2/library/functions.html#pow

0

Nie zrozumieliśmy się. Tym sposobem obliczam za pomocą wielomianu Larange : http://aragorn.pb.bialystok.pl/~mosdorf/WYK/mn/Metoda_Lagrange'a.htm
wielomian i dostaje ostateczny wynik. Nie zauważyłem a w punkcie b) mam zrobić program który będzie robił to samo i w dodatku zwróci ten wielomian. Mógłbyś mi jeszcze to jakoś naświetlić ?

2

Najpierw trochę przepisywania z linku. Zadanie jest takie:

  • masz funkcję określoną w przedziale [a, b]
  • masz zbiór n punktów w tym przedziale i wartości dla nich
  • i chcesz przybliżyć (interpolować) wartość funkcji w tym przedziale jakimś tam wielomianem stopnia n (wielomianem lagrange'a dokładnie).

Wielomian ten ma jak najbardziej przybliżać wartość funkcji, w szczególności w punktach xi ma przyjmować wartości yi (w podanych punktach ma mieć dokładnie taką samą wartość jak powinien).

Można interpolować na dwa sposoby, albo obliczać bezpośrednio z definicji wartość w każdym punkcie, albo obliczać od razu wielomian a później z niego korzystać. To drugie jest trudniejsze, więc zakładam że chodzi o pierwsze (i jest to to co Ty próbujesz najwyraźniej robić).

Twój kod po niewielkich zmianach (wolałbym większe, np. dzielenie punktów na dwie tablice brzydko wygląda, ale nie o to chodzi w zadaniu):

# podane wartości punktów
xs = [0, 1, 2]
ys = [2, 4, 8]
# czytaj: f(0) = 2, f(1) = 4, f(2) = 8

# teraz interpolacja z definicji:
def interpolate(xs, ys, x):
    result = 0.0;
    for i, xi in enumerate(xs):
        t = 1
        for j, xj in enumerate(xs):
            if j != i:
                t *= (x - xj) / float(xi - xj)
        result += ys[i] * t
    return result

# teraz test czy interpolacja zachowuje się jak powinna:
for x in [0.0, 0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0]:  # punkty w których testujemy funkcję (da się krócej, ale tak jest najprościej)
    print x, interpolate(xs, ys, x)

Wynik:

0.0 2.0  # f(0) = 2
0.25 2.3125
0.5 2.75
0.75 3.3125
1.0 4.0  # f(1) = 4
1.25 4.8125
1.5 5.75
1.75 6.8125
2.0 8.0  # f(2) = 8

Jak widać w podanych punktach interpolacja jest dokładna, a pomiędzy nimi przyjmuje wartości pośrednie - można z tego wnioskować że kod jest poprawny,

A teraz powiedz mi, czy o to Ci chodziło? Bo zostałem wywołany przez @madmike, a podobnie jak on nie do końca rozumiem intencje ;]

0

Ładnie to opisałeś i za to masz +, ale nie do końca o to mi chodziło. Ja normalnei już te punkty mam podane

te punkty czyli to:

0.0 2.0 # f(0) = 2
0.25 2.3125
0.5 2.75
0.75 3.3125
1.0 4.0 # f(1) = 4
1.25 4.8125
1.5 5.75
1.75 6.8125
2.0 8.0 # f(2) = 8

Po prostu chodziło mi o to że w pewnym momencie ta funkcja liczy takie coś jak w załączniku i jako wynik zwracało mi coś takiego i również same współczynniki czyli 5/6, -5, 43/6,0

Wrzucam tu przykładowo o co mi chodzi:

4

Można interpolować na dwa sposoby, albo obliczać bezpośrednio z definicji wartość w każdym punkcie, albo obliczać od razu wielomian a później z niego korzystać. To drugie jest trudniejsze, więc zakładam że chodzi o pierwsze (i jest to to co Ty próbujesz najwyraźniej robić).

Czyli jednak o to drugie chodziło.

W takim razie z wikipedii:

Wielomian lagrange'a to suma (ściśle, liniowa kombinacja) wielomianów:
L(x) = \sum_{j=0}^{k} y_j \ell_j(x)

gdzie lj to:
\ell_j(x) = \prod \frac{x-x_m}{x_j-x_m}

(trochę wiecej na ten temat na wspomnianej wiki, nie ma co tego przepisywać.)

Teraz jeśli chcemy wyliczyć ten wielomian z definicji, możemy po prostu wyliczyć wszystkie lj a później je odpowiednio dodać.

Tym razem inne dane:

xs = [1, 2, 3, 4]
ys = [3, 1,-1, 2]

Potrzebna będzie pomocnicza funkcja do dodawania list:

def addv(vec_a, vec_b):
    return [a + b for (a, b) in zip(vec_a, vec_b)]

Dodaje ona po prostu odpowiadające sobie elementy obu list:

>>> addv([1, 1, 1], [2, 3, 4])
[3, 4, 5]

Jeszcze rzecz o której nie napisałem - wielomian np. x^3 + 2x^2 + 5x - 2 jest reprezentowany najprościej jak się da, czyli listą [1, 2, 5, -2] (każdy element reprezentuje odpowiedni współczynnik).

I teraz faktyczny kod. Najpierw obliczanie lj:

def lagrange_basis(xs, j):
    coeffs, denominator = [1.0], 1.0
    for m in range(len(xs)):
        if m != j:
            coeffs = addv(coeffs + [0], [0] + [-xs[m] * v for v in coeffs])
            denominator *= xs[j] - xs[m]
    return [c / denominator for c in coeffs]

Patrz:
\ell_j(x) = \prod \frac{x-x_m}{x_j-x_m}

Obliczanymy tu niezależnie licznik (coeffs) i mianownik (denominator) (można by najpierw obliczyć mianownik i ustawić jako wartość początkową licznika, ale to IMO mniej czytelne), po czym je dzielimy żeby uzyskać wartość ułamka.

Mianownik tego ułamka to po prostu (xj - x0) * (xj - x1) * (xj - x2) ... (xj - xk). W obliczaniu go nie ma nic skomplikowanego chyba (po prostu dla każdego xm mnożymy go przez (xj - xm).
Licznik jest trochę bardziej skomplikowany, bo zawiera mnożenie wielomianów (mnożymy (x - x0) * (x - x1) ... (x - xk)). Na szczeście wszystkie wielomiany przez które mnożymy są postaci (x - n), więc korzystamy z:
(ax^4 + bx^3 + cx^2 + dx + e) * (x - n) = x*(ax^4 + bx^3 + cx^2 + dx + e) - n*(ax^4 + bx^3 + cx^2 + dx + e)
Niezbyt odkrywcze, wiem, ale jako że to ax^4 + bx^3 + cx^2 + dx + e to u nas tablica coeffs, to wynik takiego mnożenia wynosi właśnie addv(coeffs + [0], [0] + [-xs[m] * v for v in coeffs]).
([0] + w [0] + [-xs[m] * v ...] jest konieczne, inaczej dodawane tablice byłyby różnej długości).

Mając tą funkcję, obliczenie wielomianu sprowadza się do dodania pomnożonych przez odpowiedni współczynnik wielomianów:

def interpolate(xs, ys):
    coeffs = [0 for x in xs]
    for i in range(len(xs)):
        basis = lagrange_basis(xs, i)
        coeffs = addv(coeffs, [ys[i] * c for c in basis])
    return coeffs

Wszystko chyba jest dość oczywiste, więc niezbyt jest co tłumaczyć - po prostu obliczamy dla każdego i lagrange_basis(xs, i), mnożymy przez ys[i] (jak we wzorze), i dodajemy.

Łącząc wszystko:

xs = [1, 2, 3, 4]
ys = [3, 1,-1, 2]

def addv(vec_a, vec_b):
    return [a + b for (a, b) in zip(vec_a, vec_b)]

def lagrange_basis(xs, j):
    coeffs, denominator = [1.0], 1.0
    for m in range(len(xs)):
        if m != j:
            coeffs = addv(coeffs + [0], [0] + [-xs[m] * v for v in coeffs])
            denominator *= xs[j] - xs[m]
    return [c / denominator for c in coeffs]

def interpolate(xs, ys):
    coeffs = [0 for x in xs]
    for i in range(len(xs)):
        basis = lagrange_basis(xs, i)
        coeffs = addv(coeffs, [ys[i] * c for c in basis])
    return coeffs

print interpolate(xs, ys)

Otrzymujemy wynik wyglądający na prawidłowy:

$ python test.py
[0.8333333333333333, -5.0, 7.166666666666666, 0.0]

Do porównania z:
user image

Ofc można to wypisać czytelniej, czyli jako
0.8333*x^3 - 5*x^2 + 7.1666*x
Kwestia gustu/wymageń.

Sprawdź czy tym razem o to Ci chodziło.

1

Na pewno na pewno. Nie dość że program do sprawka mam odpicowany to jeszcze się za jednym zamachem na wejściówkę nauczyłem :D Jak widać @madmike da się odpowiadać kulturalnie i bez wywyższania się. Ty wiesz o czym ja tu mówię :D

1 użytkowników online, w tym zalogowanych: 0, gości: 1