W analizie pewnego modelu dotyczącego transportu morfogenów w tkankach [16] istotną rolę odgrywa funkcja
a ściślej: funkcja odwrotna do niej, . Naszym zadaniem jest naszkicowanie wykresu dla różnych wartości i zbadanie, jak bardzo jest bliskie 1, gdy jest rzędu kilkudziesięciu.
Jasne jest, że wszelkie nasze komputerowe działania musimy zacząć od zdefiniowania funkcji . Zrobimy to w Octave:
function v = sigmafun(s,M) v = (2/(M-1))*(1-(1-s).^(M-1)) - s.*(1-s).^(M-1); end
(Dalsze rozważania będziemy prowadzić dla ). Jeśli chcieć tylko z grubsza naszkicować wykres odwrotnej, to zadanie robi się banalne: wykres to zbiór punktów postaci
a to jest przecież ,,to samo”, co zbiór
Wobec tego wystarczy skorzystać z funkcji plot
, ale ,,na opak”:
M = 0.5; s = linspace(0,1); plot(sigmafun(s,M), s, 'r-', 'linewidth', 2);
Z wykresu na rysunku 3.1 widzimy, że bardzo szybko przybliża się do wartości granicznej równej 1, co sugerowałoby wybór innej skali dla wizualizacji: tym, co mogłoby nas naprawdę zainteresować, mogłaby być szybkość przybliżania się do 1: wybralibyśmy więc wówczas, zamiast zwykłego plot
, znacznie bardziej wyraziste semilogy(sigmafun(s,M), 1-s)
. Jednak dla ustalenia uwagi, w dalszym ciągu pozostaniemy przy wykresie w skali liniowej.
Niepokojący jest prawy koniec wykresu: wszak jest nieokreślone! Zatem doprecyzujmy nasze zadanie: w naszym konkretnym zastosowaniu, chcielibyśmy wiedzieć, ze względu na naturę badanego zjawiska, jak blisko 1 jest i narysować wykres na odcinku .
Napiszemy zatem w Octave funkcję, która będzie wyznaczać wartości funkcji odwrotnej do dla dowolnego zadanego . Idea jest następująca: z definicji funkcji odwrotnej, , gdzie spełnia , a więc jest miejscem zerowym funkcji
Aby dla zadanego znaleźć , wystarczy znaleźć miejsce zerowe skalarnej funkcji — a to już powinno być łatwe, gdy skorzystamy z funkcji fzero
. Powyższą ideę implementuje funkcja sigmainv
z poniższego listingu.
function [S, fc] = sigmainv(z, M) if z == 0 S = 0; fc = 0; else [S, FS, info,out] = fzero(@(s)Fz(s,z,M), [0,1-eps/2]); if(info ~= 1) warning(['Klopoty dla z=', num2str(z), '; info=', num2str(info)]); end fc = out.funcCount; % zliczamy liczbe wywolan Fz end end % sigmainv function Z = Fz(S, z, M) % "z" jest parametrem Z = sigmafun(S, M)-z; end
Zanim przyjrzymy się meritum funkcji sigmainv
, zwróćmy uwagę na kilka programistycznych szczegółów. Jak pamiętamy, fzero
żąda funkcji jednego argumentu, dlatego, dla każdego zadanego , konstruujemy taką funkcję ad hoc — na podstawie trzyargumentowej Fz(S,z,M)
— korzystając z mechanizmu funkcji anonimowej: funkcja
f = @(s)Fz(s,z,M)jest funkcją jednej zmiennej, z wartościami i takimi, jakie były w chwili jej definiowania. Samą funkcję
Fz
określiliśmy zaś jako funkcję lokalną dla sigmainv
. Wreszcie, aby uniknąć konsternacji fzero
dla (czym spowodowanej?) zwracamy zawsze , bez niepotrzebnych obliczeń.
Jest oczywiste, że jest monotoniczna i może przyjmować wartości z przedziału . Dlatego wiedząc, że dla funkcja jest nieokreślona, przedział lokalizujący miejsce zerowe ustaliliśmy na [0,1-eps
/2], gdyż 1-eps
/2 jest największą liczbą maszynową mniejszą od 1 (dlaczego?).
Pozornie ,,asekurancki” sposób zdefiniowania funkcji sigmainv
, w której zawsze sprawdzamy wartość parametru info
zwracaną przez fzero
, jest w istocie bardzo ważną i sensowną decyzją, pozwalającą wstępnie zweryfikować wyniki. Wszak nie wiemy a priori, czy nasz solver sobie poradzi z !
Chociaż wykres dostajemy bez trudu, stosując prostą metodę kontynuacji — biorąc za przybliżenie początkowe wartość z poprzedniego punktu wykresu (por. wykład z Matematyki Obliczeniowej II), to przychodzi nam nań czekać dość długo. Gdy ponownie spojrzymy na wykres , sprawa staje się jasna: przecież dla , ma piekielnie stromy wykres, a to znacznie utrudnia działanie solvera powodując, że praktycznie ogranicza się on do metody bisekcji. Aby uniknąć tej niedogodności i znacząco przyspieszyć rysowanie wykresu, skorzystamy ze starej, dobrej zasady prowadzenia obliczeń numerycznych (i nie tylko):
Gdy zadanie jest za trudne, należy zmienić… zadanie!
Tutaj po prostu zastąpimy ,,trudną” funkcję inną, ,,łatwiejszą”, ale o identycznych miejscach zerowych. Prosty rachunek pokazuje nam, że jest rozwiązaniem równania wtedy i tylko wtedy, gdy jest miejscem zerowym funkcji
Różnica między a jest taka, że ta ostatnia jest prawie liniowa, więc rzeczywiście powinna być ,,łatwa” dla solvera równania nieliniowego. Przy okazji, nie ma już osobliwości: jest określona nawet dla .
Wyznaczanie wartości przez znalezienie miejsca zerowego funkcji pokazuje szczegółowo kolejny listing.
function [S, fc] = sigmainv2(z,M) if z == 0 S = 0; fc = 0; else [S, FS, info, out] = fzero(@(s)Gz(s,z,M), [0,1]); if(info ~= 1) warning(['Klopoty dla z=', num2str(z), '; info=', num2str(info)]); end fc = out.funcCount; % zliczamy liczbe wywolan Gz end end % sigmainv2 function Z = Gz(S, z, M) % "z" jest parametrem p = 2/(1-M); Z = 1 - S - ((p - S)./(p+z)).^(p/2); end
Faktycznie, dzięki tej zmianie wykres (rysunek 3.2) dostajemy trzy razy szybciej aniżeli w przypadku poprzedniego podejścia, przy liczbie wywołań funkcji sześciokrotnie mniejszej niż w przypadku .
Na zakończenie warto być może zauważyć, że w dzisiejszych czasach obliczenia są na tyle tanie, że nie zawsze warto odwoływać się do aż tak wyrafinowanych metod. Gdyby naszym głównym zadaniem było narysowanie, wyłącznie w celach poglądowych, jednego wykresu na odcinku , to zapewne byłoby nam dość obojętne, czy wyznaczymy sto, sto tysięcy, czy nawet milion wartości sigmafun
: nasz komputer najpewniej i tak jest wystarczająco szybki, by z tym sobie błyskawicznie poradzić. Komenda logspace(k,m,N)
pozwala wygenerować zestaw węzłów ,,równoodległych w skali logarytmicznej”, czyli postaci , gdzie tworzą zestaw równoodległych punktów z przedziału .
Generując więc zestaw punktów skupiających się wokół , a następnie wybierając tylko te, dla których :
N = 10000; s = 1-logspace(-13,0,N); % punkty zagęszczają się wykładniczo wokół 1 Z = sigmafun(s,M); good = find(Z<=60); % odrzucamy te, które wykraczają poza zakres zmiennej "z" plot(Z(good), s(good));
udałoby się nam w ten sposób (nawet jeszcze szybciej, niż poprzednimi metodami!) ,,dociągnąć” wykres do , co jest chyba całkiem niezłym wynikiem, jak na metodę brutalnej siły…
(Ale i tutaj ważne jest wyczucie: gdyby nieopatrznie położyć s = 1-logspace(-300,0,N)
, dostalibyśmy znacznie gorszy wynik).
Czasami funkcja może być na tyle złośliwa, że zamiast zmieniać jest postać — jak to zrobiliśmy powyżej — możemy spróbować skorzystać z mniej wyrafinowanej, a za to ogólniejszej metody rozwiązywania równania nieliniowego . Taką bardzo prostą metodą, o minimalnych wymaganiach wobec , jest metoda bisekcji (zadowoli się funkcją ciągłą, zmieniającą znak). Zaimplementuj metodę bisekcji i zobacz, jak sprawdzi się w warunkach naszego problemu dla oryginalnej funkcji .
Treść automatycznie generowana z plików źródłowych LaTeXa za pomocą oprogramowania wykorzystującego LaTeXML.
strona główna | webmaster | o portalu | pomoc
© Wydział Matematyki, Informatyki i Mechaniki UW, 2009-2010. Niniejsze materiały są udostępnione bezpłatnie na licencji Creative Commons Uznanie autorstwa-Użycie niekomercyjne-Bez utworów zależnych 3.0 Polska.
Projekt współfinansowany przez Unię Europejską w ramach Europejskiego Funduszu Społecznego.
Projekt współfinansowany przez Ministerstwo Nauki i Szkolnictwa Wyższego i przez Uniwersytet Warszawski.