Wstęp do analizy matematycznej

Granice, nieskończoności i symbole nieoznaczone

Symbole nieoznaczone: ∞-∞,∞/∞, 0/0, (0^0), (1^∞), ∞*0, (∞^0)

Mathematica potrafi operować na tzw. rozszerzonej osi rzeczywistej a także płaszczyźnie zespolonej. Nieskończoność oznaczamy poleceniem Infinity, a także (w przypadku zespolonym) DirectedInfinity oraz ComplexInfinity. Nieskończoność można wpisać z klawiatury klawiszami ESC inf ESC.

Poniżej przykłady dobrze określonych działań zawierających symbol ∞:

1+Infinity

∞*∞

(∞^∞)

1/∞

0^Infinity

2 ∞

Niektórych działań nie można w ten sposób wykonać. Istnieje siedem klasycznych symboli nieoznaczonych wymienionych w nagłówku, np:

1^Infinity

∞-∞

∞^0

Wynik takiego działania nie jest określony. W granicy może mieć on jednak sens, np:

Sqrt[n^2+2]-Sqrt[n^2+n]

%/.n->Infinity

Limit[Sqrt[n^2+2]-Sqrt[n^2+n],n->Infinity]

Poniższy przykład pokazuje, że symbol nieoznaczony może przyjąć w granicy dowolną wartość:

Limit[Sqrt[n^2+1]-Sqrt[n^2-2a n],n->Infinity]

W poniższym przykładzie z kolei, ∞-∞ w granicy jest równe +∞:

Sqrt[n]-Log[n]

%/.n->Infinity

Limit[%%,n->Infinity]

Poniżej przykład, w którym wynikiem jest interwał:

Sin[Infinity]

Cos[%]

Mathematica pozwala na wykonywanie obliczeń w arytmetyce interwałów:

Sqrt[Interval[{0,1}]^1+1]

Arytmetyka interwałów może być użyta np: do bardzo konserwatywnego szacowania błędów. Błąd tak otrzymany jest zwykle większy niż otrzymany np: z różniczek czy analizy statystycznej.

n^(1/n)

%/.n->Infinity

Limit[n^(1/n),n->Infinity]

Limit[n^(1/n!),n->Infinity]

Limit[n^(1/Sqrt[Log[n]]),n->Infinity]

Limit[n!^(1/n),n->Infinity]

DirectedInfinity[1]

DirectedInfinity[1+I]

%+%%

Simplify[%]

FullForm[%%]

TreeForm[%]

1/%

FullSimplify[%]

Obliczanie granic lewo- i prawostronnych

Bardzo często musimy obliczać granice lewo- i prawostronne. W notacji tradycyjnej piszemy (dla przykładu granica jest w zerze):

x -> (SuperPlus[0]) : x zmierza do zera od strony liczb dodatnich (inaczej: x dąży do zera, jest stale dodatnie, ale coraz mniejsze)

W Mathematice napiszemy x->0, Direction[-1] (x dąży do zera w kierunku liczb ujemnych)

x->(0^(- )):  x zmierza do zera od strony liczb ujemnych (inaczej: x dąży do zera, jest stale ujemne, ale coraz bliższe zera)

W Mathematice napiszemy x->0, Direction[1] (x dąży do zera w kierunku liczb dodatnich)

Bez dodatkowych opcji granica jest rozumiana jako prawostronna! (Konkretnie, wg. manuala, ustawienie jest Direction->Automatic)

Limit[1/x,x->0]

Options[Limit]

Limit[f[x],x->0]//TraditionalForm

Wiadomo, że granica lewo - prawostronna wyrażenia 1/x w zerze różnią sie:

Limit[1/x,x->0,Direction->1]

Limit[1/x,x->0,Direction->-1]

Limit[f[x],x->0,Direction->1]

TraditionalForm[%]

HoldForm[TraditionalForm[Limit[1/x,x->0,Direction->-1]]]

Limit[Exp[-1/x],x->0]

Limit[Exp[-1/x],x->0,Direction->1]

Plot[Exp[-1/x],{x,-2,2},Exclusions->{0}]

Manipulate[Limit[Tan[x],x->Pi/2,Direction->Exp[I ϕ]],{ϕ,0,2 Pi}]

Szeregi i iloczyny skończone i nieskończone

Przykład 1: suma szeregu geometrycznego

Szkolny przykład: szereg geometryczny:

a[k_]:=q^k

Table[a[k],{k,0,12}]

Sum[a[k],{k,0,n}]

Możemy także obliczać sumy nieskończone:

Sum[a[k],{k,0,Infinity}]

