<jupyter_start><jupyter_text>Simetria: uma ideia centralSe uma função é 2 vezes derivável, temos que$$ f(x+h) = f(x) + h f'(x) + \frac{h^2}{2} f''(x) + o(h^2) $$onde $o(h^2)$ é um termo **muito pequeno** quando comparado com $h^2$.Um outro quociente fundamental é$$ f'(x) = \lim _ {h \to 0} \frac{f(x+h) - f(x-h)}{2h}. $$Ao usar as fórmulas de Taylor acima até a ordem 2 para calcular as aproximações,vemos que os termos de ordem *par* se anulam:$$ \frac{f(x+h) - f(x-h)}{2h}   = \frac{\big(f(x) + h f'(x) + \frac{h^2}{2} f''(x) + o(h^2)\big)   - \big( f(x) - h f'(x) + \frac{h^2}{2} f''(x) + o(h^2) \big)}{2h} = o(h). $$Este método é bem melhor do que o anterior quando a função é duas vezes derivável:agora, o erro é **muito menor** do que o passo, em vez de ser "mais ou menos do mesmo tamanho".  Uma nova estimativa do erroPara termos uma melhor ideia de quanto é $o(h)$, vamos supor que a função é três vezes derivável.Agora, podemos trocar $o(h^2)$ por $h^3 \frac{f'''(\xi_\pm)}{6}$ (um $\xi$ para cada valor de $x\pm h$),e assim o erro será $ \frac{h^2}{6} [f'''(\xi_+) + f'''(\xi_-)]$.Vejamos o que acontece para o seno:<jupyter_code>def df_sym(f,x,h=1e-5):
    return (f(x+h) - f(x-h))/(2*h)
h = 0.2e-2
xs = np.arange(0,7,0.05)
dfx = np.cos(xs)
dfx_approx_3 = df_sym(np.sin,xs,h)
_, [ax1, ax2] = plt.subplots(ncols=2, figsize=(15,4))

ax1.set_title('Erro de aproximação')
ax1.plot(xs, dfx - dfx_approx_3)

ax2.set_title('Erro na estimativa do erro')
ax2.plot(xs, (dfx_approx_3 - dfx) - (-(h ** 2) * np.cos(xs)/6))
plt.show()<jupyter_output><empty_output><jupyter_text>Exercício:Até que valor podemos "reduzir" o erro de aproximação, diminuindo $h$?  Exercício: derivadas numéricas de funções numéricasMude- a tolerância da bisseção subjacente à função `f`,- o passo dado pela derivada numéricae veja como o gráfico de `f` e de sua derivada muda.<jupyter_code># A função `f` do notebook anterior, com tolerância variável
from rootfinding import bissection

def f(t, tol):
    def g(x):
        return t*np.cos(x) - x
    return bissection(g,-np.pi/2,np.pi/2, tol)

def fvect(v, tol):
    return np.array([f(vi, tol) for vi in v])

v = np.arange(-3,3,0.05)
# A derivada simples, para comparar também
def df(f, x, h=1e-5):
    return (f(x+h) - f(x))/h
def do(logtol, logh, logh_sym):
    _, [ax1, ax2, ax3] = plt.subplots(ncols=3, figsize=(17,4))
    
    tol = 2**(-logtol)
    h   = 2**(-logh)
    h_s = 2**(-logh_sym)
    myf = lambda v: fvect(v, tol)
    ax1.plot(v, myf(v))
    ax1.set_title('f with precision 2**-{}'.format(logtol))

    ax2.plot(v, df(myf, v, h))
    ax2.set_title('df with step 2**-{}'.format(logh))

    ax3.plot(v, df_sym(myf, v, h_s))
    ax3.set_title('df_sym with step 2**-{}'.format(logh_sym))
    plt.show()
### Interactive solution
from ipywidgets import FloatSlider, IntSlider
from ipywidgets import interact, interactive
logtols = IntSlider(min=5, value=10, max=50)
loghs   = IntSlider(min=0, value=5, max=30)
loghs_sym   = IntSlider(min=0, value=5, max=30)

interactive(do, logtol=logtols, logh=loghs, logh_sym=loghs_sym)<jupyter_output><empty_output><jupyter_text>Será que você consegue calcular a derivada de $f$ de forma algebrica?Na verdade, basta ser "suficientemente preciso" para fazer um gráfico do **erro** de cada um dos métodos.<jupyter_code>def do(logtol, logh, logh_sym):
    _, [[ax1, ax2, ax3], [ax4, ax5, ax6]] = plt.subplots(nrows=2, ncols=3, figsize=(17,9))
    
    tol = 2**(-logtol)
    h   = 2**(-logh)
    h_s = 2**(-logh_sym)
    myf = lambda v: fvect(v, tol)

    fv = myf(v)
    ax1.plot(v, fv)
    ax1.set_title('f with precision 2**-{}'.format(logtol))

    dfv = df(myf, v, h)
    ax2.plot(v, dfv)
    ax2.set_title('df with step 2**-{}'.format(logh))

    dfv_s = df_sym(myf, v, h_s)
    ax3.plot(v, dfv_s)
    ax3.set_title('df_sym with step 2**-{}'.format(logh_sym))

    # Calcule aqui a derivada de verdade!
    ### Resposta aqui

    ax5.plot(v, dfv -  dfv_true)
    ax6.plot(v, dfv_s - dfv_true)
    plt.show()

interactive(do, logtol=logtols, logh=loghs, logh_sym=loghs_sym)<jupyter_output><empty_output><jupyter_text>Exercício: Programação funcionalEscreva uma função "gerar_derivada" que, dada uma função `f` e um passo `h`,retorna uma função `df(x)` que é uma derivada aproximada de `f` em `x`.<jupyter_code>def gerar_derivada(f, h=1e-5):
    ### Resposta aqui

dsin = gerar_derivada(np.sin)
plt.plot(xs, dsin(xs))
plt.show()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.17263917061077305, 'fraction_numerical': 0.026143790849673203, 'mean_word_length': 4.136574074074074, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '4', 'dir': 'jupyter-structured-clean-dedup', 'id': '614560', 'license': 'MIT', 'n_tokens_mistral': 2024, 'n_tokens_neox': 1954, 'n_words': 492, 'path': 'comp-cientifica-I-2018-2/semana-6/raw_files/Semana6-Parte2-DerivadasCentrais.ipynb', 'repo_name': 'mirandagil/university-courses', 'size': '4437'}