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.
- CB2325NumericaG5/__init__.py +0 -0
- CB2325NumericaG5/aproximacao.py +251 -0
- CB2325NumericaG5/erros.py +104 -0
- CB2325NumericaG5/graficos_aproximacao.py +60 -0
- CB2325NumericaG5/integracao.py +190 -0
- CB2325NumericaG5/interpolacao.py +410 -0
- CB2325NumericaG5/raizes.py +284 -0
- cb2325numericag5-0.0.1.dist-info/METADATA +20 -0
- cb2325numericag5-0.0.1.dist-info/RECORD +12 -0
- cb2325numericag5-0.0.1.dist-info/WHEEL +5 -0
- cb2325numericag5-0.0.1.dist-info/licenses/LICENSE +21 -0
- cb2325numericag5-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -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)
|