Przykład 2: zbieżność szeregu 1/(k^a)

Suma kwadratów odwrotności jest zbieżna:

Sum[1/k^2,{k,1,Infinity}]

Ale suma odwrotności nie:

Sum[1/k,{k,1,Infinity}]

W ogólności wynik można wyrazić funkcją zeta Riemanna:

Sum[1/k^(3/2),{k,1,Infinity}]

N[%]

Sum[1/k^a,{k,1,Infinity}]

Oto jej wykres, nieco przypomina 1/x :

Plot[{Zeta[a],1/(a-1)+1},{a,1,6},PlotRange->{1,Automatic},PlotStyle->{{Red,Thick},{Black,Thin}}]

Granicą funkcji Zeta w nieskończoności jest 1:

Limit[Zeta[a],a->Infinity]

Limit[Zeta[a],a->1]

Limit[Zeta[a],a->1,Direction->1]

Granica i wykres sugerują, że suma szerego 1/k^a, dla dużego k jest bardzo bliska 1 :

Sum[1/k^8,{k,1,Infinity}]

N[%]

Sum[1/k^21,{k,1,Infinity}]

Liczba wydaje się być dokładnie równa 1:

N[%]

Aby to ustalić, należy 1 odjąć:

%-1

N[Zeta[21],32]

%-1

Sum[1/Prime[k]^2,{k,1,Infinity}]

N[%]

Ciągi liczbowe

Przykład 1: proste ciągi liczbowe zdefiniowane jawnie (podanym wzorem)

Ciągiem liczbowym nazywamy funkcję określoną dla argumentów będących liczbami całkowitymi, zwykle nieujemnymi. W Mathematice nie używa się pojęcia liczby naturalnej, trzeba ją określić jako całkowitą i większą (lub większą lub równą) od zera, np: n∈Integers&&n>0. Standardowy sposób zdefiniowania funkcji użyjemy do zdefiniowania ciągu:

parzyste[n_]:=2n (* liczby parzyste *)

nieparzyste[n_]:=2n-1 (* liczby nieparzyste *)

Table[parzyste[k],{k,1,10}] (* pierwszych 10 liczb parzystych *)

Length[%]

Table[nieparzyste[k],{k,1,10}] (* pierwszych 10 liczb nieparzystych *)

Niektóre ważne ciągi są zdefiniowane w Mathematice:

Table[Prime[k],{k,1,10}] (* 10 pierwszych liczb pierwszych *)

Prime[10^9] (* miliardowa liczba pierwsza *)

Manipulate[Prime[10^n],{n,1,12,1}]

Prime[10^12]//Timing (* bilionowa liczba pierwsza; jej obliczenie zajęło ponad 20 sekund na Corei5-2500K *)

Table[Fibonacci[n],{n,1,10}] (* ciąg Fibonacciego *)

Badając ciągi pamiętajmy, że także z tej sytuacji można i należy korzystać z metod graficznych i numerycznych. Rozważmy ciąg, który często pojawia się w zadaniu ,,oblicz granicę’’:

c[n_]:=Sin[n!]/n

Ciągi rysujemy poleceniem DiscretePlot, aczkolwiek inne polecenie także można zastosować:

DiscretePlot[c[n],{n,1,24},PlotRange->{-1,1}]

Obliczmy kilkadziesiąt pierwszych wyrazów ciągu:

cTbl=Table[c[i],{i,1,24}]

Powyższe symbole zapewne niewiele mówią. Obliczmy ich wartości numeryczne:

cTbl//N

Widać, że wartości powyżej mają tendencję do malenia co do wartości bezwzględnej (Abs):

Abs[%]

Tabelę liczb najwygodniej rysować poleceniem ListPlot:

ListPlot[%]

Moja ulubiona postać:

ListPlot[cTbl,Joined->True,InterpolationOrder->0,PlotRange->{-1,1}]

Granica w nieskończoności,  to w naiwnym rozumieniu wartość dla bardzo dużej wartości indeksu:

c[10^9]

c[10^7]//N

$MaxExtraPrecision=Infinity (* UWAGA: ryzyko zawieszenie komputera!!! *)

Table[N[c[10^k],32],{k,1,5}]  (* UWAGA: dla k>5 baaaardzo długi czas obliczeń!!! *)

IntegerExponent[10^9!]

10^9!;

N[Sin[10^9!],12]

Obliczmy granicę symbolicznie, uzywając Limit:

Limit[c[n],n->Infinity]

b[n_]:=(3^n+2^n + (-2)^n)^(1/n)

