CB2325NumericaG5 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,410 @@
1
+ """
2
+ Módulo para cálculo de polinômios interpoladores.
3
+
4
+ Este módulo fornece classes para construir e avaliar polinômios interpoladores a partir de
5
+ listas de pontos no plano. Inclui implementações para:
6
+ - interpolação de Lagrange (PoliInterp),
7
+ - interpolação linear por partes (InterpLinear),
8
+ - interpolação de Hermite usando valores e derivadas (PoliHermite).
9
+
10
+ As classes validam entradas, constroem expressões simbólicas (SymPy) e expõem um método
11
+ `grafico(precisao: int = 100) -> None` para esboçar o polinômio ou os segmentos.
12
+ """
13
+
14
+
15
+ from sympy import symbols, simplify, lambdify, Number, latex, Symbol
16
+ from numpy import linspace, full_like
17
+ from matplotlib.pyplot import show, subplots
18
+ from typing import Union
19
+ import numbers
20
+
21
+
22
+ class Interpolacao:
23
+ """
24
+ Classe base para verificar e armazenar os dados de entrada comuns às interpolações.
25
+
26
+ Args:
27
+ dominio (list): Lista de pontos do domínio (valores numéricos, distintos).
28
+ imagem (list): Lista de valores da função nos pontos do domínio (numéricos).
29
+ imagem_derivada (list | None): Lista com os valores da derivada nos pontos do domínio,
30
+ ou None quando não aplicável. Valor padrão: None.
31
+
32
+ Raises:
33
+ TypeError: Se `dominio`, `imagem` (ou `imagem_derivada`, quando fornecida) não for do tipo list,
34
+ ou se algum elemento das listas não for um número real.
35
+ ValueError: Se `dominio` e `imagem` tiverem comprimentos diferentes, se `imagem_derivada`
36
+ tiver comprimento diferente, se `dominio` contiver valores repetidos ou
37
+ se `dominio` tiver menos de 2 pontos.
38
+
39
+ Attributes:
40
+ x (sympy.Symbol): Símbolo interno usado para construir expressões simbólicas.
41
+ dominio (list): Domínio validado (lista de números reais).
42
+ imagem (list): Valores da função no domínio (lista de números reais).
43
+ imagem_derivada (list | None): Valores das derivadas no domínio (lista de números reais) ou None.
44
+ """
45
+
46
+
47
+ def __init__(self, dominio:list, imagem:list, imagem_derivada:Union[list, None] = None):
48
+ # Garantimos que o domínio e a imagem são listas de pontos
49
+ if not isinstance(dominio, list) or not isinstance(imagem, list):
50
+ raise TypeError('`dominio` e `imagem` devem ser do tipo list')
51
+
52
+ # Garantimos que o domínio e a imagem possuem a mesma quantidade de pontos
53
+ if len(dominio) != len(imagem):
54
+ raise ValueError('`dominio` e `imagem` devem ter a mesma quantidade de pontos')
55
+
56
+ # Garantimos que o domínio não possui pontos repetidos
57
+ if len(set(dominio)) != len(dominio):
58
+ raise ValueError('`dominio` não pode ter valores repetidos')
59
+
60
+ # Garantimos que o domínio possui mais de 2 pontos
61
+ if len(dominio) < 2:
62
+ raise ValueError('`dominio` deve possuir mais de 2 pontos')
63
+
64
+ # Garantimos que o domínio é uma lista de números
65
+ for i in dominio:
66
+ if not isinstance(i, numbers.Real):
67
+ raise TypeError('`dominio` deve ser uma lista de números')
68
+
69
+ # Garantimos que a imagem é uma lista de números
70
+ for i in imagem:
71
+ if not isinstance(i, numbers.Real):
72
+ raise TypeError('`imagem` deve ser uma lista de números')
73
+
74
+ # Cria as variáveis internas
75
+ self.x = symbols('x')
76
+ self.dominio = dominio
77
+ self.imagem = imagem
78
+
79
+ if imagem_derivada is not None:
80
+ # Garantimos que a imagem_derivada é uma lista de pontos
81
+ if not isinstance(imagem_derivada, list):
82
+ raise TypeError('`imagem_derivada` deve ser do tipo list')
83
+
84
+ # Garantimos que o domínio e a imagem_derivada possuem a mesma quantidade de pontos
85
+ if len(dominio) != len(imagem_derivada):
86
+ raise ValueError('`dominio` e `imagem` devem ter a mesma quantidade de pontos')
87
+
88
+ # Garantimos que a imagem_derivada é uma lista de números
89
+ for i in imagem_derivada:
90
+ if not isinstance(i, numbers.Real):
91
+ raise TypeError('`imagem_derivada` deve ser uma lista de números')
92
+
93
+ # Cria uma variável interna
94
+ self.imagem_derivada = imagem_derivada
95
+
96
+ def __repr__(self):
97
+ raise NotImplementedError
98
+
99
+ def __call__(self, p:Union[int, float, Symbol]):
100
+ raise NotImplementedError
101
+
102
+ def grafico(self, precisao:int = 100) -> None:
103
+ """
104
+ Esboça o gráfico da classe Interpolacao
105
+
106
+ Args:
107
+ precisao (int, opcional): número de pontos do polinomio a serem calculados. Padroniza em 100.
108
+
109
+ Raises:
110
+ TypeError: se precisão não for do tipo int
111
+ """
112
+
113
+ #Garante que precisao seja do tipo int
114
+ if not isinstance(precisao, int):
115
+ raise TypeError("precisao deve ser do tipo int")
116
+
117
+ #Preparação
118
+ x_simb = symbols('x')
119
+ _, ax = subplots()
120
+ ax.set_aspect("equal")
121
+
122
+ #Determinação dos limites do gráfico
123
+ xmin, xmax, ymin, ymax = min(self.dominio), max(self.dominio), min(self.imagem), max(self.imagem)
124
+ mini, maxi = min(xmin, ymin), max(xmax, ymax)
125
+ ax.set_xlim(mini-1, maxi+1)
126
+ ax.set_ylim(mini-1, maxi+1)
127
+
128
+ #Conversão do polinomio
129
+ y_vals = linspace(xmin, xmax, precisao)
130
+ y_lamb = lambdify(x_simb, self.pol, "numpy")
131
+ y_func = y_lamb(y_vals)
132
+ if isinstance(y_func, (int, float)):
133
+ y_func = full_like(y_vals, y_func)
134
+
135
+ #Esboço da curva e dos pontos
136
+ ax.plot(y_vals, y_func)
137
+ for i in range(len(self.dominio)):
138
+ x_ponto, y_ponto = self.dominio[i], self.imagem[i]
139
+ ax.plot(x_ponto, y_ponto, "o")
140
+
141
+ #Exibição
142
+ ax.set_xlabel("x")
143
+ ax.set_ylabel("y")
144
+ ax.grid(True)
145
+ show()
146
+
147
+
148
+ class PoliInterp(Interpolacao):
149
+ """
150
+ Polinômio interpolador baseado no método de Lagrange.
151
+
152
+ Constrói o polinômio interpolador de Lagrange simplificado a partir de
153
+ listas `dominio` e `imagem` fornecidas na construção da instância.
154
+
155
+ Args:
156
+ dominio (list): Lista de pontos do domínio (valores numéricos distintos).
157
+ imagem (list): Lista de valores da função nos pontos do domínio (numéricos).
158
+
159
+ Raises:
160
+ ValueError: Se o ponto passado para `__call__` não for um número real nem um
161
+ `sympy.Symbol`, ou se o ponto numérico estiver fora do intervalo [min(dominio), max(dominio)]
162
+ (extrapolação não permitida).
163
+
164
+ Attributes:
165
+ pol (sympy.Expr): Polinômio interpolador simplificado (expressão simbólica).
166
+ x (sympy.Symbol): Símbolo interno herdado de Interpolacao.
167
+
168
+ Usage:
169
+ p = PoliInterp(dominio, imagem)
170
+ str(p) # representação do polinômio
171
+ p(sym) # retorna LaTeX do polinômio se sym for sympy.Symbol
172
+ p(valor_real) # retorna int/float se o valor for numérico dentro do domínio
173
+ """
174
+
175
+ def __init__(self, dominio:list, imagem:list):
176
+ super().__init__(dominio, imagem)
177
+
178
+ # método de Lagrange
179
+ soma = 0
180
+ for i in range(len(self.dominio)):
181
+ prod = 1
182
+ for e in range(len(self.dominio)):
183
+ if e != i:
184
+ prod *= (self.x - self.dominio[e]) / (self.dominio[i] - self.dominio[e])
185
+ soma += self.imagem[i] * prod
186
+
187
+ self.pol = simplify(soma) # Polinômio interpolador simplificado
188
+
189
+ def __repr__(self):
190
+ return f'{self.pol}'
191
+
192
+ def __call__(self, p:Union[int, float, Symbol]):
193
+ if not isinstance(p, numbers.Real) and not isinstance(p, Symbol):
194
+ raise ValueError('O ponto deve ser um número real ou uma variável')
195
+
196
+ # Retorna a representação do polinômio no ponto p (variável) em LaTeX
197
+ if isinstance(p, Symbol):
198
+ return latex(self.pol.subs(self.x, p))
199
+
200
+ # Previne extrapolação
201
+ if p < min(self.dominio) or p > max(self.dominio):
202
+ raise ValueError('Valores fora do intervalo do domínio não são bem aproximados')
203
+
204
+ temp = self.pol.subs(self.x, p)
205
+ if isinstance(temp, Number):
206
+ if temp.is_integer:
207
+ return int(temp)
208
+ return float(temp)
209
+ return None
210
+
211
+
212
+ class InterpLinear(Interpolacao):
213
+ """
214
+ Interpolação linear por segmentos (retas entre pares consecutivos de pontos).
215
+
216
+ Calcula e armazena retas que ligam cada par consecutivo de pontos do domínio,
217
+ representadas como expressões simbólicas simplificadas em um dicionário.
218
+
219
+ Args:
220
+ dominio (list): Lista de pontos do domínio (valores numéricos, distintos).
221
+ imagem (list): Lista de valores da função nos pontos do domínio (numéricos).
222
+
223
+ Raises:
224
+ ValueError: Se o ponto passado para `__call__` não for um número real,
225
+ ou se o ponto estiver fora do intervalo do domínio (extrapolação não permitida).
226
+
227
+ Attributes:
228
+ pares_ord (list[tuple]): Lista de pares ordenados (xi, yi) ordenados por xi.
229
+ pol (dict): Dicionário mapeando o intervalo (xi, xi+1) para a expressão simbólica da reta entre esses pontos.
230
+
231
+ Usage:
232
+ l = InterpLinear(dominio, imagem)
233
+ l(valor_real) # retorna int/float se o valor estiver no domínio
234
+ """
235
+
236
+ def __init__(self, dominio:list, imagem:list):
237
+ super().__init__(dominio, imagem)
238
+
239
+ self.pares_ord = []
240
+ for i, e in zip(dominio, imagem):
241
+ self.pares_ord.append((i, e))
242
+ self.pares_ord = sorted(self.pares_ord)
243
+
244
+ # Criamos um dicionário para dividir as retas que ligam os pontos 2 a 2
245
+ self.pol = {}
246
+
247
+ # Calcula cada reta
248
+ for i in range(len(self.pares_ord) - 1):
249
+ reta = self.pares_ord[i][1] + (self.x - self.pares_ord[i][0]) * (
250
+ (self.pares_ord[i + 1][1] - self.pares_ord[i][1]) / (self.pares_ord[i + 1][0] - self.pares_ord[i][0]))
251
+
252
+ # Adiciona a reta simplificada no dicionário: (xi, xi+1): reta
253
+ self.pol[(self.pares_ord[i][0], self.pares_ord[i + 1][0])] = simplify(reta)
254
+
255
+ def __repr__(self):
256
+ return f'{self.pol}'
257
+
258
+ def _eval(self, pos:tuple, t:Union[int, float]):
259
+ temp = self.pol[pos].subs(self.x, t)
260
+ if isinstance(temp, Number):
261
+ if temp.is_integer:
262
+ return int(temp)
263
+ return float(temp)
264
+ return None
265
+
266
+ def __call__(self, p:Union[int, float]):
267
+ if not isinstance(p, numbers.Real):
268
+ raise ValueError('O ponto deve ser um número real')
269
+
270
+ temp = [i[0] for i in self.pares_ord]
271
+
272
+ # Extrapolação
273
+ if p>temp[-1] or p<temp[0]:
274
+ raise ValueError('Valores fora do intervalo do domínio não são bem aproximados')
275
+
276
+ for i in range(len(temp) - 1):
277
+ if temp[i] <= p <= temp[i + 1]:
278
+ return self._eval((temp[i], temp[i + 1]), p)
279
+ return None
280
+
281
+ def grafico(self, precisao:int = 100) -> None:
282
+ """
283
+ Esboça o gráfico da classe InterpLinear
284
+
285
+ Argumentos:
286
+ precisao (int): número de pontos do polinomio a serem calculados. Padroniza em 100.
287
+
288
+ Raises:
289
+ TypeError: se precisão não for do tipo int
290
+ """
291
+
292
+ #Garante que precisao seja do tipo int
293
+ if not isinstance(precisao, int):
294
+ raise TypeError("precisao deve ser do tipo int")
295
+
296
+ #Preparação
297
+ x_simb = symbols('x')
298
+ _, ax = subplots()
299
+ ax.set_aspect("equal")
300
+ precisao = precisao//(len(self.dominio) - 1)
301
+
302
+ #Determinação dos limites do gráfico
303
+ xmin, xmax, ymin, ymax = self.pares_ord[0][0], self.pares_ord[len(self.pares_ord) - 1][0], min(self.imagem), max(self.imagem)
304
+ mini, maxi = min(xmin, ymin), max(xmax, ymax)
305
+ ax.set_xlim(mini-1, maxi+1)
306
+ ax.set_ylim(mini-1, maxi+1)
307
+
308
+ #Conversão do polinomio e esboço das retas
309
+ for i in range(1,len(self.pares_ord)):
310
+ y_expr = self.pares_ord[i-1][1] + (
311
+ x_simb - self.pares_ord[i-1][0]) * (
312
+ self.pares_ord[i][1] - self.pares_ord[i-1][1]) / (
313
+ self.pares_ord[i][0] - self.pares_ord[i-1][0])
314
+ y_vals = linspace(self.pares_ord[i-1][0], self.pares_ord[i][0], precisao)
315
+ y_lamb = lambdify(x_simb, y_expr, "numpy")
316
+ y_func = y_lamb(y_vals)
317
+ if isinstance(y_func, (int, float)):
318
+ y_func = full_like(y_vals, y_func)
319
+ ax.plot(y_vals, y_func)
320
+
321
+ #Esboço dos pontos
322
+ for i in range(len(self.dominio)):
323
+ x_ponto, y_ponto = self.dominio[i], self.imagem[i]
324
+ ax.plot(x_ponto, y_ponto, "o")
325
+
326
+ #Exibição
327
+ ax.set_xlabel("x")
328
+ ax.set_ylabel("y")
329
+ ax.grid(True)
330
+ show()
331
+
332
+
333
+ class PoliHermite(Interpolacao):
334
+ """
335
+ Interpolador polinomial pelo método de Hermite (uso de valores e derivadas).
336
+
337
+ Constrói o polinômio de Hermite a partir de `dominio`, `imagem` e `imagem_derivada`,
338
+ usando funções auxiliares que computam os polinômios base de Hermite Hx_j e Hy_j.
339
+
340
+ Args:
341
+ dominio (list): Lista de pontos do domínio (valores numéricos, distintos).
342
+ imagem (list): Lista de valores da função nos pontos do domínio (numéricos).
343
+ imagem_derivada (list): Lista de valores das derivadas nos pontos do domínio (numéricos).
344
+
345
+ Raises:
346
+ ValueError: Se o ponto passado para `__call__` não for um número real nem um `sympy.Symbol`,
347
+ ou se o ponto numérico estiver fora do intervalo [min(dominio), max(dominio)] (extrapolação não permitida).
348
+
349
+ Attributes:
350
+ coef_lagrange (dict): Dicionário em que cada entrada j contém (L_j(x), L_j'(x)) simplificados.
351
+ pol (sympy.Expr): Polinômio de Hermite resultante e simplificado.
352
+
353
+ Usage:
354
+ h = PoliHermite(dominio, imagem, imagem_derivada)
355
+ str(h) # representação do polinômio
356
+ h(sym) # retorna LaTeX do polinômio se sym for sympy.Symbol
357
+ h(valor_real) # retorna int/float se o valor estiver no domínio
358
+ """
359
+
360
+ def __init__(self, dominio:list, imagem:list, imagem_derivada:list):
361
+ super().__init__(dominio, imagem, imagem_derivada)
362
+
363
+ # Dicionário com os coeficientes de Lagrange
364
+ self.coef_lagrange = {}
365
+ for j in range(len(imagem)):
366
+ prod = 1
367
+ for i in range(len(dominio)):
368
+ if j != i:
369
+ prod *= (self.x - dominio[i]) / (dominio[j] - dominio[i])
370
+ self.coef_lagrange[j] = (simplify(prod), simplify(prod.diff(self.x)))
371
+
372
+ # Encontra o polinômio de hermite
373
+ self.pol = self._hermite()
374
+
375
+ def __repr__(self):
376
+ return f'{self.pol}'
377
+
378
+ def _hx_j(self, j:int):
379
+ soma = (1-2*(self.x - self.dominio[j])*(self.coef_lagrange[j][1].subs(self.x, self.dominio[j])))*(self.coef_lagrange[j][0])**2
380
+ return simplify(soma)
381
+
382
+ def _hy_j(self, j:int):
383
+ soma = (self.x-self.dominio[j])*(self.coef_lagrange[j][0])**2
384
+ return simplify(soma)
385
+
386
+ def _hermite(self):
387
+ pol = 0
388
+ for j in range(len(self.dominio)):
389
+ pol += self.imagem[j]*self._hx_j(j) + self.imagem_derivada[j]*self._hy_j(j)
390
+ return simplify(pol)
391
+
392
+ def __call__(self, p:Union[int, float, Symbol]):
393
+ if not isinstance(p, numbers.Real) and not isinstance(p, Symbol):
394
+ raise ValueError('O ponto deve ser um número ou uma variável')
395
+
396
+ # Retorna a representação do polinômio no ponto p (variável) em LaTeX
397
+ if isinstance(p, Symbol):
398
+ return latex(self.pol.subs(self.x, p))
399
+
400
+ # Evita extrapolação
401
+ if min(self.dominio) <= p <= max(self.dominio):
402
+ temp = self.pol.subs(self.x, p)
403
+ if isinstance(temp, Number):
404
+ if temp.is_integer:
405
+ return int(temp)
406
+ return float(temp)
407
+ return None
408
+
409
+ else:
410
+ raise ValueError('Valores fora do intervalo do domínio não são bem aproximados')
@@ -0,0 +1,284 @@
1
+ import sympy as sp
2
+ from typing import Callable
3
+ import numpy as np
4
+ import matplotlib.pyplot as plt
5
+
6
+ def bissecao(
7
+ f: Callable[[float], float],
8
+ a: float,
9
+ b: float,
10
+ tol: float = 1e-6,
11
+ max_iter: int = 100,
12
+ plot: bool = True
13
+ ) -> float:
14
+ """
15
+ Método da bisseção para encontrar uma raiz de uma função contínua em um intervalo [a, b].
16
+
17
+ O método da bisseção assume que f(a) e f(b) têm sinais opostos e procede dividindo o
18
+ intervalo ao meio repetidamente até que a função no ponto médio seja menor que a tolerância
19
+ desejada ou até atingir o número máximo de iterações.
20
+
21
+ Args:
22
+ f (Callable[[float], float]): Função contínua que recebe um número real e retorna um número real.
23
+ a (float): Ponto inicial do intervalo à esquerda.
24
+ b (float): Ponto inicial do intervalo à direita.
25
+ tol (float, optional): Tolerância para a convergência (critério em |f(c)|). Por padrão 1e-6.
26
+ max_iter (int, optional): Número máximo de iterações a serem executadas. Por padrão 100.
27
+ plot (bool, optional): Se True, exibe o gráfico das iterações. Padrão: True.
28
+
29
+ Raises:
30
+ ValueError: Se f(a) e f(b) não tiverem sinais opostos.
31
+
32
+ Returns:
33
+ float: Uma aproximação da raiz encontrada no intervalo [a, b].
34
+ """
35
+
36
+ if f(a) * f(b) > 0:
37
+ raise ValueError("f(a) e f(b) devem ter sinais opostos para o método da bisseção.")
38
+
39
+ x = np.linspace(a - abs(b - a) * 0.3, b + abs(b - a) * 0.3, 500)
40
+ y = np.array([f(xi) for xi in x])
41
+
42
+ plt.figure(figsize=(10, 6))
43
+ plt.plot(x, y, linewidth=2, label='f(x)')
44
+ plt.axhline(0, color='black', linestyle='--', linewidth=1)
45
+ plt.title('Método da Bisseção')
46
+ plt.xlabel('x')
47
+ plt.ylabel('f(x)')
48
+ plt.grid(True, linestyle=':', linewidth=0.6)
49
+ plt.legend()
50
+
51
+ for i in range(max_iter):
52
+ c = (a + b) / 2
53
+
54
+ plt.axvline(a, color='red', linestyle=':', alpha=0.5)
55
+ plt.axvline(b, color='green', linestyle=':', alpha=0.5)
56
+ plt.axvline(c, color='orange', linestyle='--', alpha=0.8)
57
+
58
+ # pontos
59
+ plt.scatter([a, b, c], [f(a), f(b), f(c)],
60
+ color=['red', 'green', 'orange'], s=50)
61
+
62
+ # anotação do ponto médio
63
+ plt.text(c, f(c), f"c{i+1}", fontsize=8, color='orange', ha='left', va='bottom')
64
+
65
+ if abs(f(c)) < tol:
66
+ print(f"Convergência atingida na iteração {i+1}: c = {c:.6f}")
67
+ break
68
+
69
+ if f(a) * f(c) < 0:
70
+ b = c
71
+ else:
72
+ a = c
73
+
74
+ plt.tight_layout()
75
+ if plot:
76
+ plt.show()
77
+ else:
78
+ plt.close()
79
+
80
+ return c
81
+
82
+ def newton(
83
+ f: Callable[[sp.Symbol | float], sp.Expr | float],
84
+ x0: float,
85
+ tol: float = 1e-6,
86
+ max_iter: int = 100,
87
+ plot: bool = True
88
+ ) -> float:
89
+ """
90
+ Método de Newton (Newton-Raphson) para encontrar uma raiz de uma função utilizando
91
+ derivadas simbólicas via SymPy.
92
+
93
+ A função `f` deve ser tal que `f(x)` produza uma expressão manipulável pelo SymPy
94
+ quando `x` for um `sympy.Symbol`, ou simplesmente uma função que possa ser avaliada
95
+ numericamente quando lambdificada.
96
+
97
+ Usa o SymPy para calcular a derivada automaticamente e itera até que |f(x_k)| < tol
98
+ ou até atingir o número máximo de iterações.
99
+
100
+ Args:
101
+ f (Callable[[sp.Symbol | float], sp.Expr | float]): Função que define a expressão de interesse. Deve aceitar um símbolo
102
+ SymPy ou ser compatível com `sympify`/`lambdify`.
103
+ x0 (float): Chute inicial para o método de Newton.
104
+ tol (float, optional): Tolerância para o critério de parada em |f(x_k)|. Por padrão 1e-6.
105
+ max_iter (int, optional): Número máximo de iterações a serem executadas. Por padrão 100.
106
+ plot (bool, optional): Se True, exibe o gráfico das tangentes sucessivas. Padrão: True.
107
+
108
+ Raises:
109
+ ZeroDivisionError: Se a derivada num ponto for zero (divisão por zero no método).
110
+
111
+ Returns:
112
+ float: Aproximação da raiz obtida pelo método de Newton.
113
+ """
114
+
115
+ x = sp.Symbol('x')
116
+ expr = sp.sympify(f(x))
117
+ dexpr = sp.diff(expr, x)
118
+ f_num = sp.lambdify(x, expr, 'numpy')
119
+ df_num = sp.lambdify(x, dexpr, 'numpy')
120
+
121
+ xk = x0
122
+ history = [x0]
123
+
124
+ for _ in range(max_iter):
125
+ fx = f_num(xk)
126
+ dfx = df_num(xk)
127
+
128
+ if abs(fx) < tol:
129
+ break
130
+ if dfx == 0:
131
+ raise ZeroDivisionError("Derivada nula, método de Newton falhou.")
132
+
133
+ xk = xk - fx / dfx
134
+ history.append(xk)
135
+
136
+ if plot:
137
+ x_vals = np.linspace(min(history) - 2, max(history) + 2, 800)
138
+ y_vals = f_num(x_vals)
139
+
140
+ plt.figure(figsize=(9, 6))
141
+ plt.plot(x_vals, y_vals, 'b', linewidth=1.8, label='f(x)')
142
+ plt.axhline(0, color='black', linestyle='--', linewidth=1)
143
+
144
+ for i in range(len(history) - 1):
145
+ x0 = history[i]
146
+ f0 = f_num(x0)
147
+ df0 = df_num(x0)
148
+ x1 = history[i + 1]
149
+
150
+ xs = np.linspace(x0 - 1.5, x0 + 1.5, 50)
151
+ ys = f0 + df0 * (xs - x0)
152
+ plt.plot(xs, ys, 'orange', linestyle='--', alpha=0.8)
153
+ plt.scatter(x0, f0, color='red')
154
+ plt.scatter(x1, 0, color='purple', marker='x', s=80)
155
+ plt.text(x1, 0, f"x{i+1}", color='purple', fontsize=9, va='bottom')
156
+
157
+ plt.title('Método de Newton-Raphson – Tangentes sucessivas')
158
+ plt.xlabel('x')
159
+ plt.ylabel('f(x)')
160
+ plt.legend()
161
+ plt.grid(True, linestyle=':', alpha=0.5)
162
+ plt.tight_layout()
163
+ plt.show()
164
+
165
+ return xk
166
+
167
+ def secante(
168
+ f: Callable[[float], float],
169
+ a: float,
170
+ b: float,
171
+ tol: float = 1e-6,
172
+ max_iter: int = 100,
173
+ plot: bool = True
174
+ ) -> float:
175
+ """
176
+ Método da secante para encontrar uma raiz de uma função.
177
+
178
+ O método da secante é um método iterativo que aproxima a derivada por uma diferença
179
+ finita usando dois pontos consecutivos. Requer dois chutes iniciais a e b.
180
+
181
+ Args:
182
+ f (Callable[[float], float]): Função que recebe e retorna números reais.
183
+ a (float): Primeiro chute inicial.
184
+ b (float): Segundo chute inicial.
185
+ tol (float, optional): Tolerância para o critério de parada em |f(c)|. Por padrão 1e-6.
186
+ max_iter (int, optional): Número máximo de iterações a serem executadas. Por padrão 100.
187
+ plot (bool, optional): Se True, exibe o gráfico das iterações. Padrão: True.
188
+
189
+ Raises:
190
+ ZeroDivisionError: Se ocorrer divisão por zero ao calcular o próximo iterando.
191
+
192
+ Returns:
193
+ float: Aproximação da raiz obtida pelo método da secante.
194
+ """
195
+
196
+ fa, fb = f(a), f(b)
197
+ history = [(a, fa), (b, fb)]
198
+
199
+ for _ in range(max_iter):
200
+ if abs(fb - fa) < 1e-15:
201
+ raise ZeroDivisionError("Divisão por zero no método da secante.")
202
+
203
+ c = b - fb * (b - a) / (fb - fa)
204
+
205
+ if abs(f(c)) < tol:
206
+ history.append((c, f(c)))
207
+ break
208
+
209
+ a, b = b, c
210
+ fa, fb = fb, f(c)
211
+ history.append((b, fb))
212
+
213
+ if plot:
214
+ x_vals = np.linspace(min(a, b) - 2, max(a, b) + 2, 800)
215
+ y_vals = f(x_vals)
216
+
217
+ plt.figure(figsize=(9, 6))
218
+ plt.plot(x_vals, y_vals, 'b', linewidth=1.8, label='f(x)')
219
+ plt.axhline(0, color='black', linestyle='--', linewidth=1)
220
+
221
+ for i in range(len(history) - 1):
222
+ x0, f0 = history[i]
223
+ x1, f1 = history[i + 1]
224
+ m = (f1 - f0) / (x1 - x0)
225
+
226
+ xs = np.linspace(min(x0, x1) - 0.5, max(x0, x1) + 0.5, 50)
227
+ ys = f1 + m * (xs - x1)
228
+ plt.plot(xs, ys, 'orange', linestyle='--', alpha=0.7)
229
+
230
+ plt.scatter([x0, x1], [f0, f1], color=['red', 'green'])
231
+ plt.scatter(x1, 0, color='purple', marker='x', s=80)
232
+ plt.text(x1, 0, f"x{i+1}", color='purple', fontsize=9, va='bottom')
233
+
234
+ plt.title('Método da Secante – Aproximações sucessivas')
235
+ plt.xlabel('x')
236
+ plt.ylabel('f(x)')
237
+ plt.legend()
238
+ plt.grid(True, linestyle=':')
239
+ plt.tight_layout()
240
+ plt.show()
241
+
242
+ return c
243
+
244
+ def raiz(f: Callable[..., object],
245
+ a: float | None = None,
246
+ b: float | None = None,
247
+ x0: float | None = None,
248
+ tol: float = 1e-6,
249
+ method: str = "bissecao",
250
+ max_iter: int = 100,
251
+ plot: bool = True
252
+ ) -> float:
253
+ """
254
+ Função auxiliar para selecionar o método de busca de raiz desejado.
255
+
256
+ Encaminha a execução para um dos métodos disponíveis:
257
+ - "bissecao"
258
+ - "secante"
259
+ - "newton"
260
+
261
+ Args:
262
+ f (Callable[..., object]): Função alvo cuja raiz se deseja encontrar.
263
+ a (float | None, optional): Limite esquerdo ou primeiro chute inicial (quando aplicável).
264
+ b (float | None, optional): Limite direito ou segundo chute inicial (quando aplicável).
265
+ x0 (float | None, optional): Chute inicial para o método de Newton (quando aplicável).
266
+ tol (float, optional): Tolerância para os métodos. Por padrão 1e-6.
267
+ method (str, optional): Método a ser utilizado: "bissecao", "secante" ou "newton". Por padrão "bissecao".
268
+ max_iter (int, optional): Número máximo de iterações. Padrão: 100.
269
+ plot (bool, optional): Se True, exibe gráficos. Padrão: True.
270
+
271
+ Raises:
272
+ ValueError: Se o método especificado for inválido.
273
+
274
+ Returns:
275
+ float: Aproximação da raiz obtida pelo método selecionado.
276
+ """
277
+ if method not in ("bissecao", "secante", "newton"):
278
+ raise ValueError("Método inválido.")
279
+ if method == "bissecao":
280
+ return bissecao(f, a, b, tol, max_iter, plot)
281
+ elif method == "secante":
282
+ return secante(f, a, b, tol, max_iter, plot)
283
+ elif method == "newton":
284
+ return newton(f, x0, tol, max_iter, plot)