Skip to content

Commit

Permalink
exemplo lei de kepler
Browse files Browse the repository at this point in the history
  • Loading branch information
viniciusdutra314 committed Jan 25, 2025
1 parent 42ccf72 commit 20e8059
Show file tree
Hide file tree
Showing 16 changed files with 204 additions and 144 deletions.
71 changes: 32 additions & 39 deletions LabIFSC2/_regressões.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,12 @@ def __init__(self)->None:
self._valores: Iterator = iter([])
self._sigmas:float=2

def _retornar(self,y:np.ndarray|Medida,unidade_y:str,retornar_como_medidas:bool=False)->np.ndarray|Medida:
def _retornar(self,y:np.ndarray|Medida,unidade_y:str)->np.ndarray|Medida:
if isinstance(y,Medida): y=np.array([y])
self._amostragem_pre_calculada_nominal=nominais(y,unidade_y)
self._amostragem_pre_calculada_incerteza=incertezas(y,unidade_y)
if y.size==1: y=y[0]
if retornar_como_medidas: return y
else: return self._amostragem_pre_calculada_nominal
return self._amostragem_pre_calculada_nominal

def _verificar_tipo_de_x(self,x:np.ndarray|Medida)->None:
if isinstance(x,np.ndarray):
Expand All @@ -65,8 +64,7 @@ def _verificar_tipo_de_x(self,x:np.ndarray|Medida)->None:
def __repr__(self)->str:...

@abstractmethod
def amostrar(self, x:np.ndarray|Medida,
unidade_y:str,retornar_medidas:bool=False) -> np.ndarray|Medida:...
def amostrar(self, x:np.ndarray|Medida,unidade_y:str) -> np.ndarray|Medida:...