DiscretePlot[b[k],{k,1,12},PlotRange->{0,4}]

b[10^3]//N

N[b[10^4],32]

Limit[b[k],k->Infinity]

Table[{k,b[k]},{k,1,32}]//N//TableForm

Przykład2: ciąg (Subscript[d, n])=(a^n)n!/(n^n)

Ciekawy przykład możliwości analizy ciągu liczbowego dostarcza powyższy, klasyczny przykład. Typowe zadanie to oblicz/udowodnij ile wynosi granica takiego ciągu. Wynik zależy w nietrywialny sposób od parametru a. Definiujemy funkcję dwóch zmiennych dla wygody:

d[a_,n_]:=a^n n!/n^n

Narysujmy wyrazy ciągu. Korzystam przy tym z wiedzy, że interesujące rzeczy dzieją się dla 2<a<3.

Manipulate[DiscretePlot[d[a,k],{k,1,nMAKS}],{a,2,3,0.001},{nMAKS,{10,100,1000}}]

Wnioski z analizy graficznej są nastepujące:
a) ciąg jest malejący dla a≈2
b) ciąg jest rosnący dla a≈3
b) w okolicy a≈2.5 ciąg najpierw rośnie, by nastepnie maleć

Naiwne rozumowanie sugeruje, że wykres funkcji d[a, k], gdzie k to duża liczba, powinien dać nam granicę jako funkcję a:

Plot[d[a,10^3],{a,2,3}]

Wykres zdaje się sugerować, że dla a<2.8 granica wynosi zero, a dla a>2.8 nieskończoność. Ale wykres w skali logarytmicznej nie jest już taki sugestywny:

LogPlot[d[a,10^3],{a,2,3}]

Spróbujmy obliczyć granicę symbolicznie:

Limit[d[a,k],k->Infinity]

Najpóźniej teraz powinniśmy sobie przypomnieć ogólne zasady operowania w Mathematice. Jeżeli oczekujemy wyniku dla dowolnego a, to sprawdźmy, czy jesteśmy w stanie uzyskać go dla jakiegokolwiek a:

Nadając a konkretne wartości liczbowe dostajemy odpowiedź:

Table[Limit[d[a,k],k->Infinity],{a,2,3,1/4}]

Spróbujmy z a od 2 do 3 do 1/100:

Table[Limit[d[a,k],k->Infinity],{a,2,3,1/100}]

Wynik, sugerowany przez wykres zdaje się być potwierdzony przez powyższe dane. Stosując ręczną bisekcję, możemy z dowolną dokładnością ustalić położenie punktu granicznego:

Table[{N[a],Limit[d[a,k],k->Infinity]},{a,2.71,2.72,0.0001}]

Jeżeli ktoś nie potrafi zidentyfikować wartości granicznej, sugeruję użyć WolframAlpha lub inne narzędzie identyfikacji stałych, np: https://isc.carma.newcastle.edu.au/standardCalc

WolframAlpha["2.7183"]

Graniczną wartością okazuje się być liczba e:

Limit[d[E,k],k->Infinity]

Przykład 3: uporządkowanie ciągów ze względu na szybkość wzrastania wyrazów

99% zadań dotyczących granic w nieskończoności sprowadza się do odpowiedzi na pytanie co rośnie szybciej: licznik czy mianownik. W pierwszym wypadku odpowiedzią jest nieskończoność, w drugim zero. Wiedzę taką w tradycyjnym nauczaniu zdobywa się przez doświadczenie i dowody matematyczne. Nie jest łatwo stwierdzić co szybciej rośnie, np: n! czy (n^n) ? Studenci mają także problemy z prostszymi porównaniami, typu n^2 versus Exp[n], Log[n] versus Sqrt[n] itp itd. Bywa, że jest problem nawet z porównaniem szybkości wzrastania wielomianów!

Rozważmy siedem ciągów :

ciagi={Exp[n],n^n,n!,n^2,Exp[n^2],n^(n^n),Exp[E^n]}

Możemy z nich utworzyć (7^2) ułamków:

granice=Table[ciagi[[i]]/ciagi[[k]],{i,1,7},{k,1,7}]

TableForm[granice]

Obliczmy wszystkie granice :

Limit[granice,n->Infinity]//TableForm

Powyżej widzimy, że granice są równe 0, 1 lub ∞. Pozwala to ustalić, co szybciej rośnie. Wykorzystamy ten fakt do modyfikacji polecenia Sort, służącego do sortowania wyrażeń.

Sort[ciagi,(Limit[#1/#2,n->Infinity]<Limit[#2/#1,n->Infinity])&]

Powyższa lista pozwala na błyskawiczne ,,obliczanie’’ granic. Np: ponieważ n! rośnie wolniej od (n^n) granica n!/n^n musi wyjść zero:

Limit[n!/n^n,n->Infinity]

Z kolei Exp[n] rośnie wolniej niż n!, więc:

Limit[n!/Exp[n],n->Infinity]

Speak[n!]

Możemy w ten sposób łatwo ,,posortować’’ różne wyrażenia matematyczne:

Sort[{Exp[n],Sinh[n],Cosh[n],Tanh[n]},(Limit[#1/#2,n->Infinity]<Limit[#2/#1,n->Infinity])&]

Sort[{n^3,n^4,1,n,n^2},(Limit[#1/#2,n->Infinity]<Limit[#2/#1,n->Infinity])&]

Sort[{Log[n],Sqrt[n],((1/n)^(1/n)),n Log[n]},(Limit[#1/#2,n->Infinity]<Limit[#2/#1,n->Infinity])&]

DiscretePlot[{((1/n)^(1/n)),Log[n],(Sqrt[n])},{n,1,44},PlotStyle->{Red,Green,Blue}]

Przykład 4: ciągi zdefiniowane rekurencyjnie (arytmetyczny, geometryczny, Fibonacciego i inne)

Duża część interesujących ciągów jest zdefiniowana rekurencyjnie, czyli następne wyrazy obliczamy korzystając z poprzednich. Mathematica potrafi rozwiązywać rekurencje, czyli znaleźć postać jawną na postawie rekurencyjnej. W pozostałych przypadkach musimy korzystać z metod numerycznych, w tym programowania dynamicznego (funkcji pamiętających).

Szkolnego przykładu dostarcza ciąg arytmetyczny. Kolejny wyraz ciągu arytmetycznego jest wiekszy od poprzedniego o stałą wielkość, oznaczaną zwykle przez r. Aby określić wszystkie wyrazy ciągu musimy podać (oprócz r) pierwszy z nich. Jest to sytuacja prototypowa dla rozwiązywania rekurencji (a także równań różniczkowych zwyczajnych, np. II zasady dynamiki Newtona).

Zaczynamy od określenia pierwszego wyrazu:

arytm1[1]=a1

Każdy następny jest większy o r:

arytm1[k_]:=arytm1[k-1]+r

Powyższe informacje wystarczają do wygenerowania pierwszych kilkunastu wyrazów:

Table[arytm1[k],{k,1,10}]

Możemy obliczyć ich sumę :

Sum[arytm1[k],{k,1,10}]

Można obliczyć setny wyraz:

arytm1[100]

Ale nie 1000-ny:

arytm1[800]

Osiągnęliśmy limit wywołań rekurencyjnych, zdefiniowany pod nazwą $RecursionLimit. Jego wartość startowa to 256. 
Aby obliczyć dalsze wyrazy, musimy ją podnieść:

$RecursionLimit=2000

arytm1[1001]

To niestety nie pozwala na obliczenie n-tego wyrazu:

arytm1[n]

Aby poradzić sobie z tego typu problemem, najwygodniej jest znać wzór ogólny ciągu. Można go znaleźć poleceniem RSolve:

Clear[arytm]

RSolve[arytm[k]==arytm[k-1]+r,arytm[k],k]

Składnia polecenia RSolve jest następująca:

pierwszy argument: relacja wiążąca wyrazy ciągu (rekurencja) zapisana znakiem równości logicznej arytm[k]==arytm[k-1]
drugi argument: szukany wzór arytm[k]
trzeci argument: zmienna numerująca wyrazy ciągu k

W powyższym wyniku pojawia się stała dowolna C[1]. Aby nadać jej wartość, musimy dodać informację, np: że pierwszy wyraz oznaczamy a1.

UWAGA: opanowanie polecenia RSolve jest bardzo ważne, gdyż nie wymaga znajomosci matematyki wyższej, a jedo składnia i sposób posługiwania się jest identyczny jak DSolve, które to polecenie rozwiązuje równania różniczkowe.

RSolve[{arytm[k]==arytm[k-1]+r,arytm[1]==a1},arytm[k],k]

Otrzymaliśmy szkolny wzór na k-ty wyraz ciągu arytmetycznego:

arytm2[k_]:=a1-r+k r

Teraz możemy znaleźć wzór na sumę n wyrazów ciągu arytmetycznego:

Sum[arytm2[k],{k,1,n}]

Factor[%]

Jeżeli weźmiemy pod uwagę, że n-ty wyraz ciągu to a1+(n-1)*r:

Simplify[%,a1-r+n r==an]

Obliczanie wyrazów ciągu jest teraz łatwiejsze, i znacznie szybsze (widać to dobrze na ciągu Fibonacciego).

Analogicznie możemy zbadać ciąg geometryczny:

geom1=geom/.RSolve[{geom[k+1]==geom[k]*q,geom[1]==a1},geom,k][[1]]

Sum[a1 (q^(-1+k)),{k,1,n}]

Sum[a1 (q^(-1+k)),{k,1,Infinity}]

Table[geom1[k],{k,1,12}]

Ciąg Fibonaccciego jest funkcją w Mathematice (Fibonacci), ale stanowi dobry przykład.

Quit[]

Ciąg jest zdefiniowany bardzo prosto: wyraz ciągu jest sumą dwóch poprzedzających:

fib1[1]=1

fib1[2]=1

fib1[n_]:=fib1[n-1]+fib1[n-2]

fib1[3]

fib1[13]

Table[fib1[k],{k,1,12}]

RSolve bez problemu radzi sobie z rekurencją:

fib[n]/.RSolve[{fib[n+2]==fib[n+1]+fib[n],fib[1]==1,fib[2]==1},fib[n],n][[1]]

Aby zrozumieć, co kryje się za symbolem Fibonacci użyjemy FunctionExpand:

FunctionExpand[%]

Dostajemy dziwny na pierwszy rzut oka wzór, szczególnie pierwiastki wydają się nie pasować do ciągu o wartościach całkowitych. We wzorze występuje tzw. złoty podział GoldenRatio:

GoldenRatio//FunctionExpand

Wartości uzyskane z powyższego wzoru są całkowite:

((((((1/2) (1+(Sqrt[5]))))^n)-((((2/(1+(Sqrt[5])))))^n) Cos[n π])/(Sqrt[5]))/.n->3

Expand[%]

Simplify[%]

Pojawienie się pierwiastków, w tym obiektów Root nie powinno dziwić przy rozwiązywaniu rekurencji, szczególnie wyższych niż 2 rzędów:

RSolve[a[n+3]==a[n]-2a[n+2]+a[n+1],a,n]

Pojawiają się także często funkcje Fibonacci i blisko powiązana LucasL. Ich uproszczenie może wymagać trochę wysiłku i mocy obliczeniowej:

RSolve[a[n+2]==a[n]+n+a[n+1],a,n]

a[k]/.%[[1]]

FullSimplify[%]

Refine[%,k∈Integers]

Simplify[%]

Czasem możemy natrafić na obiekty DifferenceRoot. Upraszczając, są one tym dla równań rekurencyjnych, co obiekty Root dla pierwiastków wielomianów. Kilka standardowych rozwiązań, jak Fibonacci, zostało nazwanych. Pozostałe są bezimienne, ale możemy (lub raczej: będziemy mogli w przyszłości) nimi operować w Mathematice:

RSolve[a[n+2]==a[n]*n+a[n+1],a,n]

Bliponacci=a/.%[[1]];

Bliponacci[n]n+Bliponacci[n+1]

FullSimplify[%]

FunctionExpand[%]

?DifferenceRoot

Na koniec parę uwag na temat szybkości obliczeń. Ta uwaga jest konieczna, gdyż często studenci poddają w wątpliwość sens wykonywania tego typu rachunków. Porównajmy czas niezbędny na obliczenie 32 wyrazu ciągu Fibonacciego:

fib1[32]//Timing

Clear[fib2]

fib2[n_]:=((((((1/2) (1+(Sqrt[5]))))^n)-((((2/(1+(Sqrt[5])))))^n) Cos[n π])/(Sqrt[5]))

Czas potrzebny na użycie gotowego wzoru jest niemierzalnie mały:

fib2[32]//Timing

Nawet dodanie czasu upraszczania ciągle daje znaczące przyspieszenie:

fib2[32]//Simplify//Timing

Wbudowana funkcja jest najszybsza:

Fibonacci[32]//Timing

Szybkość obliczania rekurencji można poprawić używając funkcji pamiętających, inaczej programowania dynamicznego. Mathematica idealnie nadaje się do jego zastosowania:

Quit[]

fib3[1]=1

fib3[2]=1

fib3[n_]:=fib3[n]=fib3[n-1]+fib3[n-2]

fib3[32]//Timing

Table[fib2[k]//N,{k,1,10^5}]//Timing//First

Table[fib3[k]//N,{k,1,10^5}]//Timing//First

Table[Fibonacci[k]//N,{k,1,10^5}]//Timing//First