def __iter__(self)->Iterator[object]:
return self._valores
Expand All @@ -81,6 +79,9 @@ def mudar_intervalo_de_confianca(self,sigmas:Real)->None:
Returns:
None
"""
if sigmas<0:
raise ValueError('O intervalo de confiança precisa ser um valor positivo')

self._sigmas=float(sigmas)

@property
Expand Down Expand Up @@ -132,24 +133,22 @@ def __init__(self,coeficientes:np.ndarray):
self.grau=len(coeficientes)-1

@obrigar_tipos
def amostrar(self:'MPolinomio', x:np.ndarray | Medida,unidade_y:str,
retornar_como_medidas:bool=False) -> np.ndarray | Medida:
def amostrar(self:'MPolinomio', x:np.ndarray | Medida,unidade_y:str) -> np.ndarray | Medida:
"""
Calcula a função nos pontos de entrada x
Gera uma amostra de valores y a partir de um conjunto de valores x utilizando os coeficientes do polinômio.
Args:
x (np.ndarray | Medida): Valores de entrada para os quais as amostras serão geradas.
x (np.ndarray | Medida): Conjunto de valores de entrada.
unidade_y (str): Unidade de medida para os valores de saída.
retornar_como_medidas (bool, optional): Se True, retorna os valores como instâncias da classe Medida.
Caso contrário, retorna como np.ndarray. O padrão é False.
Returns:
np.ndarray | Medida: Valores de saída gerados pelo polinômio, no formato especificado por retornar_como_medidas.
np.ndarray | Medida: Valores de saída calculados a partir do polinômio.
"""

self._verificar_tipo_de_x(x)
y=Medida(0,unidade_y,0)
for index,coef in enumerate(self._coeficientes):y+=coef*x**(self.grau-index)
return self._retornar(y,unidade_y,retornar_como_medidas)
return self._retornar(y,unidade_y)

def __iter__(self) -> Iterator[Medida]:
return iter(self._coeficientes)
Expand All @@ -172,24 +171,21 @@ def __init__(self,a:Medida,k:Medida,base:Real):
self._valores=iter((a,k,base))

@obrigar_tipos
def amostrar(self:'MExponencial', x:np.ndarray|Medida, unidade_y:str,retornar_como_medidas:bool=False)->np.ndarray|Medida:
def amostrar(self:'MExponencial', x:np.ndarray|Medida,unidade_y:str)->np.ndarray|Medida:
"""
Calcula a função nos pontos de entrada x
Gera uma amostra exponencial baseada nos parâmetros fornecidos.
Args:
x (np.ndarray | Medida): Valores de entrada para a função exponencial.
unidade_y (str): Unidade de medida para os valores de saída.
retornar_como_medidas (bool, optional): Indica se os valores de saída devem ser retornados como objetos do tipo Medida.
Padrão é False.
x (np.ndarray | Medida): O valor ou array de valores para os quais a amostra será gerada.
Returns:
np.ndarray | Medida: Valores de saída calculados pela função exponencial,
no formato especificado por retornar_como_medidas.
np.ndarray | Medida: A amostra gerada, no mesmo formato do parâmetro de entrada `x`.
"""

self._verificar_tipo_de_x(x)
y:np.ndarray|Medida=np.power(float(self.base),(self.expoente*x))*self.cte_multiplicativa
return self._retornar(y,unidade_y,retornar_como_medidas)
return self._retornar(y,unidade_y)

def __repr__(self)->str:
return f'MExponencial(cte_multiplicativa={self.cte_multiplicativa},expoente={self.expoente},base={self.base})'
Expand All @@ -198,40 +194,37 @@ class MLeiDePotencia(ABCRegressao):
@obrigar_tipos
def __init__(self, a: Medida, n: Medida,y_unidade:pint.Quantity):
super().__init__()
self.a = a
self.n = n
self.cte_multiplicativa = a
self.potencia = n
self._valores=iter([a,n])
self._y_unidade=y_unidade

@obrigar_tipos
def amostrar(self:'MLeiDePotencia', x:np.ndarray|Medida,
unidade_y:str,retornar_como_medidas:bool=False) -> np.ndarray|Medida:
def amostrar(self:'MLeiDePotencia', x:np.ndarray|Medida,unidade_y:str) -> np.ndarray|Medida:
"""
Calcula a função nos pontos de entrada x
Amostra valores com base na lei de potência.
Args:
x (np.ndarray | Medida): Valores de entrada para a amostragem.
unidade_y (str): Unidade de medida para a saída y.
retornar_como_medidas (bool, optional): Se True, retorna os valores como objetos Medida.
Caso contrário, retorna como np.ndarray.
Padrão é False.
x (np.ndarray | Medida): Valores de entrada para amostragem.
unidade_y (str): Unidade da medida de saída.
Returns:
np.ndarray | Medida: Valores amostrados, no formato especificado por retornar_como_medidas.
np.ndarray | Medida: Valores amostrados com a unidade especificada.
"""

self._verificar_tipo_de_x(x)
if isinstance(x,Medida):x=np.array([x])
unidade_expoente=str((x[0]._nominal**self.n._nominal).units)
unidade_expoente=str((x[0]._nominal**self.potencia._nominal).units)
x=_forcar_troca_de_unidade(x,'')
expoente=x**self.n
expoente=x**self.potencia
expoente_medida=_forcar_troca_de_unidade(expoente,unidade_expoente)
y=expoente_medida*self.a
y=expoente_medida*self.cte_multiplicativa
if not y[0]._nominal.is_compatible_with(self._y_unidade):
raise ValueError(f'Unidade de x não está correta')
return self._retornar(y,unidade_y,retornar_como_medidas)
return self._retornar(y,unidade_y)

def __repr__(self)->str:
return f'MLeiDePotencia(a={self.a}, b={self.n})'
return f'MLeiDePotencia(cte_multiplicativa={self.cte_multiplicativa}, potencia={self.potencia})'



Expand Down
1 change: 1 addition & 0 deletions LabIFSC2/constantes/constantes.py
Original file line number Diff line number Diff line change
Expand Up @@ -368,3 +368,4 @@
euler=2.7182818284590452353602874713527
golden_ratio=1.61803398874989484820458
astronomical_unit=Medida(149_597_870_700,"m",0)
solar_mass=Medida(1.988475e30,"kg",0.000092e30)
11 changes: 1 addition & 10 deletions docs/_instalacao.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,8 @@

## Instalação em ambiente virtual
# Instalação em ambiente virtual

O ecossistema Python é cheio de gerenciadores de pacotes/ambientes ([pipx](https://github.com/pypa/pipx), [poetry](https://python-poetry.org/), [uv](https://astral.sh/blog/uv), [miniconda](https://docs.anaconda.com/miniconda/), etc.). Sinta-se livre para escolher o de sua preferência. Caso seja um iniciante no assunto, recomendamos testar o [uv](https://astral.sh/blog/uv).

### Global

Caso queira utilizar o LabIFSC2 para todos os projetos, faça:
```bash
pip install LabIFSC2
```

### Local

Iremos descrever aqui como uma instalação local é feita usando somente as ferramentas nativas do Python (pip, venv).

Com o comando abaixo, estamos chamando o módulo (python -m) venv (virtual environment) e criando um ambiente virtual na pasta .venv (na maioria dos sistemas operacionais, isso será uma pasta oculta, visto que começa com ponto):
Expand Down
64 changes: 0 additions & 64 deletions docs/como_contribuir.md

This file was deleted.

2 changes: 2 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ A biblioteca está disponível no PyPI(Python Package Index), então ela pode se
```bash
pip install LabIFSC2
```
Recomendamos você instalar o LabIFSC2 é um ambiente virtual, caso não saiba o que é isso, por favor
leia essa [secção](_instalacao.md)

## Escopo de aplicação
A biblioteca tem a intenção de agilizar cálculos dos laboratórios de física do IFSC da USP de São Carlos:
Expand Down
2 changes: 1 addition & 1 deletion docs/introducao.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import LabIFSC2 as lab
```

## Classe Medida

A principal classe da biblioteca é a `Medida`. Ela recebe 3 argumentos para sua inicialização[^1]:

- Nominal (\(\mu\)): Valor(es) medido(s).
Expand All @@ -21,7 +22,6 @@ A principal classe da biblioteca é a `Medida`. Ela recebe 3 argumentos para sua
Para um exemplo prático, consideraremos um tipo de medida comum, o IMC (Índice de Massa Corporal), não confundir com (ICMC). Suponhamos que uma pessoa foi medida (valores fictícios) com uma fita métrica e temos certeza apenas na casa de 1 cm de sua altura e usamos uma balança com precisão de 100 g para medir a sua massa.

Com o LabIFSC2, podemos fazer os cálculos assim:

```py title="Cálculo de IMC"
--8<-- "tests/test_doc_imc.py:5:8"
```
Expand Down
71 changes: 63 additions & 8 deletions docs/regressao_pratica.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ Temos 4 regressões possíveis:
O resultado é guardado respectivamente em um objeto `MPolinomio`,`MExponencial` e `MPotencia`. Essas classes são
todas herdadas da mesma classe abstrata, então elas compartilham a mesma interface. Na prática essas classes
são irrelevantes, você provavelmente só vai pensar que está lidando com uma regressão.
!!! warning
Lembre-se que, para regressões exponenciais, todos os valores de y precisam ser positivos. No caso da regressão de lei de potência, os valores em x também precisam ser positivos. Além disso, um valor pode não ser negativo, mas devido à incerteza associada, ele pode assumir valores negativos

## Calcular regressão

### Polinomial
Expand All @@ -25,8 +28,6 @@ de que **a** é o coeficiente de maior grau), ou fazendo a técnica de unpacking
coeficientes ao polinomio (igual ao unpacking de uma tupla)

### Exponencial
!!! warning
Lembre-se que, para regressões exponenciais, todos os valores de y precisam ser positivos. No caso da regressão de lei de potência, os valores em x também precisam ser positivos. Além disso, um valor pode não ser negativo, mas devido à incerteza associada, ele pode assumir valores negativos

Imagine um experimento que queiramos determinar a meia vida de um material radioativo, *as escalas de massa e tempo
são somente ilustrativas*, podemos acessar a constante multiplicativa fazendo `exponencial.cte_multiplicativa` e o expoente
Expand All @@ -37,18 +38,72 @@ fazendo `exponencial.expoente`.
```
Repare que a regressão aceita uma base (por padrão base=\(e\))

## Lei de Potência
Um exemplo clássico da lei de potência é a terceira lei de Kepler, onde o período orbital (T) se relaciona com o raio orbital (R) segundo:
$$\frac{T²}{R³}=\frac{4\pi^2}{GM}\to T \propto R^{1.5}$$
A regressão de lei de potência encontra parâmetros como a constante multiplicativa e o expoente a partir de dados experimentais. O exemplo abaixo pega dados da [NASA](https://nssdc.gsfc.nasa.gov/planetary/factsheet/planet_table_british.html)
para demonstrar experimentalmente a terceira lei de Kepler, eu peguei as distâncias em milhas justamente para demonstrar
como com o LabIFSC2 você não precisa se preocupar com unidades.


```py
--8<-- "tests/test_doc_kepler.py:8:29"
```
Perceba como essa lei de fato aproxima muito bem os dados, essa 'lei' na verdade é uma aproximação que só considera
a atração gravitacional do sol, então é esperado observar alguns pequenos desvios visto que o sistema solar não é composto
só pelo sol, mas um sistema complexo de dezenas de milhares de corpos massivos.
## Amostrar

Quando queremos fazer o gráfico de uma curva precisamos amostrar essa curva em conjunto de pontos,
tomemos o exemplo simples de fazer o gráfico da função exponencial
Quando queremos fazer o gráfico de uma curva precisamos amostrar essa curva em conjunto de pontos, para isso
precisamos especificar em que intervalo queremos calcular a curva e em que unidades esse cálculo deve retornar

- `amostrar(intervalo_em_x,unidade_y)`

No exemplo abaixo calculamos o que a nossa regressão (da secção anterior) no intervalo dos planetas do sistema solar ([0,30] unidades astronômicas),
e pedimos para ele retornar esse resultado em anos

A interface do LabIFSC2 é parecida, a diferença é que devido a [propagação de erros](propagacao_de_erros.md) ser feita por monte carlo, além da curva temos também a dispersão dessa curva teórica.
```py hl_lines="36 61"
--8<-- "tests/test_doc_kepler.py:36:63"
```

- `amostrar(intervalo_em_x,unidade_y)`
<img src="./images/kepler.jpg" width=600>

Podemos visualizar esses dados fazendo um pequeno código em matplotlib, para ler mais sobre
gráficos vá para a secção [Gráficos](graficos.md)

```py
--8<-- "tests/test_doc_kepler.py:64:79"
```


!!!warning
Visto que a biblioteca realiza uma simulação Monte Carlo para cada ponto da curva, usar
muitos pontos de amostragem provavelmente irá causar uma lentidão no seu código, no meu
notebook eu tive uma boa experiência amostrando a regressão em **100 pontos**, recomendo você
começar com esse valor, se a curva ficar pouco definida aumente esse valor.

Outra dica, caso tenha que usar a amostragem mais de uma vez, salve o array em uma variável,
assim não ira precisar calcular duas vezes. Eu fiz isso no código de exemplo, eu queria printar
a amostragem e também usa-la no matplotlib, isso é uma dica geral de programação, tente sempre
calcular as coisas só uma vez.


## curva_min e curva_max
Como estamos tratando os fittings usando medidas, também temos a incerteza associada ao fitting,
quando chamamos a função `amostrar`, a biblioteca automaticamente cria dois atributos no objeto
regressão, `regressao.curva_min` e `regressao.curva_max`.

Essas curvas são as [bandas de confiança](https://en.wikipedia.org/wiki/Confidence_and_prediction_bands) do fitting, existem
várias maneiras de faze-las mas basicamente estamos aproximando cada ponto amostrado como uma distribuição gaussiana e
calculando o intervalo de confiança dessa gaussiana.

### curvamin e curvamax
Resumidamente, estamos pegando \(2\sigma\) abaixo e acima do fitting, pela hipótese da distribuição gaussiana[^1],
os dados devem cair nesse intervalo com 95% de certeza

Caso você queira uma confiança diferente dessa basta chamar a função `regressao.mudar_intervalo_de_confianca`
e especificar quantos sigmas de confiança

### Exemplos
[^1]:
Se a biblioteca trabalha com distribuições estatísticas arbitrárias, por que fazer essa hipótese?
Basicamente cada chamada da função intervalo_de_confiança para uma distribuição arbitrária,
é \(O(nln(n))\) de complexidade, então calcular ela em centenas ou milhares de pontos é inviável
1 change: 0 additions & 1 deletion mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ nav:
- Formataçõs/LaTeX: formatacoes_latex.md
- Como erros são propagados?: propagacao_de_erros.md
- API Completa: api.md
- Como contribuir: contribuir.md

repo_url: https://github.com/viniciusdutra314/LabIFSC2
theme:
Expand Down
3 changes: 2 additions & 1 deletion scripts/gerar_codata_constantes.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,5 @@
python_arquivo.write('pi=3.14159265358979323846\n')
python_arquivo.write('euler=2.7182818284590452353602874713527\n')
python_arquivo.write('golden_ratio=1.61803398874989484820458\n')
python_arquivo.write('astronomical_unit=Medida(149_597_870_700,"m",0)\n')
python_arquivo.write('astronomical_unit=Medida(149_597_870_700,"m",0)\n')
python_arquivo.write('solar_mass=Medida(1.988475e30,"kg",0.000092e30)\n') #https://en.wikipedia.org/wiki/Solar_mass
Loading

0 comments on commit 20e8059

Please sign in to comment.