diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..41c67a4 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,100 @@ +name: Quarto Documentation + +on: + push: + branches: [main] + tags: [v*] + workflow_dispatch: + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + actions: write + contents: write # needed for gh-pages + +jobs: + build-docs: + name: Build and Deploy Documentation + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: '3.11' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install ".[dev]" + + - name: Set PYTHONPATH + run: echo "PYTHONPATH=$GITHUB_WORKSPACE/src" >> $GITHUB_ENV + + - name: Install Quarto + uses: quarto-dev/quarto-actions/setup@v2 + + - name: Check Quarto installation + run: | + quarto check + + - name: Render Quarto site + run: | + quarto render book + + # Deploy Preview for PRs + - name: Publish PR Preview + if: github.event_name == 'pull_request' + uses: peaceiris/actions-gh-pages@v4 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./docs/_site + destination_dir: previews/PR${{ github.event.number }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # Deploy Dev Site from main + - name: Publish Dev Site + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + uses: peaceiris/actions-gh-pages@v4 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./docs/_site + destination_dir: dev + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # Deploy Versioned Release + - name: Publish Versioned Site + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') + uses: peaceiris/actions-gh-pages@v4 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./docs/_site + destination_dir: ${{ github.ref_name }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Create 'latest' alias for stable release + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') && !contains(github.ref_name, '-') + run: | + version="${GITHUB_REF#refs/tags/}" + echo "Detected version: $version" + mkdir -p ./latest + cp -r ./docs/_site/* ./latest/ + + - name: Publish stable release to 'latest' + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') && !contains(github.ref_name, '-') + uses: peaceiris/actions-gh-pages@v4 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./latest + destination_dir: latest + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..485d8bd --- /dev/null +++ b/.gitignore @@ -0,0 +1,149 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + + +**/lightning_logs/** + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# IPython / Jupyter Notebook +.ipynb_checkpoints +*.ipynb_export + +# PEP 582; used by e.g. python-pdm +__pypackages__/ + +# pyenv +.python-version + +# pipenv +Pipfile.lock + +# poetry +poetry.lock + +# pdm +pdm.lock +__pypackages__/ + +# celery beat schedule file +celerybeat-schedule +celerybeat.pid + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# pyre type checker +.pyre/ + +# pytype +.pytype/ + +# Cython debug symbols +cython_debug/ + +# VS Code +.vscode/ + +# PyCharm +.idea/ + +# macOS +.DS_Store + +# Others +*.log +*.tmp \ No newline at end of file diff --git a/book/.gitignore b/book/.gitignore index 4f0c017..8aec4bf 100644 --- a/book/.gitignore +++ b/book/.gitignore @@ -1,3 +1,5 @@ /.quarto/ -_book/ \ No newline at end of file +_book/ + +**/lightning_logs/** \ No newline at end of file diff --git a/book/_quarto.yml b/book/_quarto.yml index 53caa0a..fc18f34 100644 --- a/book/_quarto.yml +++ b/book/_quarto.yml @@ -2,7 +2,7 @@ project: type: book book: - title: "book" + title: "Previsão de Séries temporais com Python: um pequeno guia" author: "Felipe Angelim" date: "10/11/2025" chapters: @@ -17,18 +17,13 @@ book: - part: "Part II: Intermediário" chapters: - content/pt/part2/exog_variables.qmd - - content/pt/part2/feature_engineering.qmd - content/pt/part2/ml_models.qmd - - content/pt/part2/probabilistic_forecasting.qmd - - content/pt/part2/probabilistic_metrics.qmd - - part: "Part III: Avançado" + - content/pt/part2/panel_data.qmd + - content/pt/part2/hierarchical_forecasting.qmd + - content/pt/part2/deep_learning.qmd + - part: "Part III: Apêndices" chapters: - - content/pt/part3/panel_data.qmd - - content/pt/part3/hierarchical_forecasting.qmd - - content/pt/part3/deep_learning.qmd - - part: "Part IV: Apêndices" - chapters: - - content/pt/part4/sktime_custom.qmd + - content/pt/extra/sktime_custom.qmd bibliography: references.bib diff --git a/book/content/pt/extra/img/private_methods.png b/book/content/pt/extra/img/private_methods.png new file mode 100644 index 0000000..fa12ee2 Binary files /dev/null and b/book/content/pt/extra/img/private_methods.png differ diff --git a/book/content/pt/extra/sktime_custom.qmd b/book/content/pt/extra/sktime_custom.qmd new file mode 100644 index 0000000..cb8d193 --- /dev/null +++ b/book/content/pt/extra/sktime_custom.qmd @@ -0,0 +1,380 @@ +# Criando modelos customizados com sktime + +O sktime oferece um ecossistema robusto, mas em cenarios reais frequentemente precisamos ajustar comportamentos, incorporar dados hierarquicos ou adicionar pre-processamentos especificos. Felizmente, o sktime torna relativamente simples a criacao de modelos customizados, desde que sigamos algumas regras. + +Acredito que essa e uma das grandes vantagens da biblioteca: o foco em ser extensivel e customizavel. + +## O sistema de tags + +Entre a chamada dos metodos publicos e privados existe uma camada de validacoes e conversões controlada pelas tags. Elas sinalizam ao `BaseForecaster` e ao `BaseTransformer` o que precisa ser garantido antes de executar a implementacao customizada. + +As tags mais importantes para um forecaster podem ser definidas assim: + +```python +_tags = { + "capability:exogenous": True, + "requires-fh-in-fit": False, + "X_inner_mtype": [ + "pd.Series", + "pd.DataFrame", + "pd-multiindex", + "pd_multiindex_hier", + ], + "y_inner_mtype": [ + "pd.Series", + "pd.DataFrame", + "pd-multiindex", + "pd_multiindex_hier", + ] +} +``` + +Cada uma delas indica o que o modelo é capaz de fazer nos seus métodos privados +`_fit` e `_predict`: + +* `capability:exogenous`: Indica se o modelo suporta variáveis exógenas (X) durante o ajuste e a previsão. +* `requires-fh-in-fit`: Indica se o modelo precisa do horizonte de previsão (fh) durante o ajuste. Alguns modelos precisam devido a sua implementação interna. +* `y_inner_mtype`: Define os tipos de dados aceitos para a variável dependente (y) durante o ajuste e a previsão. +* `X_inner_mtype`: Define os tipos de dados aceitos para as variáveis exógenas (X) durante o ajuste e a previsão. + +Os machine-types (**mtypes**), ou tipos para máquina, são a peça mais crucial nesse sistema. + +### Machine-types disponiveis + +Os `mtypes` definem qual a estrutura de dados que o modelo aceita como entrada e produz como saída. Os principais mtypes para séries temporais são: + +- `np.ndarray` +- `pd.Series` +- `pd.DataFrame` +- `pd-multiindex` (ideia de painel) +- `pd_multiindex_hier` (dados hierarquicos) + +Se o modelo suporta um `mtype` hierárquico e passamos um dado hierárquico, o +dado chegará normalmente ao método privado `_fit` ou `_predict`. Caso contrário, o sktime tentará converter o dado para um mtype suportado. + +#### Baixando exemplos por mtype + +Para entender melhor cada mtype, podemos baixar exemplos práticos usando a função `get_examples` do sktime: + +```{python} +from sktime.datatypes import get_examples + +get_examples(mtype="np.ndarray", as_scitype="Series")[0] +``` + +```{python} +get_examples(mtype="pd.DataFrame", as_scitype="Series")[0].head() +``` + +```{python} +get_examples(mtype="pd-multiindex", as_scitype="Panel")[0].head() +``` + +```{python} +get_examples(mtype="pd_multiindex_hier", as_scitype="Hierarchical")[0].head() +``` + +Alguns mtypes tem limitações: uma `pd.Series` simples nao representa problemas hierarquicos, sendo necessario recorrer ao `pd_multiindex_hier`. + +#### Exemplo prático + +Vamos criar o nosso primeiro esqueleto de forecaster customizado. Para isso, baixamos uma série de exemplo: + +```{python} +# | echo: false +import warnings + +warnings.filterwarnings("ignore") +``` + +```{python} +from sktime.forecasting.base import BaseForecaster +from sktime.utils._testing.series import _make_series + +y = _make_series(4) +y +``` + +Nosso protótipo irá apenas printar os dados recebidos no método `_fit`. O `__init__` recebe um dicionário de tags para definir as capacidades do modelo. + +```{python} +class Logger(BaseForecaster): + + _tags = { + "requires-fh-in-fit": False, + } + + def __init__(self, tags_to_set): + self.tags_to_set = tags_to_set + super().__init__() + + self.set_tags(**tags_to_set) + + def _fit(self, y, X=None, fh=None): + print("Inside fit:") + print(y) + return self + +``` + + +```{python} +logger = Logger(tags_to_set={"y_inner_mtype" : ["pd.Series"] }) +logger.fit(y) +``` + +```{python} +logger = Logger(tags_to_set={"y_inner_mtype" : ["np.ndarray"] }) +logger.fit(y) +``` + +```{python} + +logger = Logger(tags_to_set={"y_inner_mtype" : ["pd.DataFrame"] }) +logger.fit(y) + +``` + +```{python} +try: + logger = Logger(tags_to_set={"y_inner_mtype" : ["pd_multiindex_hier"] }) + logger.fit(y) +except ValueError as e: + print(e) +``` + +```{python} +try: + logger = Logger(tags_to_set={"y_inner_mtype": ["pd.DataFrame", "pd_multiindex_hier"]}) + logger.fit(y) +except ValueError as e: + print(e) +``` + +#### Input hierárquico + +Agora veremos como o modelo se comporta com dados hierárquicos. Note que, nos casos onde o modelo não suporta dados hierárquicos, o sktime tentará convertê-los para um mtype suportado. + +```{python} + +from sktime.utils._testing.hierarchical import _make_hierarchical + +y = _make_hierarchical((1,2), max_timepoints=4, min_timepoints=2) +y +``` + + +```{python} +logger = Logger(tags_to_set={"y_inner_mtype" : ["pd.Series"] }) +logger.fit(y) +``` + + +```{python} +logger = Logger(tags_to_set={"y_inner_mtype" : ["np.ndarray"] }) +logger.fit(y) +``` + + +```{python} +logger = Logger(tags_to_set={"y_inner_mtype" : ["pd.DataFrame"] }) +logger.fit(y) +``` + + +```{python} +try: + logger = Logger(tags_to_set={"y_inner_mtype": ["pd_multiindex_hier"]}) + logger.fit(y) +except ValueError as e: + print(e) +``` + +## Criando um modelo naive + +Agora, vamos implementar um modelo simples de previsão, o `CustomNaiveForecaster`, que prevê o valor médio dos últimos n pontos da série temporal. + +É um exemplo simples, mas que ilustra bem como criar um forecaster customizado com sktime. + +```{python} +from tsbook.datasets.retail import SyntheticRetail + +dataset = SyntheticRetail("panel") +y_train, y_test = dataset.load("y_train", "y_test") +y_train +``` + + +```{python} +from sktime.utils.plotting import plot_series + +plot_series( + y_train.loc[0], + y_train.loc[24], + labels=[ + "SKU 0", + "SKU 24", + ], +) + +``` + + +Abaixo, implementamos o `CustomNaiveForecaster` seguindo as regras do sktime (clique para expandir). Em seguida, explicamos passo a passo. + +```{python} +# | code-fold: true +from sktime.forecasting.base import BaseForecaster +import pandas as pd + + +class CustomNaiveForecaster(BaseForecaster): + """ + A simple naive forecaster + + Parameters + ---------- + n : int + Number of past values to use. + """ + + _tags = { + "requires-fh-in-fit": False, + "y_inner_mtype": [ + "pd.Series", + ], + } + + # Add hyperparameters in init! + def __init__(self, n=1): + # 1. Set hyper-parameters + self.n = n + + # 2. Initialize parent class + super().__init__() + + # 3. Check hyper-parameters + assert self.n > 0, "n must be greater than 0" + + def _fit(self, y, X, fh): + """ + Fit necessary parameters. + """ + + self.value_ = y.iloc[-self.n :].mean() + return self + + def _predict(self, fh, X): + """ + Use forecasting horizon and optionally X to predict y + """ + + # During fit, BaseForecaster sets + # self.cutoff to the latest cutoff time point + index = fh.to_absolute_index(self.cutoff) + y_pred = pd.Series( + index=index, + data=[self.value_ for _ in range(len(index))], + ) + y_pred.name = self._y.name + + return y_pred + + # Veremos mais tarde como usar esse método + @classmethod + def get_test_params(cls, parameter_set="default"): + return [ + {"n": 1}, + {"n": 2}, + ] +``` + +### Definindo o método `__init__` + +O método `__init__` possui 3 etapas: + +1. A definição dos hiperparametros e seus atributos com mesmo nome. +2. A chamada do `super().__init__()` para inicializar a classe pai. +3. A validação dos hiperparâmetros. + + + +```python +# Add hyperparameters in init! +def __init__(self, n=1): + # 1. Set hyper-parameters + self.n = n + + # 2. Initialize parent class + super().__init__() + + # 3. Check hyper-parameters + assert self.n > 0, "n must be greater than 0" +``` + +No caso de algum preprocessamento dos hiperparâmetros no `__init__`, devemos guardar em uma variável com nome diferente do hiperparâmetro. Por exemplo, se tivéssemos interesse em ter um atributo `n` diferente do passado no `__init__`, poderíamos fazer: + +``` +self._n = n + 1 +``` + +O `self.n` funciona como uma digital do modelo, e deve ser exatamente o que foi passado no `__init__`. + +### Definindo o método `_fit` + +No método `_fit`, devemos implementar a lógica de ajuste do modelo. No nosso caso, calculamos a média dos últimos `n` valores e armazenamos em `self.value_`. + +O `_` após o nome do atributo indica que é um atributo aprendido durante o ajuste, e será retornado quando chamarmos `get_fitted_params()`. + +Note que podemos supor que `y` é do tipo definido na tag `y_inner_mtype`, ou seja, uma `pd.Series`. + +### Definindo o método `_predict` + +No método `_predict`, implementamos a lógica de previsão. Usamos o horizonte de previsão `fh` para determinar os índices futuros e retornamos uma série com o valor previsto para cada ponto no horizonte. + +O `fh` é um objeto do tipo `ForecastingHorizon`, que possui o método `to_absolute_index(cutoff)` para converter o horizonte relativo em índices absolutos, considerando o último ponto conhecido (`self.cutoff`). + +Retornamos um `pd.Series` com os índices e os valores previstos. + +### Usando o `CustomNaiveForecaster` + +Agora, já podemos usar o nosso modelo customizado para fazer previsões. + +```{python} +custom_naive_model = CustomNaiveForecaster() +custom_naive_model.fit(y_train) +``` + +Como passamos um dado hierárquico, o sktime converteu automaticamente para `pd.Series`, que é o mtype suportado pelo nosso modelo. Os modelos internos, para cada série, ficam disponíveis em `forecasters_`. + +```{python} +custom_naive_model.forecasters_ +``` + + +```{python} +y_pred = custom_naive_model.predict(fh=y_test.index.get_level_values(-1).unique()) + +fig, _ = plot_series( + y_train.loc[0], + y_pred.loc[0], + labels=[ + "SKU 0", + "Previsão SKU 0", + ], +) +fig.show() +``` + +## Testes unitários + +O sktime também fornece uma funcionalidade que traz testes unitários prontos para validar se o modelo customizado está funcionando corretamente. + +Ele usa os hiperparâmetros retornados pelo método `get_test_params` para criar instâncias do modelo e executar uma série de testes. + +```{python} +from sktime.utils.estimator_checks import check_estimator + + +check_estimator(CustomNaiveForecaster, tests_to_exclude=["test_doctest_examples"]) +``` \ No newline at end of file diff --git a/book/content/pt/part1/components_and_diff.qmd b/book/content/pt/part1/components_and_diff.qmd index a800bbf..0c4bb44 100644 --- a/book/content/pt/part1/components_and_diff.qmd +++ b/book/content/pt/part1/components_and_diff.qmd @@ -28,6 +28,44 @@ Os dados são diários, e vemos que sempre positivos. Também notamos que existe Algo que deve chamar a atenção nesse gráfico é que a magnitude da série temporal está aumentando ao longo do tempo. Isso não deve passar desapercebido, pois é um ponto importante para entendermos o que vem a seguir. +## Auto-correlação + +É interessante entender o quanto de informação o passado de uma série temporal carrega sobre o seu futuro. Uma maneira de capturar essa relação (considerando variações lineares) é através da auto-correlação. A autocorrelação para um determinado lag $k$ é definida como: + +$$ +\text{Corr}(Y_t,Y_{t-k})=\frac{\text{Cov}(Y_t,Y_{t-k})}{\sqrt{\text{Var}(Y_t)\text{Var}(Y_{t-k})}} = \frac{E[(Y_t - \mu)(Y_{t-k} - \mu)]}{\sqrt{\text{Var}(Y_t)\text{Var}(Y_{t-k})}} +$$ + +Em outras palavras, quando o valor em $k$ observações atrás está acima (ou abaixo) da média, o valor atual também tende a estar acima (ou abaixo) da média? + +Com `plot_correlations`, podemos visualizar algumas informações úteis: + +1. No plot superior, vemos a série temporal original. +2. No canto inferior esquerdo, temos o gráfico de autocorrelação (ACF), que mostra a correlação entre a série temporal e suas versões defasadas (lags). Valores próximos de 1 ou -1 indicam uma forte correlação positiva ou negativa, respectivamente. +3. No canto inferior direito, temos o gráfico de autocorrelação parcial (PACF), que mostra a correlação entre a série temporal e suas versões defasadas, controlando para as correlações intermediárias. É útil para entender o efeito isolado de um lag. + +```{python} +from sktime.utils.plotting import plot_correlations + + +fig, ax = plot_correlations(y_train, lags=60) +fig.show() +``` + +No gráfico de autocorrelação, vemos algumas características interessantes: + +1. Valores são extremamente altos, e decaem lentamente ao longo do tempo. +2. Existem oscilações claras, indicando padrões sazonais na série temporal. + +::: {.callout-tip} + + +É um erro comum usar a correlação de lags de uma série para seleção de variáveis (lags). Sempre que possível, devemos eliminar a tendência antes de analisar a autocorrelação. + +::: + +Veremos que esses padrões são indicativos de que a série temporal possui componentes importantes, e que valores passados dizem muito sobre valores futuros. + ## Componentes de séries temporais Séries temporais podem ser decompostas em 3 componentes principais: @@ -96,19 +134,12 @@ plot_series(y_train_log, labels=["Logaritmo"]) Ainda que não esteja perfeito, essa transformação estabiliza as variações da série temporal, o que é importante para alguns modelos de previsão. -```{python} -from sktime.utils.plotting import plot_correlations - - -plot_correlations(y_train, lags=60) -``` - - - ### Decompondo a série temporal -Sktime fornece algumas opções para decompor séries temporais. +Sktime fornece algumas opções para decompor séries temporais. Aqui, vamos usar o `Detrender` para remover a tendência, e o `Deseasonalizer` para remover a sazonalidade. + +#### Tendência ```{python} from sktime.transformations.series.detrend import Detrender, Deseasonalizer @@ -121,10 +152,21 @@ plot_series(y_train_detrended, labels=["Detrended"]) ``` +Vemos uma mudança importante no gráfico de autocorrelação: + ```{python} -plot_correlations(y_train_detrended, lags=60) +fig, _ = plot_correlations(y_train_detrended, lags=60) +fig.show() + ``` +O que indica que, ao eliminar a tendencia, a informação que o passado carrega sobre o futuro diminuiu bastante. Na verdade, a existência da tendência - um efeito de longo prazo - faz com que valores passados sejam altamente correlacionados com valores futuros, e pode dar a falsa impressão de que a série é "fácil" de modelar. + + +#### Sazonalidade + +Agora, usamos o `Deseasonalizer` para remover a sazonalidade: + ```{python} deseasonalizer = LogTransformer() * Deseasonalizer(model="additive", sp=365) deseasonalizer.fit(y_train) @@ -133,6 +175,8 @@ plot_series(y_train_log, y_train_deseasonalized, labels=["Log with seasonality", ``` +Podemos usar o `Detrender` e o `Deseasonalizer` juntos para remover ambos os componentes: + ```{python} remove_components = LogTransformer() * Detrender(model="additive") * Deseasonalizer(model="additive", sp=365) remove_components.fit(y_train) @@ -142,19 +186,14 @@ plot_series(y_train_log, y_train_removed, labels=["Log with seasonality", "Desea ```{python} -plot_correlations(y_train_removed, lags=60) +fig, _ = plot_correlations(y_train_removed, lags=60) +fig.show() ``` -```{python} - -remove_components = LogTransformer() * Detrender(model="additive") * Deseasonalizer(model="additive", sp=365) * Deseasonalizer(model="additive", sp=7) - -remove_components.fit(y_train) -y_train_removed = remove_components.transform(y_train) - -plot_correlations(y_train_removed, lags=60) -``` +::: {.callout-tip} +Tente adicionar mais um deseasonalizer para remover a sazonalidade semanal. +::: ## Séries estacionárias diff --git a/book/content/pt/part1/ets_and_ar.qmd b/book/content/pt/part1/ets_and_ar.qmd index 4ae0906..192d6b4 100644 --- a/book/content/pt/part1/ets_and_ar.qmd +++ b/book/content/pt/part1/ets_and_ar.qmd @@ -101,9 +101,38 @@ plot_series( ) ``` +Um modelo auto-regressivo mais complexo é o ARIMA, que combina autoregressão (AR), média móvel (MA) e diferenciação integrada (I) para lidar com séries temporais não estacionárias. Não vamos estudar o ARIMA aqui pois envolve conceitos mais avançados, mas temos ele disponível no sktime, `sktime.forecasting.arima.ARIMA`. ## STL: dividir e conquistar +Sabemos que séries temporais podem ser decompostas em componentes de tendência, sazonalidade e resíduos. O modelo STL (Seasonal and Trend decomposition using Loess) é uma técnica que permite fazer essa decomposição de forma robusta. + +Temos no sktime o `STLTransformer`, que permite fazer a decomposição STL: + +```{python} +from sktime.transformations.series.detrend import STLTransformer + +stl = STLTransformer(sp=365) +stl.fit(y_train) + +``` + +E agora podemos inspecionar os componentes: + +```{python} +fig, ax = plt.subplots(3, 1, figsize=(10, 8), sharex=True) +stl.trend_.plot.line(ax=ax[0]) +ax[0].set_title("Tendência") +stl.seasonal_.plot.line(ax=ax[1]) +ax[1].set_title("Sazonalidade") +stl.resid_.plot.line(ax=ax[2]) +ax[2].set_title("Resíduos") +fig.show() +``` + + +Uma possibilidade, uma vez que temos os diferentes componentes, é modelar cada componente separadamente e depois combinar as previsões. O sktime tem o `STLForecaster`, que faz exatamente isso: + ```{python} from sktime.forecasting.trend import STLForecaster from sktime.forecasting.naive import NaiveForecaster @@ -125,4 +154,35 @@ plot_series( labels=["Treino", "Teste", "Previsão STL + AR"], ) +``` + +Para fins de demonstração, podemos complicar um pouco mais o modelo, modelando os resíduos com outro `STLForecaster`: + +```{python} + +model = STLForecaster( + forecaster_trend=AutoREG(lags=31), + forecaster_seasonal=NaiveForecaster(sp=7), + forecaster_resid=STLForecaster( + forecaster_trend=AutoREG(lags=31), + forecaster_seasonal=NaiveForecaster(sp=365), + forecaster_resid=AutoREG(lags=31), + sp=365, + ), + sp=7, +) + +model.fit(y_train) +``` + +```{python} +y_pred = model.predict(fh=y_test.index) + +plot_series( + y_train, + y_test, + y_pred, + labels=["Treino", "Teste", "Previsão STL + AR"], +) + ``` \ No newline at end of file diff --git a/book/content/pt/part2/deep_learning.qmd b/book/content/pt/part2/deep_learning.qmd new file mode 100644 index 0000000..2dd690f --- /dev/null +++ b/book/content/pt/part2/deep_learning.qmd @@ -0,0 +1,152 @@ +## Modelos de deep learning e zero-shot forecasting + +Além de modelos de ML simples, também podemos usar modelos de deep learning para forecasting. Podemos com certeza usar uma rede neural simples como um regressor, assim como fizemos com os modelos de ML tradicionais. No entanto, existem alguns modelos com arquiteturas específicas para forecasting de séries temporais. Por exemplo, o N-BEATS é um modelo de deep learning que pode ser usado para forecasting. + +::: {.callout-tip} + +Importante notar que esse livro/tutorial tem o objetivo de ser curto e prático, então não entraremos em muitos detalhes sobre deep learning ou sobre todos métodos +existentes. + +::: + + +```{python} +# | echo: false +import warnings + +warnings.filterwarnings("ignore") +``` + +```{python} +# | code-fold: true +import pandas as pd +import matplotlib.pyplot as plt + +from sktime.utils.plotting import plot_series + +``` + +```{python} +# | code-fold: true + +from tsbook.datasets.retail import SyntheticRetail + +dataset = SyntheticRetail("panel") +y_train, X_train, y_test, X_test = dataset.load( + "y_train", "X_train", "y_test", "X_test" +) + +fh = y_test.index.get_level_values(-1).unique() +``` + +## N-Beats + + N-BEATS é um modelo de séries temporais totalmente baseado em camadas densas (MLP)—nada de RNN/LSTM nem convolução. Ele pega uma janela do passado e entrega diretamente a previsão do futuro, aprendendo padrões como tendência e sazonalidade de forma pura, só com perceptrons. + + +O N-BEATS usa bases para construir sinais interpretáveis: + +* Base de tendência: combina funções polinomiais (captura subidas/descidas suaves). +* Base sazonal: combina senos/cossenos (captura repetições periódicas). +* Base genérica: aprende formas livres (sem pressupor forma analítica). + +Assim, internamente, ele determina os coeficientes das funções base para modelar a série temporal, baseado no histórico observado, fazendo uma espécie de meta-aprendizado interno. + +Os blocos são empilhados e executados de forma sucessiva. Pense numa fila de especialistas olhando a mesma janela do passado: + +* Cada um explica a sua parte do que viu (remove do passado o que já foi entendido = backcast), e propõe um pedaço da previsão (seu forecast). +* O que não foi explicado segue para o próximo especialista. No final, a previsão é a soma do que cada um sugeriu. + +![](img/nbeats_simplified.png) + + +### Pytorch Forecasting + +O Sktime nao é uma biblioteca especializada em deep learning, mas sim uma API +uniforme que provê acesso aos mais diversos algoritmos. + +Logo, também provemos acesso a bibliotecas especializadas em deep learning, como o +Pytorch Forecasting, que implementa o N-BEATS. + +Aqui, temos que definir os hiperparâmetros do modelo, como o número de blocos, o tamanho do contexto (janela de entrada), e o número de coeficientes para as funcões de base. + +```{python} +from sktime.forecasting.pytorchforecasting import PytorchForecastingNBeats +from pytorch_forecasting.data.encoders import EncoderNormalizer + +CONTEXT_LENGTH = 120 +nbeats = PytorchForecastingNBeats( + train_to_dataloader_params={"batch_size": 256}, + trainer_params={"max_epochs": 1}, + model_params={ + "stack_types": ["trend", "seasonality"], # One of the following values: “generic”, “seasonality” or “trend”. + "num_blocks" : [2,2], # The number of blocks per stack. + "context_length": CONTEXT_LENGTH, # lookback period + "expansion_coefficient_lengths" : [2, 5], + "learning_rate": 1e-3, + }, + dataset_params={ + + "max_encoder_length": CONTEXT_LENGTH, + "target_normalizer": EncoderNormalizer() + }, +) + +nbeats.fit(y_train.astype(float), fh=fh) +``` + +```{python} +y_pred_nbeats = nbeats.predict(fh=fh, X=X_test) +``` + +```{python} + +from sktime.performance_metrics.forecasting import MeanSquaredScaledError + +metric = MeanSquaredScaledError(multilevel="uniform_average_time") +metric(y_true=y_test, y_pred=y_pred_nbeats, y_train=y_train) +``` + +Agora, podemos visualizar o forecast para uma das séries. Vemos que, mesmo +com poucas épocas de treinamento ou tuning, o N-BEATS já consegue capturar a tendência. + +```{python} +fig, ax = plt.subplots(figsize=(10, 4)) +y_train.loc[10].plot(ax=ax, label="Train") +y_test.loc[10].plot(ax=ax, label="Test") +y_pred_nbeats.loc[10].plot(ax=ax, label="N-BEATS") +fig.show() +``` + +## Zero-shot forecasting com N-BEATS + +Zero-shot forecasting se refere ao fato de fazer previsão para uma série jamais +vista pelo modeo, sem utilizar a série para treinar ou ajustar parâmetros dele. + +Aqui, para simular esse cenário, vamos criar uma nova série temporal combinando duas séries do conjunto de treino. + +```{python} +new_y_train = (y_train.loc[0]**2 + y_train.loc[20]).astype(float) +new_y_test = (y_test.loc[0]**2 + y_test.loc[20]).astype(float) + +# Plotting the new series +fig, ax = plt.subplots(figsize=(10, 4)) +new_y_train["sales"].plot.line(ax=ax, label="New Train") +new_y_test["sales"].plot.line(ax=ax, label="New Test") +fig.show() +``` + +Na interface atual do sktime, usamos o argumento `y` do método `predict` para passar a nova série temporal para o modelo: + +```{python} +y_pred_zeroshot = nbeats.predict(fh=fh, y=new_y_train) +``` + +```{python} +fig, ax = plt.subplots(figsize=(10, 4)) +new_y_train["sales"].plot.line(ax=ax, label="New Train") +new_y_test["sales"].plot.line(ax=ax, label="New Test") +y_pred_zeroshot["sales"].plot.line(ax=ax, label="N-BEATS Zero-shot") +plt.legend() +plt.show() +``` diff --git a/book/content/pt/part2/feature_engineering.qmd b/book/content/pt/part2/feature_engineering.qmd deleted file mode 100644 index d64e087..0000000 --- a/book/content/pt/part2/feature_engineering.qmd +++ /dev/null @@ -1,2 +0,0 @@ -# Engenharia de features - diff --git a/book/content/pt/part2/hierarchical_forecasting.qmd b/book/content/pt/part2/hierarchical_forecasting.qmd new file mode 100644 index 0000000..0694118 --- /dev/null +++ b/book/content/pt/part2/hierarchical_forecasting.qmd @@ -0,0 +1,302 @@ +# Forecasting Hierárquico + +Muitas vezes, não apenas temos múltiplas séries temporais, mas essas séries também estão organizadas em uma hierarquia. Por exemplo, vendas de produtos podem ser organizadas por SKU, categoria, departamento e total da loja. + +Vamos usar o mesmo dataset sintético, mas agora com uma hierarquia de produtos. + +```{mermaid} + +graph TD + root["__total"] + + %% group -1 + root --> g_minus1["-1"] + g_minus1 --> sku20["20"] + g_minus1 --> sku21["21"] + g_minus1 --> sku22["22"] + g_minus1 --> sku23["23"] + g_minus1 --> sku24["24"] + + %% group 0 + root --> g0["0"] + g0 --> sku0["0"] + g0 --> sku1["1"] + g0 --> sku2["2"] + g0 --> sku3["3"] + g0 --> sku4["4"] + + %% group 1 + root --> g1["..."] + + + %% group 3 + root --> g3["3"] + g3 --> sku15["15"] + g3 --> sku16["16"] + g3 --> sku17["17"] + g3 --> sku18["18"] + g3 --> sku19["19"] +``` + + +Ao mesmo tempo que dados hierarárquicos são interessantes pois nos trazem mais informação, eles também trazem desafios adicionais. Imagine que queremos prever as vendas futuras de cada produto. Se fizermos previsões independetes para cada produto, não há garantia que a soma das previsões dos produtos será igual à previsão do total da loja. Isso é chamado de incoerência nas previsões hierárquicas. O processo de ajustar as previsões para garantir coerência é chamado de **reconciliação**. + +## Carregando dados + +Vamos usar os dados sintéticos, agora com sua versao hierárquica. +```{python} +# | echo: false + +import warnings +import pandas as pd +import matplotlib.pyplot as plt + +warnings.filterwarnings("ignore") +``` + +```{python} +from tsbook.datasets.retail import SyntheticRetail + +dataset = SyntheticRetail("hierarchical") +y_train, X_train, y_test, X_test = dataset.load("y_train", "X_train", "y_test", "X_test") +``` + +## Uso de pandas e dados hierárquicos + +Agora, os dataframes possuem mais de 2 ou mais índices, representando a hierarquia. + +```{python} +y_train +``` + +Para obter o número de pontos de série únicos (séries temporais individuais), podemos fazer o seguinte: + +```{python} +y_train.index.droplevel(-1).nunique() +``` + +Note que existem algumas séries com um identificador `__total`. Esse identificador representa o total para aquele nível da hierarquia. Por exemplo, se o id completo é `(-1, "__total")`, isso representa o total do grupo -1. + +```{python} +y_train.loc[(-1, "__total")].head() +``` + +O total de todas as séries é representado por `("__total", "__total")`. + +```{python} +y_train.loc[("__total", "__total")] +``` + +Para contabilizar o número de séries temporais individuais, podemos fazer o seguinte: +```{python} +y_train.index.droplevel(-1).nunique() +``` + +## Previsão sem reconciliação + +Vamos fazer uma previsão e entender o problema da incoerência. + +```{python} +fh = y_test.index.get_level_values(-1).unique() +``` + +```{python} +from tsbook.forecasting.reduction import ReductionForecaster +from lightgbm import LGBMRegressor + +forecaster = ReductionForecaster( + LGBMRegressor(n_estimators=50, verbose=-1, objective="tweedie"), + window_length=30, + normalization_strategy="divide_mean", +) +forecaster.fit(y_train, X=X_train) +y_pred = forecaster.predict(fh, X=X_test) + +``` + +Para somar as previsões de baixo para cima, podemos usar o transformador `Aggregator`. Vamos ver que, +quando somarmos as previsões das séries filhas, o resultado não é igual à previsão da série total. + +```{python} +from sktime.transformations.hierarchical.aggregate import Aggregator + +Aggregator().fit_transform(y_pred) - y_pred +``` + +Existe uma diferença... ou seja, os valores não batem. +Imagine o impacto de levar previsões incoerentes para a tomada de decisão em uma empresa? +A raiz do problema é que temos mais modelos que graus de liberdade. Para ilustrar, suponha que temos 3 séries: $A$, $B$ e $C$, onde: + +$$ +C(t) = A(t) + B(t) +$$ + +Aqui, temos 3 séries, mas apenas 2 graus de liberdade, pois $C$ é completamente determinado por $A$ e $B$. Se fizermos previsões independentes para $A$, $B$ e $C$, não há garantia de que a relação acima será mantida nas previsões. + +## Reconciliação de previsões hierárquicas + +![](img/hierarchical_reconciled_vs_not.png) + +Existem diferentes métodos para reconciliar previsões em séries temporais hierárquicas. Não existe uma solução única, e o melhor método depende dos dados e do contexto. + +## Bottom-up + +A maneira mais simples de reconcialiar previsões hierárquicas é a abordagem **bottom-up**. Nessa abordagem, fazemos previsões apenas para as séries mais baixas na hierarquia (as séries filhas) e depois somamos essas previsões para obter as previsões das séries superiores (as séries pais). + +Hierarchical Bottom-up + +Lados positivos: + +* Simplicidade: fácil de entender e implementar. +* Coerência garantida: a soma das previsões das séries filhas sempre será igual à previsão da série pai. +* Sérias filhas podem capturar detalhes específicos que podem ser perdidos em níveis superiores. + +No entanto, essa abordagem também tem desvantagens: é sucetível ao ruído nas séries filhas, e se as séries filhas tiverem pouca informação, as previsões podem ser ruins. Por exemplo, muitos zeros nas séries de níveis baixos pode levar a previsões ruins a niveis agregados. + +```{python} +from sktime.transformations.hierarchical.reconcile import BottomUpReconciler + +bottom_up = BottomUpReconciler() * forecaster +bottom_up.fit(y_train) + +y_pred_bottomup = bottom_up.predict(fh=fh) +``` + + +Agora vemos que as previsões são coerentes: + +```{python} +Aggregator().fit_transform(y_pred_bottomup) - y_pred_bottomup +``` + +## Top-down (forecast proportions) + +Outra abordagem é a **top-down**. Nessa abordagem, fazemos previsões apenas para as séries superiores na hierarquia (as séries pais) e depois distribuímos essas previsões para as séries filhas com base em proporções previstas. + +Suponha que temos a seguinte hierarquia $C(t) = A(t) + B(t)$. Considere $\hat{C}(t)$, $\hat{A}(t)$ e $\hat{B}(t)$ como as previsões para $C$, $A$ e $B$, respectivamente. Na abordagem top-down, faríamos o seguinte: + +1. Prever $\hat{C}(t)$, $\hat{A}(t)$ e $\hat{B}(t)$ independentemente. +2. Calcular as proporções previstas para os níveis mais baixos: +$$ +p_A(t) = \frac{\hat{A}(t)}{\hat{A}(t) + \hat{B}(t)} +$$ + +$$ +p_B(t) = \frac{\hat{B}(t)}{\hat{A}(t) + \hat{B}(t)} +$$ + +3. Distribuir a previsão de $C$ para $A$ e $B$ usando essas proporções: +$$ +\tilde{A}(t) = p_A(t) \cdot \hat{C}(t) +$$ + +$$ +\tilde{B}(t) = p_B(t) \cdot \hat{C}(t) +$$ + +Essa abordagem é capaz de usufruir da qualidade do forecast total, e ainda consegue distribuir para as séries filhas baseadas no histórico. + +Topdown Forecast + + +O que chamam de "Proporções históricas" é equivalente a esse método, mas com um modelo Naive para prever as proporções. + +Esse método pode ser bom quando o forecast total é de boa qualidade. No entanto, +dependemos profundamente da qualidade do forecast total e das proporções. + +```{python} +from sktime.transformations.hierarchical.reconcile import TopdownReconciler + +top_down_fcst = TopdownReconciler() * forecaster +top_down_fcst.fit(y_train) + +y_pred_topdown = top_down_fcst.predict(fh=fh) +``` + +## Reconciliação ótima + +Existe uma abordagem mais sofisticada, com uma intuição geométrica interessante. +A ideia é ajustar as previsões iniciais para que elas satisfaçam as restrições de soma da hierarquia. Por exemplo, para a hierarquia $C(t) = A(t) + B(t)$, queremos garantir que: + +$$ +\hat{C}(t) = \hat{A}(t) + \hat{B}(t) +$$ + +Se consideramos nosso espaço 3D de observações $(\hat{A}, \hat{B}, \hat{C})$, a +condição acima é satisfeita para um plano 2D nesse universo. + + +![](img/coherent_plane.png) + + +Podemos então projetar nossas previsões iniciais nesse plano para obter previsões coerentes. Essa projeção pode ser feita de várias maneiras, levando a diferentes métodos de reconciliação ótima. Os métodos levam o nome "OLS" pois a projeção é feita minimizando o erro quadrático (Ordinary Least Squares). + +* **OLS** : projetar ortogonalmente todas as previsões base na espaço de reconciliação, tratando todas as séries igualmente. +* **Weighted OLS**: projetar obliquamente, ou seja, considerando pesos diferentes para cada série, permitindo dar mais importância a certas séries na reconciliação. A projeção não faz mais uma perpendicular, mas sim uma oblíqua. +* **Minimum trace (MinT)**: use a matriz de covariância do erro para encontrar as previsões reconciliadas ótimas. Chamado de "ótimo". + + +Para a reconciliação ótima com OLS, podemos usar o `OptimalReconciler` do sktime: + +```{python} +from sktime.transformations.hierarchical.reconcile import OptimalReconciler + +optimal = OptimalReconciler("ols") * forecaster +optimal.fit(y_train) +y_pred_optimal = optimal.predict(fh=fh) +``` + + +```{python} +# | code-fold: true +from sktime.utils.plotting import plot_series +import matplotlib.pyplot as plt +import pandas as pd + + +idx = y_train.index.droplevel(-1).unique()[10] + +plot_series( + y_train.loc[idx,], + y_test.loc[idx,], + y_pred.loc[idx,], + y_pred_optimal.loc[idx,], + labels=["Train", "Test", "Predicted (sem reconciliação)", "Predicted (ótimo)"], +) +plt.xlim(pd.to_datetime("2024-05-01"), None) +plt.show() +``` + +Para reconciliações ótimas (que usam a covariância do erro), podemos usar o `ReconcilerForecaster` do sktime, que internamente já faz o cálculo da covariância do erro: + +```{python} +from sktime.forecasting.reconcile import ReconcilerForecaster + + +mint_forecaster = ReconcilerForecaster( + forecaster=forecaster, + method="mint_shrink") + +mint_forecaster.fit(y_train) +y_pred_mint = mint_forecaster.predict(fh=fh) +``` + +## Comparando resultados + +```{python} +from sktime.performance_metrics.forecasting import MeanSquaredScaledError + +metric = MeanSquaredScaledError(multilevel="uniform_average_time") + +pd.DataFrame( + { + "Baseline": metric(y_test, y_pred, y_train=y_train), + "BottomUpReconciler": metric(y_test, y_pred_bottomup, y_train=y_train), + "TopDownReconciler": metric(y_test, y_pred_topdown, y_train=y_train), + "OptimalReconciler (ols)": metric(y_test, y_pred_optimal, y_train=y_train), + "Mint Reconciler": metric(y_test, y_pred_mint, y_train=y_train), + }, + index=["Mean Absolute Scaled Error"], +) +``` + diff --git a/book/content/pt/part2/img/coherent_plane.png b/book/content/pt/part2/img/coherent_plane.png new file mode 100644 index 0000000..836d394 Binary files /dev/null and b/book/content/pt/part2/img/coherent_plane.png differ diff --git a/book/content/pt/part2/img/global_reduction.png b/book/content/pt/part2/img/global_reduction.png new file mode 100644 index 0000000..7fbe4c6 Binary files /dev/null and b/book/content/pt/part2/img/global_reduction.png differ diff --git a/book/content/pt/part2/img/hierarchical_bottomup.png b/book/content/pt/part2/img/hierarchical_bottomup.png new file mode 100644 index 0000000..472dc40 Binary files /dev/null and b/book/content/pt/part2/img/hierarchical_bottomup.png differ diff --git a/book/content/pt/part2/img/hierarchical_reconciled_vs_not.png b/book/content/pt/part2/img/hierarchical_reconciled_vs_not.png new file mode 100644 index 0000000..4723676 Binary files /dev/null and b/book/content/pt/part2/img/hierarchical_reconciled_vs_not.png differ diff --git a/book/content/pt/part2/img/hierarchical_td_fcst.png b/book/content/pt/part2/img/hierarchical_td_fcst.png new file mode 100644 index 0000000..60f44fb Binary files /dev/null and b/book/content/pt/part2/img/hierarchical_td_fcst.png differ diff --git a/book/content/pt/part2/img/hierarchical_topdown.png b/book/content/pt/part2/img/hierarchical_topdown.png new file mode 100644 index 0000000..bb0348d Binary files /dev/null and b/book/content/pt/part2/img/hierarchical_topdown.png differ diff --git a/book/content/pt/part2/img/nbeats_simplified.png b/book/content/pt/part2/img/nbeats_simplified.png new file mode 100644 index 0000000..aeac0a4 Binary files /dev/null and b/book/content/pt/part2/img/nbeats_simplified.png differ diff --git a/book/content/pt/part2/img/reduction.png b/book/content/pt/part2/img/reduction.png new file mode 100644 index 0000000..d672f20 Binary files /dev/null and b/book/content/pt/part2/img/reduction.png differ diff --git a/book/content/pt/part2/ml_models.qmd b/book/content/pt/part2/ml_models.qmd index 7178410..feb8193 100644 --- a/book/content/pt/part2/ml_models.qmd +++ b/book/content/pt/part2/ml_models.qmd @@ -1,6 +1,33 @@ # Modelos de Machine Learning +Nesse capítulo vamos ver as maneiras de usar modelos de Machine Learning para +forecasting. Aqui é onde mais acontecem erros de novos praticantes, pois +muitas vezes tentam aplicar modelos de ML diretamente na série temporal. + + +Para usar um modelo de ML, precisamos transformar a série temporal em um problema de regressão tradicional. Isso é feito criando janelas deslizantes (sliding windows) da série temporal, onde cada janela é usada como uma amostra de treinamento para o modelo de ML. + +Ou seja, se temos uma série temporal $(y_t)$, podemos criar janelas de tamanho $n$ e usar os valores $y_{t-n}, y_{t-n+1}, \ldots, y_{t-1}$ como características (features) para prever o valor $y_t$. + +![img/reduction.png](img/reduction.png) + + +Para prever mais de um passo à frente, existem duas abordagens: + +1. **Previsão recursive**: se queremos prever $h$ passos à frente, podemos usar o modelo para prever $y_{t+1}$, depois usar essa previsão para prever $y_{t+2}$, e assim por diante, até $y_{t+h}$. Isso pode levar a erros acumulados, pois cada previsão depende das previsões anteriores. +2. **Previsão direta**: em vez de prever um passo de cada vez, podemos treinar o modelo para prever todos os $h$ passos à frente de uma vez. Isso pode ser feito usando um modelo para cada $h$ ou usando um modelo que prevê um vetor de $h$ valores. + +A verdade é que as duas abordagens podem ser vistas como uma: a previsão recursiva pode ser vista como uma previsão direta para $h=1$. + ```{python} +# | echo: false + +import warnings +warnings.filterwarnings("ignore") +``` + +```{python} +# | code-fold: true from tsbook.datasets.retail import SyntheticRetail from sktime.utils.plotting import plot_series from sktime.forecasting.naive import NaiveForecaster @@ -9,40 +36,150 @@ dataset = SyntheticRetail("univariate") y_train, X_train, y_test, X_test = dataset.load( "y_train", "X_train", "y_test", "X_test" ) +``` + +## O problema da tendência + +A tendência em séries temporais é como um constante problema de data drift: + +```{python} +# | code-fold: true +_X = [y_train.iloc[i : i + 7] for i in range(0, 700)] + +_X_test = [y_train.iloc[i : i + 7] for i in range(700, 800)] + + +def set_index(x): + x.index = range(len(x)) + return x -X_train + +_X = [set_index(x) for x in _X] +_X_test = [set_index(x) for x in _X_test] + +import matplotlib.pyplot as plt + +fig, ax = plt.subplots() +for x in _X: + ax.plot(x, color="gray", alpha=0.3) +for x in _X_test: + ax.plot(x, color="red", alpha=0.3) + +# Add legend, with 1 red line for test and 1 gray for train +from matplotlib.lines import Line2D + +legend_handles = [ + Line2D([0], [0], color="gray", alpha=0.3, lw=2, label="Treino"), + Line2D([0], [0], color="red", alpha=0.3, lw=2, label="Teste"), +] +ax.legend(handles=legend_handles, loc="best") +ax.set_title("Série original - Magnitudes diferentes para cada janela") +fig.show() ``` +Quando criamos nossas janelas e olhamos treine e teste, esse problema fica claro. A informação de uma série em treino não é util para prever a série de teste, pois elas estão em magnitudes diferentes. + +Uma possível solução para isso é normalizar cada janela, dividindo pelo valor médio da janela. Assim, todas as janelas ficam na mesma escala: ```{python} -from tsbook.forecasting.global_reduction import GlobalReductionForecaster +# | code-fold: true + +_X = [x / x.mean() for x in _X] +_X_test = [x / x.mean() for x in _X_test] + +fig, ax = plt.subplots() +for x in _X: + ax.plot(x, color="gray", alpha=0.3) +for x in _X_test: + ax.plot(x, color="red", alpha=0.3) + +ax.legend(handles=legend_handles, loc="best") +ax.set_title("Série normalizada") +fig.show() + +``` + +e podemos prever sem problemas. + +Outra possibilidade é a diferenciação, como já vimos em capítulos anteriores. A diferenciação remove a tendência da série, tornando-a estacionária. + +## Usando modelos de ML com sktime + +Primeiro, vamos import `ReductionForecaster`, que é a classe que implementa a abordagem de janelas deslizantes para usar modelos de ML em séries temporais. Vamos testar um primeiro caso sem nenhum tipo de preprocessamento, apenas criando as janelas: + +```{python} +from tsbook.forecasting.reduction import ReductionForecaster from sklearn.ensemble import RandomForestRegressor + +model = ReductionForecaster( + RandomForestRegressor(n_estimators=100, random_state=42), + window_length=30, +) + +model.fit(y_train, X=X_train) +``` + +```{python} +y_pred = model.predict(fh=y_test.index, X=X_test) +plot_series(y_train, y_test, y_pred, labels=["Treino", "Teste", "Previsão com ML"]) +plt.show() +``` + +Claramente, tivemos o problema que mencionamos anteriormente. + + +### Solução 1: Diferenciação + +Uma solução é usar a diferenciação para remover a tendência da série. + +```{python} from sktime.transformations.series.difference import Differencer regressor = RandomForestRegressor(n_estimators=100, random_state=42) -model = Differencer() * GlobalReductionForecaster( + +model = Differencer() * ReductionForecaster( regressor, window_length=30, steps_ahead=1, ) model.fit(y_train, X=X_train) -y_pred = model.predict(fh=y_test.index, X=X_test) ``` ```{python} +y_pred_diff = model.predict(fh=y_test.index, X=X_test) + plot_series( - y_train, y_test, y_pred, labels=["Treino", "Teste", "Previsão com ML + Diferença"] + y_train, y_test, y_pred_diff, labels=["Treino", "Teste", "Previsão com ML + Diferença"] ) +plt.show() ``` +Aqui, já vemos uma melhora significativa. Mas tem algo que podemos melhorar para realizar a diferenciação? Sim. Lembre que essa série tem um padrão multiplicativo. Então, antes de aplicar a diferenciação, podemos aplicar uma transformação logarítmica para estabilizar a variância: ```{python} +from sktime.transformations.series.boxcox import LogTransformer + +model_log = LogTransformer() * model + +model_log.fit(y_train, X=X_train) +y_pred_log_diff = model_log.predict(fh=y_test.index, X=X_test) +plot_series( + y_train, + y_test, + y_pred_log_diff, + labels=["Treino", "Teste", "Previsão com ML + Log + Diferença"], +) -import numpy as np -from typing import Callable, Tuple +``` + +### Solução 2: Normalização por janela + +A diferenciação aumenta o ruído da série, o que pode dificultar o trabalho do modelo de ML. Outra opção é normalizar em cada janela. A classe `ReductionForecaster` tem um parâmetro chamado `normalization_strategy`, que pode ser usado para determinar a estratégia de normalização. Vamos usar a estratégia `divide_mean`, que divide cada janela pelo seu valor médio. -model = GlobalReductionForecaster( +```{python} + +model = ReductionForecaster( regressor, window_length=30, steps_ahead=1, @@ -50,23 +187,23 @@ model = GlobalReductionForecaster( ) model.fit(y_train, X=X_train) -y_pred = model.predict(fh=y_test.index, X=X_test) +y_pred_norm = model.predict(fh=y_test.index, X=X_test) -``` - -```{python} plot_series( - y_train, y_test, y_pred, labels=["Treino", "Teste", "Previsão com ML + Diferença + Normalização"] + y_train, y_test, y_pred_norm, labels=["Treino", "Teste", "Previsão com ML + Normalização"] ) ``` + +### Modelo direto e recursivo + +Podemos fazer um conjunto de modelos, um para cada passo à frente. Abaixo, definimos `steps_ahead=12`, o que significa que o modelo vai prever 12 passos à frente diretamente. + ```{python} -from tsbook.forecasting.global_reduction import GlobalReductionForecaster -from typing import Optional -model = GlobalReductionForecaster( +model = ReductionForecaster( regressor, window_length=30, steps_ahead=12, @@ -74,7 +211,7 @@ model = GlobalReductionForecaster( ) model.fit(y_train, X=X_train) -y_pred = model.predict(fh=y_test.index, X=X_test) +y_pred_norm_direct = model.predict(fh=y_test.index, X=X_test) ``` @@ -82,7 +219,39 @@ y_pred = model.predict(fh=y_test.index, X=X_test) plot_series( y_train, y_test, - y_pred, - labels=["Treino", "Teste", "Previsão com ML + Diferença + Normalização"], + y_pred_norm_direct, + labels=["Treino", "Teste", "Previsão com ML + Normalização"], ) -``` \ No newline at end of file +``` + +Podemos comparar o MAPE de todos os modelos: + +```{python} +from sktime.performance_metrics.forecasting import MeanAbsolutePercentageError + +mape = MeanAbsolutePercentageError() + +results = {} +for _y_pred, label in zip( + [ + y_pred, + y_pred_diff, + y_pred_log_diff, + y_pred_norm, + y_pred_norm_direct, + ], + [ + "ML", + "ML + Diferença", + "ML + Log + Diferença", + "ML + Normalização", + "ML + Normalização + Direto", + ], +): + results[label] = mape(y_test, _y_pred) + +import pandas as pd + +results = pd.DataFrame.from_dict(results, orient="index", columns=["MAPE"]) +results.sort_values("MAPE") +``` diff --git a/book/content/pt/part2/panel_data.qmd b/book/content/pt/part2/panel_data.qmd new file mode 100644 index 0000000..ababee0 --- /dev/null +++ b/book/content/pt/part2/panel_data.qmd @@ -0,0 +1,462 @@ +# Dados em painel (multi-series) + +Em muitas aplicações, não temos acesso a uma única série temporal, mas sim a um conjunto de séries temporais relacionadas. Isso é comum em cenários como vendas de produtos em diferentes lojas, consumo de energia em diferentes regiões, etc. Esses dados são chamados de dados em painel. + +Uma ideia poderosa é aproveitar a similaridade entre as séries para melhorar as previsões. Chamamos de **modelos globais** os modelos capazes de aprender padrões comuns entre as séries, ao contrário dos **modelos locais** que aprendem apenas com uma única série. + +A maioria dos modelos clássicos de séries temporais são locais. Modelos globais são, em geral, baseados em modelos tabulares de ML ou deep learning. Segundo competições de séries temporais, como a M5, em forecasts de painel os modelos globais são os que apresentam melhor desempenho [@makridakis2022m5]. + + +## Acessando os dados + +Aqui, vamos usar o dataset sintético que vimos antes, mas agora teremos acesso às várias séries temporais que compõe o total. + +Esse dataset é feito para simular um caso de varejo, onde temos vendas diárias de vários produtos: + +```{python} +# | echo: false +import warnings + +warnings.filterwarnings("ignore") +``` + +```{python} +# | code-fold: true +import pandas as pd +import matplotlib.pyplot as plt + +from sktime.utils.plotting import plot_series + +``` + +```{python} +from tsbook.datasets.retail import SyntheticRetail +dataset = SyntheticRetail("panel") +y_train, X_train, y_test, X_test = dataset.load( + "y_train", "X_train", "y_test", "X_test" +) +``` + +Note que, para dados em painel, os dataframes possuem mais um nível de índice, que identifica a série temporal a que cada observação pertence: + +```{python} +display(X_train) +``` + +Podemos visualizar algumas séries. Vemos que há mais zeros nesse dataset, em comparação +ao que usamos antes. + +```{python} +from sktime.utils.plotting import plot_series + +fig, ax = plt.subplots(figsize=(10, 4)) +y_train.unstack(level=0).droplevel(0, axis=1).iloc[:, [0,10]].plot(ax=ax, alpha=0.7) +plt.show() +``` + +### Pandas e multi-índices + +Para trabalhar com essas estruturas de dados, é importante revisar algumas operações do pandas. + +```{python} +y_train.index.get_level_values(-1) +``` + +As seguintes operações são bem úteis para trabalhar com multi-índices: + +```{python} +y_train.index +``` + +Acessar valores únicos no primeiro nivel (nível 0, mais à esquerda): +```{python} +y_train.index.get_level_values(0).unique() +``` + +Selecionar uma série específica (nível 0 igual a 0): +```{python} +y_train.loc[0] +``` + +Aqui, podemos usar `pd.IndexSlice` para selecionar várias séries ao mesmo tempo. +Note que pd.IndexSlice é passado diretamente para `.loc`: +```{python} +y_train.loc[pd.IndexSlice[[0,2], :]] +``` + +Agora, para selecionar o horizonte de forecasting, temos que chamar `unique`: + +```{python} +fh = y_test.index.get_level_values(1).unique() + +fh +``` + +## Upcasting automático + +Nem todos modelos suportam nativamente dados em painel. Por exemplo, exponential smoothing. +Aqui, temos uma boa notícia: sem linhas extras necessárias. O sktime faz *upcasting* automático para dados em painel ao usar estimadores do `sktime`. + +```{python} +from sktime.forecasting.naive import NaiveForecaster + + +naive_forecaster = NaiveForecaster(strategy="last", window_length=1) +naive_forecaster.fit(y_train) +y_pred_naive = naive_forecaster.predict(fh=fh) + +y_pred_naive +``` + +Internamente, o `sktime` cria um clone do estimador para cada série nos dados em painel. +Em seguida, cada clone é treinado com a série correspondente. Isso é feito de +forma transparente para usuário, mas sem exigir esforço. + +O atributo `forecasters_` armazena um DataFrame com os estimatores de cada série. + +```{python} +naive_forecaster.forecasters_.head() +``` + +É dificil explicar o quanto isso é extremamente útil para código limpo e prototipagem rápida. +Foi um dos motivos que me levaram a usar o `sktime`. + + +## Métricas + +Agora que temos várias séries, precisamos explicar como calcular métricas de avaliação. +O sktime oferece duas opções para isso, como argumentos na criação da métrica: + +* `multilevel="uniform_average_time"` para calcular a média das séries temporais no painel. +* `multilevel="raw_values"` para obter o erro por série. + + +```{python} +from sktime.performance_metrics.forecasting import MeanSquaredScaledError + +metric = MeanSquaredScaledError(multilevel="uniform_average_time") +``` + +```{python} +metric(y_true=y_test, y_pred=y_pred_naive, y_train=y_train) +``` + +Na prática, as métricas que a sua aplicação exige podem ser diferentes. Por exemplo, +as séries temporais podem ter diferentes importâncias, e você pode querer ponderar +as métricas de acordo. + +Para isso, é possível criar uma métrica customizada no sktime, mas não entraremos +nesse mérito aqui. + +## Modelos globais de Machine Learning + +Quando vimos como usar modelos de Machine Learning para forecasting, já mencionamos +como é necessário traduzir o problema de séries temporais para um problema de regressão tradicional. + +No caso de dados em painel, também podemos usar essa abordagem, mas agora aproveitando +todas as séries temporais para treinar um único modelo global. + +![](img/global_reduction.png) + +Abaixo, vamos comparar um LightGBM global com um local. Veremos o seguinte: o modelo local é **melhor** que o modelo global, **se não processarmos os dados** corretamente para o modelo global aproveitar as **similaridades** entre as séries! + +```{python} +from tsbook.forecasting.reduction import ReductionForecaster +from lightgbm import LGBMRegressor + +global_forecaster1 = ReductionForecaster( + LGBMRegressor(n_estimators=100, verbose=-1), + window_length=30, +) + +global_forecaster1.fit(y_train, X_train) +``` + +```{python} +y_pred_global1 = global_forecaster1.predict(fh=fh, X=X_test) +``` + +```{python} +fig, ax = plt.subplots(figsize=(10, 4)) +y_train.loc[10, "sales"].plot(ax=ax, label="Treino") +y_test.loc[10, "sales"].plot(ax=ax, label="Teste") +y_pred_global1.loc[10, "sales"].plot(ax=ax, label="Global 1") +plt.legend() +plt.show() +``` + + +Para forçar que um modelo global funcione como um modelo local, podemos usar `ForecastByLevel`, que cria um modelo separado para cada série temporal, mesmo quando o estimador suporta dados em painel. + + +```{python} +from sktime.forecasting.compose import ForecastByLevel + +local_forecaster1 = ForecastByLevel(global_forecaster1, groupby="local") + +local_forecaster1.fit(y_train, X=X_train) +``` + +```{python} +y_pred_local1 = local_forecaster1.predict(fh=fh, X=X_test) + +err_global1 = metric(y_true=y_test, y_pred=y_pred_global1, y_train=y_train) +err_local1 = metric(y_true=y_test, y_pred=y_pred_local1, y_train=y_train) + +errors = pd.DataFrame( + { + "Global (1)": [err_global1], + "Local (1)": [err_local1], + }, + index=["MSE"], +) +``` + +## Preprocessamento e engenharia de features + +Sabemos como preprocessar séries temporais univariadas para melhorar o desempenho dos modelos de ML. Aplicamos da mesma maneira que fizemos anteriormente o `Differencer`, com objetivo de remover tendências. + +```{python} +from sktime.transformations.series.difference import Differencer + + +global_forecaster2 = Differencer() * global_forecaster1 +global_forecaster2.fit(y_train, X_train) +``` + +```{python} +y_pred_global2 = global_forecaster2.predict(fh=fh, X=X_test) +metric_global2 = metric(y_true=y_test, y_pred=y_pred_global2, y_train=y_train) +``` + +E agora sua versão local: + +```{python} + +local_forecaster2 = ForecastByLevel(global_forecaster2, groupby="local") +local_forecaster2.fit(y_train, X=X_train) + +y_pred_local2 = local_forecaster2.predict(fh=fh, X=X_test) +metric_local2 = metric(y_true=y_test, y_pred=y_pred_local2, y_train=y_train) +``` + +Agora, podemos comparar: + +```{python} +errors["Global (2)"] = metric_global2 +errors["Local (2)"] = metric_local2 + +errors +``` + +Note como já superamos o modelo global incial, e o modelo local. Isso é para +destacar que é **essencial** realizar um bom preprocessamento e engenharia de features para que modelos de Machine Learning tenham bom desempenho em dados em painel. + +### Normalização por janela + +Agora, vamos usar a normalização por janela, que é especialmente útil em dados em painel, onde as séries podem ter diferentes escalas. + +```{python} + +global_forecaster3 = global_forecaster1.clone().set_params( + normalization_strategy="divide_mean" +) + +global_forecaster3.fit(y_train, X_train) +``` + +```{python} +# Predict +y_pred_global3 = global_forecaster3.predict(fh=fh, X=X_test) + +# Métrica +metric_global3 = metric(y_true=y_test, y_pred=y_pred_global3, y_train=y_train) + +errors["Global 3 (window norm)"] = metric_global3 + +display(errors) +``` + +Vemos que resultados são ainda melhores! + + +### Pipelines de features exógenas + +Podemos ajudar o modelo a capturar sazonalidades adicionando features de Fourier como features exógenas. + +Usamos `**` para criar um pipeline aplicado sobre as features exógenas: + +```{python} +from sktime.transformations.series.fourier import FourierFeatures + +fourier_features = FourierFeatures( + sp_list=[365.25, 365.25 / 12], fourier_terms_list=[1, 1], freq="D" +) + +global_forecaster4 = fourier_features**global_forecaster3 +global_forecaster4.fit(y_train, X_train) +``` + +```{python} +y_pred_global4 = global_forecaster4.predict(fh=fh, X=X_test) +metric_global4 = metric(y_true=y_test, y_pred=y_pred_global4, y_train=y_train) + +errors["Global 4 (fourier)"] = metric_global4 +errors +``` + +## Agrupamento + Modelos globais + +Uma técnica muito adotada é fazer modelos globais por clusters de séries temporais similares. + +Uma maneira de categorizar é usando ADI (Average Demand Interval) e CV² (squared Coefficient of Variation). O componente ADI é calculado como o percentual de períodos com demanda (y>0), e $CV^2$ é o quadrado do coeficiente de variação das demandas positivas. + +| **Categoria** | **ADI** | **CV²** | **Padrão típico** | **Exemplos** | +|:--------------|:--------|:--------|:------------------|:-------------| +| **Suave (Smooth)** | ≤ 1,32 | ≤ 0,49 | Demanda contínua e estável | Itens de consumo diário, alimentos | +| **Errática (Erratic)** | ≤ 1,32 | > 0,49 | Demanda contínua, porém muito variável | Moda, eletrônicos | +| **Intermitente (Intermittent)** | > 1,32 | ≤ 0,49 | Muitos períodos sem venda, mas valores estáveis quando ocorre | Peças de reposição, ferramentas | +| **Irregular (Lumpy)** | > 1,32 | > 0,49 | Muitos períodos com zero e valores muito variáveis quando há demanda | Equipamentos caros, sobressalentes grandes | + + + +```{python} +# | echo: false +import numpy as np +import matplotlib.pyplot as plt + +rng = np.random.default_rng(42) + + +def compute_adi(y): + nnz = np.count_nonzero(y > 0) + return len(y) / nnz if nnz > 0 else np.inf + + +def compute_cv2(y): + pos = y[y > 0] + if len(pos) <= 1: + return np.inf + m = pos.mean() + s = pos.std(ddof=1) + return (s / m) ** 2 if m > 0 else np.inf + + +def plot_category(ax, series_list, title): + x = np.arange(len(series_list[0])) + for y in series_list: + ax.plot(x, y, linewidth=2) + adis = [compute_adi(y) for y in series_list] + cv2s = [compute_cv2(y) for y in series_list] + ax.text( + 0.02, + 0.98, + f"avg ADI={np.mean(adis):.2f}\navg CV²={np.mean(cv2s):.2f}", + transform=ax.transAxes, + va="top", + ha="left", + bbox=dict(boxstyle="round,pad=0.3", fc="white", ec="0.5", alpha=0.9), + ) + ax.set_title(title, fontsize=12) + ax.set_xlabel("time") + ax.set_ylabel("demand") + ax.grid(True, alpha=0.3) + + +N = 60 + + +def gen_smooth(n=3): + out = [] + for _ in range(n): + base = 20 + 2 * np.sin(np.linspace(0, 3 * np.pi, N)) + noise = rng.normal(0, 2, N) + out.append(np.clip(base + noise, 0, None)) + return out + + +def gen_erratic(n=3): + out = [] + for _ in range(n): + base = 20 + 2 * np.sin(np.linspace(0, 2 * np.pi, N)) + noise = rng.normal(0, 9, N) + out.append(np.clip(base + noise, 0, None)) + return out + + +def gen_intermittent(n=3): + out = [] + for _ in range(n): + mask = rng.binomial(1, 0.35, N) + values = 18 + rng.normal(0, 2, N) + out.append(np.clip(mask * values, 0, None)) + return out + + +def gen_lumpy(n=3): + out = [] + for _ in range(n): + mask = rng.binomial(1, 0.25, N) + values = rng.gamma(shape=2.0, scale=10.0, size=N) + out.append(np.clip(mask * values, 0, None)) + return out + + +cats = [ + ("Smooth (low ADI, low CV²)", gen_smooth()), + ("Erratic (low ADI, high CV²)", gen_erratic()), + ("Intermittent (high ADI, low CV²)", gen_intermittent()), + ("Lumpy (high ADI, high CV²)", gen_lumpy()), +] + +fig, axes = plt.subplots(2, 2, figsize=(10, 6)) +for ax, (title, series) in zip(axes.ravel(), cats): + plot_category(ax, series, title) + +plt.tight_layout() +plt.show() +``` + + +```{python} + + +from sktime.forecasting.compose import GroupbyCategoryForecaster +from sktime.transformations.series.adi_cv import ADICVTransformer + +# TODO: customize yours! +group_forecaster = GroupbyCategoryForecaster( + forecasters = + {"smooth": global_forecaster3.clone(), + "erratic": global_forecaster3.clone(), + "intermittent": global_forecaster3.clone(), + "lumpy": global_forecaster3.clone(), + }, + transformer=ADICVTransformer(features=["class"],)) + + +group_forecaster.fit(y_train, X_train) + +``` + + +```{python} + +y_pred_group = group_forecaster.predict(fh=fh, X=X_test) +metric_group = metric(y_true=y_test, y_pred=y_pred_group, y_train=y_train) + +metric_group +``` + +Although it did not perform better than the best global model in this synthetic example, in real-world scenarios this approach can be very effective, particularly when there are large samples for each category, allowing the model to learn specific patterns for each group of series. + +In addition, this can be useful when there are computation constraints, as training multiple smaller models for each cluster can be more efficient than training a single large global model. + + +## Resumo + +Aqui, vimos como trabalhar com dados em painel (multi-series) usando o sktime. Vimos, especialmente, como criar modelos globais de Machine Learning que aproveitam as similaridades entre as séries para melhorar o desempenho das previsões. Também destacamos a importância do preprocessamento e da engenharia de features para obter bons resultados com esses modelos. + +Para uma análise mais aprofundada, recomendo o artigo de [@montero2021principles], que discute princípios para forecasting em dados em painel usando modelos globais de Machine Learning. + + diff --git a/book/content/pt/part2/probabilistic_forecasting.qmd b/book/content/pt/part2/probabilistic_forecasting.qmd deleted file mode 100644 index 8ff2a03..0000000 --- a/book/content/pt/part2/probabilistic_forecasting.qmd +++ /dev/null @@ -1,2 +0,0 @@ -# Forecast probabilístico - diff --git a/book/content/pt/part2/probabilistic_metrics.qmd b/book/content/pt/part2/probabilistic_metrics.qmd deleted file mode 100644 index 599cbff..0000000 --- a/book/content/pt/part2/probabilistic_metrics.qmd +++ /dev/null @@ -1,2 +0,0 @@ -# Métricas probabilísticas - diff --git a/book/content/pt/part3/deep_learning.qmd b/book/content/pt/part3/deep_learning.qmd deleted file mode 100644 index 4afeca8..0000000 --- a/book/content/pt/part3/deep_learning.qmd +++ /dev/null @@ -1 +0,0 @@ -# Deep learning \ No newline at end of file diff --git a/book/content/pt/part3/hierarchical_forecasting.qmd b/book/content/pt/part3/hierarchical_forecasting.qmd deleted file mode 100644 index 84b37d6..0000000 --- a/book/content/pt/part3/hierarchical_forecasting.qmd +++ /dev/null @@ -1 +0,0 @@ -# Séries hierarquicas \ No newline at end of file diff --git a/book/content/pt/part3/panel_data.qmd b/book/content/pt/part3/panel_data.qmd deleted file mode 100644 index 2e2be5c..0000000 --- a/book/content/pt/part3/panel_data.qmd +++ /dev/null @@ -1 +0,0 @@ -# Dados em Painel e modelos globais \ No newline at end of file diff --git a/book/content/pt/part4/sktime_custom.qmd b/book/content/pt/part4/sktime_custom.qmd deleted file mode 100644 index b5a1c5a..0000000 --- a/book/content/pt/part4/sktime_custom.qmd +++ /dev/null @@ -1 +0,0 @@ -# Criando modelos customizados com sktime \ No newline at end of file diff --git a/book/cover.png b/book/cover.png deleted file mode 100644 index e1f5bc6..0000000 Binary files a/book/cover.png and /dev/null differ diff --git a/book/index.qmd b/book/index.qmd index 2062215..5b3e68a 100644 --- a/book/index.qmd +++ b/book/index.qmd @@ -1,5 +1,7 @@ -# Preface {.unnumbered} +# Prefácio {.unnumbered} -This is a Quarto book. +Esse é um pequeno livro feito para o workshop de sktime na Python Brasil 2025. O objetivo é apresentar os conceitos básicos de séries temporais e como usar o sktime para modelagem e previsão. -To learn more about Quarto books visit . +Esse livro é raso em diversos aspectos, mas pode ser útil como ponto de partida para quem quer aprender mais sobre séries temporais em Python. + +Caso queira contribuir para sktime, visite o [repositório oficial no GitHub](https://github.com/sktime/sktime) e participe do nosso [Discord](https://discord.com/invite/54ACzaFsn7). diff --git a/book/intro.qmd b/book/intro.qmd deleted file mode 100644 index 3d07efe..0000000 --- a/book/intro.qmd +++ /dev/null @@ -1,2 +0,0 @@ -# Introduction - diff --git a/book/poetry.lock b/book/poetry.lock deleted file mode 100644 index 6ce369e..0000000 --- a/book/poetry.lock +++ /dev/null @@ -1,3324 +0,0 @@ -# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. - -[[package]] -name = "anyio" -version = "4.11.0" -description = "High-level concurrency and networking framework on top of asyncio or Trio" -optional = false -python-versions = ">=3.9" -files = [ - {file = "anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc"}, - {file = "anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4"}, -] - -[package.dependencies] -idna = ">=2.8" -sniffio = ">=1.1" -typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} - -[package.extras] -trio = ["trio (>=0.31.0)"] - -[[package]] -name = "appnope" -version = "0.1.4" -description = "Disable App Nap on macOS >= 10.9" -optional = false -python-versions = ">=3.6" -files = [ - {file = "appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c"}, - {file = "appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee"}, -] - -[[package]] -name = "argon2-cffi" -version = "25.1.0" -description = "Argon2 for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "argon2_cffi-25.1.0-py3-none-any.whl", hash = "sha256:fdc8b074db390fccb6eb4a3604ae7231f219aa669a2652e0f20e16ba513d5741"}, - {file = "argon2_cffi-25.1.0.tar.gz", hash = "sha256:694ae5cc8a42f4c4e2bf2ca0e64e51e23a040c6a517a85074683d3959e1346c1"}, -] - -[package.dependencies] -argon2-cffi-bindings = "*" - -[[package]] -name = "argon2-cffi-bindings" -version = "25.1.0" -description = "Low-level CFFI bindings for Argon2" -optional = false -python-versions = ">=3.9" -files = [ - {file = "argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:3d3f05610594151994ca9ccb3c771115bdb4daef161976a266f0dd8aa9996b8f"}, - {file = "argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8b8efee945193e667a396cbc7b4fb7d357297d6234d30a489905d96caabde56b"}, - {file = "argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3c6702abc36bf3ccba3f802b799505def420a1b7039862014a65db3205967f5a"}, - {file = "argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1c70058c6ab1e352304ac7e3b52554daadacd8d453c1752e547c76e9c99ac44"}, - {file = "argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e2fd3bfbff3c5d74fef31a722f729bf93500910db650c925c2d6ef879a7e51cb"}, - {file = "argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4f9665de60b1b0e99bcd6be4f17d90339698ce954cfd8d9cf4f91c995165a92"}, - {file = "argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ba92837e4a9aa6a508c8d2d7883ed5a8f6c308c89a4790e1e447a220deb79a85"}, - {file = "argon2_cffi_bindings-25.1.0-cp314-cp314t-win32.whl", hash = "sha256:84a461d4d84ae1295871329b346a97f68eade8c53b6ed9a7ca2d7467f3c8ff6f"}, - {file = "argon2_cffi_bindings-25.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b55aec3565b65f56455eebc9b9f34130440404f27fe21c3b375bf1ea4d8fbae6"}, - {file = "argon2_cffi_bindings-25.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:87c33a52407e4c41f3b70a9c2d3f6056d88b10dad7695be708c5021673f55623"}, - {file = "argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:aecba1723ae35330a008418a91ea6cfcedf6d31e5fbaa056a166462ff066d500"}, - {file = "argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2630b6240b495dfab90aebe159ff784d08ea999aa4b0d17efa734055a07d2f44"}, - {file = "argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:7aef0c91e2c0fbca6fc68e7555aa60ef7008a739cbe045541e438373bc54d2b0"}, - {file = "argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e021e87faa76ae0d413b619fe2b65ab9a037f24c60a1e6cc43457ae20de6dc6"}, - {file = "argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3e924cfc503018a714f94a49a149fdc0b644eaead5d1f089330399134fa028a"}, - {file = "argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87b72589133f0346a1cb8d5ecca4b933e3c9b64656c9d175270a000e73b288d"}, - {file = "argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1db89609c06afa1a214a69a462ea741cf735b29a57530478c06eb81dd403de99"}, - {file = "argon2_cffi_bindings-25.1.0-cp39-abi3-win32.whl", hash = "sha256:473bcb5f82924b1becbb637b63303ec8d10e84c8d241119419897a26116515d2"}, - {file = "argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl", hash = "sha256:a98cd7d17e9f7ce244c0803cad3c23a7d379c301ba618a5fa76a67d116618b98"}, - {file = "argon2_cffi_bindings-25.1.0-cp39-abi3-win_arm64.whl", hash = "sha256:b0fdbcf513833809c882823f98dc2f931cf659d9a1429616ac3adebb49f5db94"}, - {file = "argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6dca33a9859abf613e22733131fc9194091c1fa7cb3e131c143056b4856aa47e"}, - {file = "argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:21378b40e1b8d1655dd5310c84a40fc19a9aa5e6366e835ceb8576bf0fea716d"}, - {file = "argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d588dec224e2a83edbdc785a5e6f3c6cd736f46bfd4b441bbb5aa1f5085e584"}, - {file = "argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5acb4e41090d53f17ca1110c3427f0a130f944b896fc8c83973219c97f57b690"}, - {file = "argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:da0c79c23a63723aa5d782250fbf51b768abca630285262fb5144ba5ae01e520"}, - {file = "argon2_cffi_bindings-25.1.0.tar.gz", hash = "sha256:b957f3e6ea4d55d820e40ff76f450952807013d361a65d7f28acc0acbf29229d"}, -] - -[package.dependencies] -cffi = {version = ">=1.0.1", markers = "python_version < \"3.14\""} - -[[package]] -name = "arrow" -version = "1.3.0" -description = "Better dates & times for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "arrow-1.3.0-py3-none-any.whl", hash = "sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80"}, - {file = "arrow-1.3.0.tar.gz", hash = "sha256:d4540617648cb5f895730f1ad8c82a65f2dad0166f57b75f3ca54759c4d67a85"}, -] - -[package.dependencies] -python-dateutil = ">=2.7.0" -types-python-dateutil = ">=2.8.10" - -[package.extras] -doc = ["doc8", "sphinx (>=7.0.0)", "sphinx-autobuild", "sphinx-autodoc-typehints", "sphinx_rtd_theme (>=1.3.0)"] -test = ["dateparser (==1.*)", "pre-commit", "pytest", "pytest-cov", "pytest-mock", "pytz (==2021.1)", "simplejson (==3.*)"] - -[[package]] -name = "asttokens" -version = "3.0.0" -description = "Annotate AST trees with source code positions" -optional = false -python-versions = ">=3.8" -files = [ - {file = "asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2"}, - {file = "asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7"}, -] - -[package.extras] -astroid = ["astroid (>=2,<4)"] -test = ["astroid (>=2,<4)", "pytest", "pytest-cov", "pytest-xdist"] - -[[package]] -name = "async-lru" -version = "2.0.5" -description = "Simple LRU cache for asyncio" -optional = false -python-versions = ">=3.9" -files = [ - {file = "async_lru-2.0.5-py3-none-any.whl", hash = "sha256:ab95404d8d2605310d345932697371a5f40def0487c03d6d0ad9138de52c9943"}, - {file = "async_lru-2.0.5.tar.gz", hash = "sha256:481d52ccdd27275f42c43a928b4a50c3bfb2d67af4e78b170e3e0bb39c66e5bb"}, -] - -[[package]] -name = "attrs" -version = "25.4.0" -description = "Classes Without Boilerplate" -optional = false -python-versions = ">=3.9" -files = [ - {file = "attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373"}, - {file = "attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11"}, -] - -[[package]] -name = "babel" -version = "2.17.0" -description = "Internationalization utilities" -optional = false -python-versions = ">=3.8" -files = [ - {file = "babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2"}, - {file = "babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d"}, -] - -[package.extras] -dev = ["backports.zoneinfo", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata"] - -[[package]] -name = "beautifulsoup4" -version = "4.14.2" -description = "Screen-scraping library" -optional = false -python-versions = ">=3.7.0" -files = [ - {file = "beautifulsoup4-4.14.2-py3-none-any.whl", hash = "sha256:5ef6fa3a8cbece8488d66985560f97ed091e22bbc4e9c2338508a9d5de6d4515"}, - {file = "beautifulsoup4-4.14.2.tar.gz", hash = "sha256:2a98ab9f944a11acee9cc848508ec28d9228abfd522ef0fad6a02a72e0ded69e"}, -] - -[package.dependencies] -soupsieve = ">1.2" -typing-extensions = ">=4.0.0" - -[package.extras] -cchardet = ["cchardet"] -chardet = ["chardet"] -charset-normalizer = ["charset-normalizer"] -html5lib = ["html5lib"] -lxml = ["lxml"] - -[[package]] -name = "bleach" -version = "6.2.0" -description = "An easy safelist-based HTML-sanitizing tool." -optional = false -python-versions = ">=3.9" -files = [ - {file = "bleach-6.2.0-py3-none-any.whl", hash = "sha256:117d9c6097a7c3d22fd578fcd8d35ff1e125df6736f554da4e432fdd63f31e5e"}, - {file = "bleach-6.2.0.tar.gz", hash = "sha256:123e894118b8a599fd80d3ec1a6d4cc7ce4e5882b1317a7e1ba69b56e95f991f"}, -] - -[package.dependencies] -tinycss2 = {version = ">=1.1.0,<1.5", optional = true, markers = "extra == \"css\""} -webencodings = "*" - -[package.extras] -css = ["tinycss2 (>=1.1.0,<1.5)"] - -[[package]] -name = "certifi" -version = "2025.10.5" -description = "Python package for providing Mozilla's CA Bundle." -optional = false -python-versions = ">=3.7" -files = [ - {file = "certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de"}, - {file = "certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43"}, -] - -[[package]] -name = "cffi" -version = "2.0.0" -description = "Foreign Function Interface for Python calling C code." -optional = false -python-versions = ">=3.9" -files = [ - {file = "cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44"}, - {file = "cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49"}, - {file = "cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c"}, - {file = "cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb"}, - {file = "cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0"}, - {file = "cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4"}, - {file = "cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453"}, - {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495"}, - {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5"}, - {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb"}, - {file = "cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a"}, - {file = "cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739"}, - {file = "cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe"}, - {file = "cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c"}, - {file = "cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92"}, - {file = "cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93"}, - {file = "cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5"}, - {file = "cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664"}, - {file = "cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26"}, - {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9"}, - {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414"}, - {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743"}, - {file = "cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5"}, - {file = "cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5"}, - {file = "cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d"}, - {file = "cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d"}, - {file = "cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c"}, - {file = "cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe"}, - {file = "cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062"}, - {file = "cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e"}, - {file = "cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037"}, - {file = "cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba"}, - {file = "cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94"}, - {file = "cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187"}, - {file = "cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18"}, - {file = "cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5"}, - {file = "cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6"}, - {file = "cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb"}, - {file = "cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca"}, - {file = "cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b"}, - {file = "cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b"}, - {file = "cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2"}, - {file = "cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3"}, - {file = "cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26"}, - {file = "cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c"}, - {file = "cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b"}, - {file = "cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27"}, - {file = "cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75"}, - {file = "cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91"}, - {file = "cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5"}, - {file = "cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13"}, - {file = "cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b"}, - {file = "cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c"}, - {file = "cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef"}, - {file = "cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775"}, - {file = "cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205"}, - {file = "cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1"}, - {file = "cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f"}, - {file = "cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25"}, - {file = "cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad"}, - {file = "cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9"}, - {file = "cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d"}, - {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c"}, - {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8"}, - {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc"}, - {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592"}, - {file = "cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512"}, - {file = "cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4"}, - {file = "cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e"}, - {file = "cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6"}, - {file = "cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9"}, - {file = "cffi-2.0.0-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf"}, - {file = "cffi-2.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7"}, - {file = "cffi-2.0.0-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c"}, - {file = "cffi-2.0.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165"}, - {file = "cffi-2.0.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534"}, - {file = "cffi-2.0.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f"}, - {file = "cffi-2.0.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63"}, - {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2"}, - {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65"}, - {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322"}, - {file = "cffi-2.0.0-cp39-cp39-win32.whl", hash = "sha256:2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a"}, - {file = "cffi-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9"}, - {file = "cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529"}, -] - -[package.dependencies] -pycparser = {version = "*", markers = "implementation_name != \"PyPy\""} - -[[package]] -name = "charset-normalizer" -version = "3.4.3" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -optional = false -python-versions = ">=3.7" -files = [ - {file = "charset_normalizer-3.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-win32.whl", hash = "sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-win32.whl", hash = "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0f2be7e0cf7754b9a30eb01f4295cc3d4358a479843b31f328afd210e2c7598c"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c60e092517a73c632ec38e290eba714e9627abe9d301c8c8a12ec32c314a2a4b"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:252098c8c7a873e17dd696ed98bbe91dbacd571da4b87df3736768efa7a792e4"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3653fad4fe3ed447a596ae8638b437f827234f01a8cd801842e43f3d0a6b281b"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8999f965f922ae054125286faf9f11bc6932184b93011d138925a1773830bbe9"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d95bfb53c211b57198bb91c46dd5a2d8018b3af446583aab40074bf7988401cb"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:5b413b0b1bfd94dbf4023ad6945889f374cd24e3f62de58d6bb102c4d9ae534a"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:b5e3b2d152e74e100a9e9573837aba24aab611d39428ded46f4e4022ea7d1942"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a2d08ac246bb48479170408d6c19f6385fa743e7157d716e144cad849b2dd94b"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-win32.whl", hash = "sha256:ec557499516fc90fd374bf2e32349a2887a876fbf162c160e3c01b6849eaf557"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:5d8d01eac18c423815ed4f4a2ec3b439d654e55ee4ad610e153cf02faf67ea40"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:70bfc5f2c318afece2f5838ea5e4c3febada0be750fcf4775641052bbba14d05"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:23b6b24d74478dc833444cbd927c338349d6ae852ba53a0d02a2de1fce45b96e"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:34a7f768e3f985abdb42841e20e17b330ad3aaf4bb7e7aeeb73db2e70f077b99"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb731e5deb0c7ef82d698b0f4c5bb724633ee2a489401594c5c88b02e6cb15f7"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:257f26fed7d7ff59921b78244f3cd93ed2af1800ff048c33f624c87475819dd7"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1ef99f0456d3d46a50945c98de1774da86f8e992ab5c77865ea8b8195341fc19"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2c322db9c8c89009a990ef07c3bcc9f011a3269bc06782f916cd3d9eed7c9312"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:511729f456829ef86ac41ca78c63a5cb55240ed23b4b737faca0eb1abb1c41bc"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:88ab34806dea0671532d3f82d82b85e8fc23d7b2dd12fa837978dad9bb392a34"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-win32.whl", hash = "sha256:16a8770207946ac75703458e2c743631c79c59c5890c80011d536248f8eaa432"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:d22dbedd33326a4a5190dd4fe9e9e693ef12160c77382d9e87919bce54f3d4ca"}, - {file = "charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a"}, - {file = "charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14"}, -] - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] - -[[package]] -name = "comm" -version = "0.2.3" -description = "Jupyter Python Comm implementation, for usage in ipykernel, xeus-python etc." -optional = false -python-versions = ">=3.8" -files = [ - {file = "comm-0.2.3-py3-none-any.whl", hash = "sha256:c615d91d75f7f04f095b30d1c1711babd43bdc6419c1be9886a85f2f4e489417"}, - {file = "comm-0.2.3.tar.gz", hash = "sha256:2dc8048c10962d55d7ad693be1e7045d891b7ce8d999c97963a5e3e99c055971"}, -] - -[package.extras] -test = ["pytest"] - -[[package]] -name = "contourpy" -version = "1.3.3" -description = "Python library for calculating contours of 2D quadrilateral grids" -optional = false -python-versions = ">=3.11" -files = [ - {file = "contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1"}, - {file = "contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381"}, - {file = "contourpy-1.3.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7"}, - {file = "contourpy-1.3.3-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1"}, - {file = "contourpy-1.3.3-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a"}, - {file = "contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db"}, - {file = "contourpy-1.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620"}, - {file = "contourpy-1.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f"}, - {file = "contourpy-1.3.3-cp311-cp311-win32.whl", hash = "sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff"}, - {file = "contourpy-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42"}, - {file = "contourpy-1.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470"}, - {file = "contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb"}, - {file = "contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6"}, - {file = "contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7"}, - {file = "contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8"}, - {file = "contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea"}, - {file = "contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1"}, - {file = "contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7"}, - {file = "contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411"}, - {file = "contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69"}, - {file = "contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b"}, - {file = "contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc"}, - {file = "contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5"}, - {file = "contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1"}, - {file = "contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286"}, - {file = "contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5"}, - {file = "contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67"}, - {file = "contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9"}, - {file = "contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659"}, - {file = "contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7"}, - {file = "contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d"}, - {file = "contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263"}, - {file = "contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9"}, - {file = "contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d"}, - {file = "contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216"}, - {file = "contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae"}, - {file = "contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20"}, - {file = "contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99"}, - {file = "contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b"}, - {file = "contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a"}, - {file = "contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e"}, - {file = "contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3"}, - {file = "contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8"}, - {file = "contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301"}, - {file = "contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a"}, - {file = "contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77"}, - {file = "contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5"}, - {file = "contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4"}, - {file = "contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36"}, - {file = "contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3"}, - {file = "contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b"}, - {file = "contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36"}, - {file = "contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d"}, - {file = "contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd"}, - {file = "contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339"}, - {file = "contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772"}, - {file = "contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77"}, - {file = "contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13"}, - {file = "contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe"}, - {file = "contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f"}, - {file = "contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0"}, - {file = "contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4"}, - {file = "contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f"}, - {file = "contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae"}, - {file = "contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc"}, - {file = "contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b"}, - {file = "contourpy-1.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497"}, - {file = "contourpy-1.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8"}, - {file = "contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e"}, - {file = "contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989"}, - {file = "contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77"}, - {file = "contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880"}, -] - -[package.dependencies] -numpy = ">=1.25" - -[package.extras] -bokeh = ["bokeh", "selenium"] -docs = ["furo", "sphinx (>=7.2)", "sphinx-copybutton"] -mypy = ["bokeh", "contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.17.0)", "types-Pillow"] -test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] -test-no-images = ["pytest", "pytest-cov", "pytest-rerunfailures", "pytest-xdist", "wurlitzer"] - -[[package]] -name = "cycler" -version = "0.12.1" -description = "Composable style cycles" -optional = false -python-versions = ">=3.8" -files = [ - {file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"}, - {file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"}, -] - -[package.extras] -docs = ["ipython", "matplotlib", "numpydoc", "sphinx"] -tests = ["pytest", "pytest-cov", "pytest-xdist"] - -[[package]] -name = "debugpy" -version = "1.8.17" -description = "An implementation of the Debug Adapter Protocol for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "debugpy-1.8.17-cp310-cp310-macosx_15_0_x86_64.whl", hash = "sha256:c41d2ce8bbaddcc0009cc73f65318eedfa3dbc88a8298081deb05389f1ab5542"}, - {file = "debugpy-1.8.17-cp310-cp310-manylinux_2_34_x86_64.whl", hash = "sha256:1440fd514e1b815edd5861ca394786f90eb24960eb26d6f7200994333b1d79e3"}, - {file = "debugpy-1.8.17-cp310-cp310-win32.whl", hash = "sha256:3a32c0af575749083d7492dc79f6ab69f21b2d2ad4cd977a958a07d5865316e4"}, - {file = "debugpy-1.8.17-cp310-cp310-win_amd64.whl", hash = "sha256:a3aad0537cf4d9c1996434be68c6c9a6d233ac6f76c2a482c7803295b4e4f99a"}, - {file = "debugpy-1.8.17-cp311-cp311-macosx_15_0_universal2.whl", hash = "sha256:d3fce3f0e3de262a3b67e69916d001f3e767661c6e1ee42553009d445d1cd840"}, - {file = "debugpy-1.8.17-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:c6bdf134457ae0cac6fb68205776be635d31174eeac9541e1d0c062165c6461f"}, - {file = "debugpy-1.8.17-cp311-cp311-win32.whl", hash = "sha256:e79a195f9e059edfe5d8bf6f3749b2599452d3e9380484cd261f6b7cd2c7c4da"}, - {file = "debugpy-1.8.17-cp311-cp311-win_amd64.whl", hash = "sha256:b532282ad4eca958b1b2d7dbcb2b7218e02cb934165859b918e3b6ba7772d3f4"}, - {file = "debugpy-1.8.17-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:f14467edef672195c6f6b8e27ce5005313cb5d03c9239059bc7182b60c176e2d"}, - {file = "debugpy-1.8.17-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:24693179ef9dfa20dca8605905a42b392be56d410c333af82f1c5dff807a64cc"}, - {file = "debugpy-1.8.17-cp312-cp312-win32.whl", hash = "sha256:6a4e9dacf2cbb60d2514ff7b04b4534b0139facbf2abdffe0639ddb6088e59cf"}, - {file = "debugpy-1.8.17-cp312-cp312-win_amd64.whl", hash = "sha256:e8f8f61c518952fb15f74a302e068b48d9c4691768ade433e4adeea961993464"}, - {file = "debugpy-1.8.17-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:857c1dd5d70042502aef1c6d1c2801211f3ea7e56f75e9c335f434afb403e464"}, - {file = "debugpy-1.8.17-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:3bea3b0b12f3946e098cce9b43c3c46e317b567f79570c3f43f0b96d00788088"}, - {file = "debugpy-1.8.17-cp313-cp313-win32.whl", hash = "sha256:e34ee844c2f17b18556b5bbe59e1e2ff4e86a00282d2a46edab73fd7f18f4a83"}, - {file = "debugpy-1.8.17-cp313-cp313-win_amd64.whl", hash = "sha256:6c5cd6f009ad4fca8e33e5238210dc1e5f42db07d4b6ab21ac7ffa904a196420"}, - {file = "debugpy-1.8.17-cp314-cp314-macosx_15_0_universal2.whl", hash = "sha256:045290c010bcd2d82bc97aa2daf6837443cd52f6328592698809b4549babcee1"}, - {file = "debugpy-1.8.17-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:b69b6bd9dba6a03632534cdf67c760625760a215ae289f7489a452af1031fe1f"}, - {file = "debugpy-1.8.17-cp314-cp314-win32.whl", hash = "sha256:5c59b74aa5630f3a5194467100c3b3d1c77898f9ab27e3f7dc5d40fc2f122670"}, - {file = "debugpy-1.8.17-cp314-cp314-win_amd64.whl", hash = "sha256:893cba7bb0f55161de4365584b025f7064e1f88913551bcd23be3260b231429c"}, - {file = "debugpy-1.8.17-cp38-cp38-macosx_15_0_x86_64.whl", hash = "sha256:8deb4e31cd575c9f9370042876e078ca118117c1b5e1f22c32befcfbb6955f0c"}, - {file = "debugpy-1.8.17-cp38-cp38-manylinux_2_34_x86_64.whl", hash = "sha256:b75868b675949a96ab51abc114c7163f40ff0d8f7d6d5fd63f8932fd38e9c6d7"}, - {file = "debugpy-1.8.17-cp38-cp38-win32.whl", hash = "sha256:17e456da14848d618662354e1dccfd5e5fb75deec3d1d48dc0aa0baacda55860"}, - {file = "debugpy-1.8.17-cp38-cp38-win_amd64.whl", hash = "sha256:e851beb536a427b5df8aa7d0c7835b29a13812f41e46292ff80b2ef77327355a"}, - {file = "debugpy-1.8.17-cp39-cp39-macosx_15_0_x86_64.whl", hash = "sha256:f2ac8055a0c4a09b30b931100996ba49ef334c6947e7ae365cdd870416d7513e"}, - {file = "debugpy-1.8.17-cp39-cp39-manylinux_2_34_x86_64.whl", hash = "sha256:eaa85bce251feca8e4c87ce3b954aba84b8c645b90f0e6a515c00394a9f5c0e7"}, - {file = "debugpy-1.8.17-cp39-cp39-win32.whl", hash = "sha256:b13eea5587e44f27f6c48588b5ad56dcb74a4f3a5f89250443c94587f3eb2ea1"}, - {file = "debugpy-1.8.17-cp39-cp39-win_amd64.whl", hash = "sha256:bb1bbf92317e1f35afcf3ef0450219efb3afe00be79d8664b250ac0933b9015f"}, - {file = "debugpy-1.8.17-py2.py3-none-any.whl", hash = "sha256:60c7dca6571efe660ccb7a9508d73ca14b8796c4ed484c2002abba714226cfef"}, - {file = "debugpy-1.8.17.tar.gz", hash = "sha256:fd723b47a8c08892b1a16b2c6239a8b96637c62a59b94bb5dab4bac592a58a8e"}, -] - -[[package]] -name = "decorator" -version = "5.2.1" -description = "Decorators for Humans" -optional = false -python-versions = ">=3.8" -files = [ - {file = "decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a"}, - {file = "decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360"}, -] - -[[package]] -name = "defusedxml" -version = "0.7.1" -description = "XML bomb protection for Python stdlib modules" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -files = [ - {file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"}, - {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"}, -] - -[[package]] -name = "executing" -version = "2.2.1" -description = "Get the currently executing AST node of a frame, and other information" -optional = false -python-versions = ">=3.8" -files = [ - {file = "executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017"}, - {file = "executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4"}, -] - -[package.extras] -tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"] - -[[package]] -name = "fastjsonschema" -version = "2.21.2" -description = "Fastest Python implementation of JSON schema" -optional = false -python-versions = "*" -files = [ - {file = "fastjsonschema-2.21.2-py3-none-any.whl", hash = "sha256:1c797122d0a86c5cace2e54bf4e819c36223b552017172f32c5c024a6b77e463"}, - {file = "fastjsonschema-2.21.2.tar.gz", hash = "sha256:b1eb43748041c880796cd077f1a07c3d94e93ae84bba5ed36800a33554ae05de"}, -] - -[package.extras] -devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benchmark", "pytest-cache", "validictory"] - -[[package]] -name = "fonttools" -version = "4.60.1" -description = "Tools to manipulate font files" -optional = false -python-versions = ">=3.9" -files = [ - {file = "fonttools-4.60.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9a52f254ce051e196b8fe2af4634c2d2f02c981756c6464dc192f1b6050b4e28"}, - {file = "fonttools-4.60.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c7420a2696a44650120cdd269a5d2e56a477e2bfa9d95e86229059beb1c19e15"}, - {file = "fonttools-4.60.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee0c0b3b35b34f782afc673d503167157094a16f442ace7c6c5e0ca80b08f50c"}, - {file = "fonttools-4.60.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:282dafa55f9659e8999110bd8ed422ebe1c8aecd0dc396550b038e6c9a08b8ea"}, - {file = "fonttools-4.60.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4ba4bd646e86de16160f0fb72e31c3b9b7d0721c3e5b26b9fa2fc931dfdb2652"}, - {file = "fonttools-4.60.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0b0835ed15dd5b40d726bb61c846a688f5b4ce2208ec68779bc81860adb5851a"}, - {file = "fonttools-4.60.1-cp310-cp310-win32.whl", hash = "sha256:1525796c3ffe27bb6268ed2a1bb0dcf214d561dfaf04728abf01489eb5339dce"}, - {file = "fonttools-4.60.1-cp310-cp310-win_amd64.whl", hash = "sha256:268ecda8ca6cb5c4f044b1fb9b3b376e8cd1b361cef275082429dc4174907038"}, - {file = "fonttools-4.60.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7b4c32e232a71f63a5d00259ca3d88345ce2a43295bb049d21061f338124246f"}, - {file = "fonttools-4.60.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3630e86c484263eaac71d117085d509cbcf7b18f677906824e4bace598fb70d2"}, - {file = "fonttools-4.60.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5c1015318e4fec75dd4943ad5f6a206d9727adf97410d58b7e32ab644a807914"}, - {file = "fonttools-4.60.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e6c58beb17380f7c2ea181ea11e7db8c0ceb474c9dd45f48e71e2cb577d146a1"}, - {file = "fonttools-4.60.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ec3681a0cb34c255d76dd9d865a55f260164adb9fa02628415cdc2d43ee2c05d"}, - {file = "fonttools-4.60.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f4b5c37a5f40e4d733d3bbaaef082149bee5a5ea3156a785ff64d949bd1353fa"}, - {file = "fonttools-4.60.1-cp311-cp311-win32.whl", hash = "sha256:398447f3d8c0c786cbf1209711e79080a40761eb44b27cdafffb48f52bcec258"}, - {file = "fonttools-4.60.1-cp311-cp311-win_amd64.whl", hash = "sha256:d066ea419f719ed87bc2c99a4a4bfd77c2e5949cb724588b9dd58f3fd90b92bf"}, - {file = "fonttools-4.60.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7b0c6d57ab00dae9529f3faf187f2254ea0aa1e04215cf2f1a8ec277c96661bc"}, - {file = "fonttools-4.60.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:839565cbf14645952d933853e8ade66a463684ed6ed6c9345d0faf1f0e868877"}, - {file = "fonttools-4.60.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8177ec9676ea6e1793c8a084a90b65a9f778771998eb919d05db6d4b1c0b114c"}, - {file = "fonttools-4.60.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:996a4d1834524adbb423385d5a629b868ef9d774670856c63c9a0408a3063401"}, - {file = "fonttools-4.60.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a46b2f450bc79e06ef3b6394f0c68660529ed51692606ad7f953fc2e448bc903"}, - {file = "fonttools-4.60.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6ec722ee589e89a89f5b7574f5c45604030aa6ae24cb2c751e2707193b466fed"}, - {file = "fonttools-4.60.1-cp312-cp312-win32.whl", hash = "sha256:b2cf105cee600d2de04ca3cfa1f74f1127f8455b71dbad02b9da6ec266e116d6"}, - {file = "fonttools-4.60.1-cp312-cp312-win_amd64.whl", hash = "sha256:992775c9fbe2cf794786fa0ffca7f09f564ba3499b8fe9f2f80bd7197db60383"}, - {file = "fonttools-4.60.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6f68576bb4bbf6060c7ab047b1574a1ebe5c50a17de62830079967b211059ebb"}, - {file = "fonttools-4.60.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:eedacb5c5d22b7097482fa834bda0dafa3d914a4e829ec83cdea2a01f8c813c4"}, - {file = "fonttools-4.60.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b33a7884fabd72bdf5f910d0cf46be50dce86a0362a65cfc746a4168c67eb96c"}, - {file = "fonttools-4.60.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2409d5fb7b55fd70f715e6d34e7a6e4f7511b8ad29a49d6df225ee76da76dd77"}, - {file = "fonttools-4.60.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c8651e0d4b3bdeda6602b85fdc2abbefc1b41e573ecb37b6779c4ca50753a199"}, - {file = "fonttools-4.60.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:145daa14bf24824b677b9357c5e44fd8895c2a8f53596e1b9ea3496081dc692c"}, - {file = "fonttools-4.60.1-cp313-cp313-win32.whl", hash = "sha256:2299df884c11162617a66b7c316957d74a18e3758c0274762d2cc87df7bc0272"}, - {file = "fonttools-4.60.1-cp313-cp313-win_amd64.whl", hash = "sha256:a3db56f153bd4c5c2b619ab02c5db5192e222150ce5a1bc10f16164714bc39ac"}, - {file = "fonttools-4.60.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:a884aef09d45ba1206712c7dbda5829562d3fea7726935d3289d343232ecb0d3"}, - {file = "fonttools-4.60.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8a44788d9d91df72d1a5eac49b31aeb887a5f4aab761b4cffc4196c74907ea85"}, - {file = "fonttools-4.60.1-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e852d9dda9f93ad3651ae1e3bb770eac544ec93c3807888798eccddf84596537"}, - {file = "fonttools-4.60.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:154cb6ee417e417bf5f7c42fe25858c9140c26f647c7347c06f0cc2d47eff003"}, - {file = "fonttools-4.60.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5664fd1a9ea7f244487ac8f10340c4e37664675e8667d6fee420766e0fb3cf08"}, - {file = "fonttools-4.60.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:583b7f8e3c49486e4d489ad1deacfb8d5be54a8ef34d6df824f6a171f8511d99"}, - {file = "fonttools-4.60.1-cp314-cp314-win32.whl", hash = "sha256:66929e2ea2810c6533a5184f938502cfdaea4bc3efb7130d8cc02e1c1b4108d6"}, - {file = "fonttools-4.60.1-cp314-cp314-win_amd64.whl", hash = "sha256:f3d5be054c461d6a2268831f04091dc82753176f6ea06dc6047a5e168265a987"}, - {file = "fonttools-4.60.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:b6379e7546ba4ae4b18f8ae2b9bc5960936007a1c0e30b342f662577e8bc3299"}, - {file = "fonttools-4.60.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9d0ced62b59e0430b3690dbc5373df1c2aa7585e9a8ce38eff87f0fd993c5b01"}, - {file = "fonttools-4.60.1-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:875cb7764708b3132637f6c5fb385b16eeba0f7ac9fa45a69d35e09b47045801"}, - {file = "fonttools-4.60.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a184b2ea57b13680ab6d5fbde99ccef152c95c06746cb7718c583abd8f945ccc"}, - {file = "fonttools-4.60.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:026290e4ec76583881763fac284aca67365e0be9f13a7fb137257096114cb3bc"}, - {file = "fonttools-4.60.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f0e8817c7d1a0c2eedebf57ef9a9896f3ea23324769a9a2061a80fe8852705ed"}, - {file = "fonttools-4.60.1-cp314-cp314t-win32.whl", hash = "sha256:1410155d0e764a4615774e5c2c6fc516259fe3eca5882f034eb9bfdbee056259"}, - {file = "fonttools-4.60.1-cp314-cp314t-win_amd64.whl", hash = "sha256:022beaea4b73a70295b688f817ddc24ed3e3418b5036ffcd5658141184ef0d0c"}, - {file = "fonttools-4.60.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:122e1a8ada290423c493491d002f622b1992b1ab0b488c68e31c413390dc7eb2"}, - {file = "fonttools-4.60.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a140761c4ff63d0cb9256ac752f230460ee225ccef4ad8f68affc723c88e2036"}, - {file = "fonttools-4.60.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0eae96373e4b7c9e45d099d7a523444e3554360927225c1cdae221a58a45b856"}, - {file = "fonttools-4.60.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:596ecaca36367027d525b3b426d8a8208169d09edcf8c7506aceb3a38bfb55c7"}, - {file = "fonttools-4.60.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2ee06fc57512144d8b0445194c2da9f190f61ad51e230f14836286470c99f854"}, - {file = "fonttools-4.60.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b42d86938e8dda1cd9a1a87a6d82f1818eaf933348429653559a458d027446da"}, - {file = "fonttools-4.60.1-cp39-cp39-win32.whl", hash = "sha256:8b4eb332f9501cb1cd3d4d099374a1e1306783ff95489a1026bde9eb02ccc34a"}, - {file = "fonttools-4.60.1-cp39-cp39-win_amd64.whl", hash = "sha256:7473a8ed9ed09aeaa191301244a5a9dbe46fe0bf54f9d6cd21d83044c3321217"}, - {file = "fonttools-4.60.1-py3-none-any.whl", hash = "sha256:906306ac7afe2156fcf0042173d6ebbb05416af70f6b370967b47f8f00103bbb"}, - {file = "fonttools-4.60.1.tar.gz", hash = "sha256:ef00af0439ebfee806b25f24c8f92109157ff3fac5731dc7867957812e87b8d9"}, -] - -[package.extras] -all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "pycairo", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0)", "xattr", "zopfli (>=0.1.4)"] -graphite = ["lz4 (>=1.7.4.2)"] -interpolatable = ["munkres", "pycairo", "scipy"] -lxml = ["lxml (>=4.0)"] -pathops = ["skia-pathops (>=0.5.0)"] -plot = ["matplotlib"] -repacker = ["uharfbuzz (>=0.23.0)"] -symfont = ["sympy"] -type1 = ["xattr"] -unicode = ["unicodedata2 (>=15.1.0)"] -woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] - -[[package]] -name = "fqdn" -version = "1.5.1" -description = "Validates fully-qualified domain names against RFC 1123, so that they are acceptable to modern bowsers" -optional = false -python-versions = ">=2.7, !=3.0, !=3.1, !=3.2, !=3.3, !=3.4, <4" -files = [ - {file = "fqdn-1.5.1-py3-none-any.whl", hash = "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014"}, - {file = "fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f"}, -] - -[[package]] -name = "h11" -version = "0.16.0" -description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -optional = false -python-versions = ">=3.8" -files = [ - {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, - {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, -] - -[[package]] -name = "httpcore" -version = "1.0.9" -description = "A minimal low-level HTTP client." -optional = false -python-versions = ">=3.8" -files = [ - {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, - {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, -] - -[package.dependencies] -certifi = "*" -h11 = ">=0.16" - -[package.extras] -asyncio = ["anyio (>=4.0,<5.0)"] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<1.0)"] - -[[package]] -name = "httpx" -version = "0.28.1" -description = "The next generation HTTP client." -optional = false -python-versions = ">=3.8" -files = [ - {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, - {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, -] - -[package.dependencies] -anyio = "*" -certifi = "*" -httpcore = "==1.*" -idna = "*" - -[package.extras] -brotli = ["brotli", "brotlicffi"] -cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] -zstd = ["zstandard (>=0.18.0)"] - -[[package]] -name = "idna" -version = "3.11" -description = "Internationalized Domain Names in Applications (IDNA)" -optional = false -python-versions = ">=3.8" -files = [ - {file = "idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea"}, - {file = "idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902"}, -] - -[package.extras] -all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] - -[[package]] -name = "ipykernel" -version = "6.30.1" -description = "IPython Kernel for Jupyter" -optional = false -python-versions = ">=3.9" -files = [ - {file = "ipykernel-6.30.1-py3-none-any.whl", hash = "sha256:aa6b9fb93dca949069d8b85b6c79b2518e32ac583ae9c7d37c51d119e18b3fb4"}, - {file = "ipykernel-6.30.1.tar.gz", hash = "sha256:6abb270161896402e76b91394fcdce5d1be5d45f456671e5080572f8505be39b"}, -] - -[package.dependencies] -appnope = {version = ">=0.1.2", markers = "platform_system == \"Darwin\""} -comm = ">=0.1.1" -debugpy = ">=1.6.5" -ipython = ">=7.23.1" -jupyter-client = ">=8.0.0" -jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" -matplotlib-inline = ">=0.1" -nest-asyncio = ">=1.4" -packaging = ">=22" -psutil = ">=5.7" -pyzmq = ">=25" -tornado = ">=6.2" -traitlets = ">=5.4.0" - -[package.extras] -cov = ["coverage[toml]", "matplotlib", "pytest-cov", "trio"] -docs = ["intersphinx-registry", "myst-parser", "pydata-sphinx-theme", "sphinx", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling", "trio"] -pyqt5 = ["pyqt5"] -pyside6 = ["pyside6"] -test = ["flaky", "ipyparallel", "pre-commit", "pytest (>=7.0,<9)", "pytest-asyncio (>=0.23.5)", "pytest-cov", "pytest-timeout"] - -[[package]] -name = "ipython" -version = "9.6.0" -description = "IPython: Productive Interactive Computing" -optional = false -python-versions = ">=3.11" -files = [ - {file = "ipython-9.6.0-py3-none-any.whl", hash = "sha256:5f77efafc886d2f023442479b8149e7d86547ad0a979e9da9f045d252f648196"}, - {file = "ipython-9.6.0.tar.gz", hash = "sha256:5603d6d5d356378be5043e69441a072b50a5b33b4503428c77b04cb8ce7bc731"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -decorator = "*" -ipython-pygments-lexers = "*" -jedi = ">=0.16" -matplotlib-inline = "*" -pexpect = {version = ">4.3", markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\""} -prompt_toolkit = ">=3.0.41,<3.1.0" -pygments = ">=2.4.0" -stack_data = "*" -traitlets = ">=5.13.0" -typing_extensions = {version = ">=4.6", markers = "python_version < \"3.12\""} - -[package.extras] -all = ["ipython[doc,matplotlib,test,test-extra]"] -black = ["black"] -doc = ["docrepr", "exceptiongroup", "intersphinx_registry", "ipykernel", "ipython[matplotlib,test]", "setuptools (>=61.2)", "sphinx (>=1.3)", "sphinx-rtd-theme", "sphinx_toml (==0.0.4)", "typing_extensions"] -matplotlib = ["matplotlib (>3.7)"] -test = ["packaging", "pytest", "pytest-asyncio", "testpath"] -test-extra = ["curio", "ipykernel", "ipython[matplotlib]", "ipython[test]", "jupyter_ai", "nbclient", "nbformat", "numpy (>=1.25)", "pandas (>2.0)", "trio"] - -[[package]] -name = "ipython-pygments-lexers" -version = "1.1.1" -description = "Defines a variety of Pygments lexers for highlighting IPython code." -optional = false -python-versions = ">=3.8" -files = [ - {file = "ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c"}, - {file = "ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81"}, -] - -[package.dependencies] -pygments = "*" - -[[package]] -name = "ipywidgets" -version = "8.1.7" -description = "Jupyter interactive widgets" -optional = false -python-versions = ">=3.7" -files = [ - {file = "ipywidgets-8.1.7-py3-none-any.whl", hash = "sha256:764f2602d25471c213919b8a1997df04bef869251db4ca8efba1b76b1bd9f7bb"}, - {file = "ipywidgets-8.1.7.tar.gz", hash = "sha256:15f1ac050b9ccbefd45dccfbb2ef6bed0029d8278682d569d71b8dd96bee0376"}, -] - -[package.dependencies] -comm = ">=0.1.3" -ipython = ">=6.1.0" -jupyterlab_widgets = ">=3.0.15,<3.1.0" -traitlets = ">=4.3.1" -widgetsnbextension = ">=4.0.14,<4.1.0" - -[package.extras] -test = ["ipykernel", "jsonschema", "pytest (>=3.6.0)", "pytest-cov", "pytz"] - -[[package]] -name = "isoduration" -version = "20.11.0" -description = "Operations with ISO 8601 durations" -optional = false -python-versions = ">=3.7" -files = [ - {file = "isoduration-20.11.0-py3-none-any.whl", hash = "sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042"}, - {file = "isoduration-20.11.0.tar.gz", hash = "sha256:ac2f9015137935279eac671f94f89eb00584f940f5dc49462a0c4ee692ba1bd9"}, -] - -[package.dependencies] -arrow = ">=0.15.0" - -[[package]] -name = "jedi" -version = "0.19.2" -description = "An autocompletion tool for Python that can be used for text editors." -optional = false -python-versions = ">=3.6" -files = [ - {file = "jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9"}, - {file = "jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0"}, -] - -[package.dependencies] -parso = ">=0.8.4,<0.9.0" - -[package.extras] -docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] -qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] -testing = ["Django", "attrs", "colorama", "docopt", "pytest (<9.0.0)"] - -[[package]] -name = "jinja2" -version = "3.1.6" -description = "A very fast and expressive template engine." -optional = false -python-versions = ">=3.7" -files = [ - {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, - {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, -] - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "joblib" -version = "1.5.2" -description = "Lightweight pipelining with Python functions" -optional = false -python-versions = ">=3.9" -files = [ - {file = "joblib-1.5.2-py3-none-any.whl", hash = "sha256:4e1f0bdbb987e6d843c70cf43714cb276623def372df3c22fe5266b2670bc241"}, - {file = "joblib-1.5.2.tar.gz", hash = "sha256:3faa5c39054b2f03ca547da9b2f52fde67c06240c31853f306aea97f13647b55"}, -] - -[[package]] -name = "json5" -version = "0.12.1" -description = "A Python implementation of the JSON5 data format." -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "json5-0.12.1-py3-none-any.whl", hash = "sha256:d9c9b3bc34a5f54d43c35e11ef7cb87d8bdd098c6ace87117a7b7e83e705c1d5"}, - {file = "json5-0.12.1.tar.gz", hash = "sha256:b2743e77b3242f8d03c143dd975a6ec7c52e2f2afe76ed934e53503dd4ad4990"}, -] - -[package.extras] -dev = ["build (==1.2.2.post1)", "coverage (==7.5.4)", "coverage (==7.8.0)", "mypy (==1.14.1)", "mypy (==1.15.0)", "pip (==25.0.1)", "pylint (==3.2.7)", "pylint (==3.3.6)", "ruff (==0.11.2)", "twine (==6.1.0)", "uv (==0.6.11)"] - -[[package]] -name = "jsonpointer" -version = "3.0.0" -description = "Identify specific nodes in a JSON document (RFC 6901)" -optional = false -python-versions = ">=3.7" -files = [ - {file = "jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942"}, - {file = "jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef"}, -] - -[[package]] -name = "jsonschema" -version = "4.25.1" -description = "An implementation of JSON Schema validation for Python" -optional = false -python-versions = ">=3.9" -files = [ - {file = "jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63"}, - {file = "jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85"}, -] - -[package.dependencies] -attrs = ">=22.2.0" -fqdn = {version = "*", optional = true, markers = "extra == \"format-nongpl\""} -idna = {version = "*", optional = true, markers = "extra == \"format-nongpl\""} -isoduration = {version = "*", optional = true, markers = "extra == \"format-nongpl\""} -jsonpointer = {version = ">1.13", optional = true, markers = "extra == \"format-nongpl\""} -jsonschema-specifications = ">=2023.03.6" -referencing = ">=0.28.4" -rfc3339-validator = {version = "*", optional = true, markers = "extra == \"format-nongpl\""} -rfc3986-validator = {version = ">0.1.0", optional = true, markers = "extra == \"format-nongpl\""} -rfc3987-syntax = {version = ">=1.1.0", optional = true, markers = "extra == \"format-nongpl\""} -rpds-py = ">=0.7.1" -uri-template = {version = "*", optional = true, markers = "extra == \"format-nongpl\""} -webcolors = {version = ">=24.6.0", optional = true, markers = "extra == \"format-nongpl\""} - -[package.extras] -format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] -format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "rfc3987-syntax (>=1.1.0)", "uri-template", "webcolors (>=24.6.0)"] - -[[package]] -name = "jsonschema-specifications" -version = "2025.9.1" -description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" -optional = false -python-versions = ">=3.9" -files = [ - {file = "jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe"}, - {file = "jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d"}, -] - -[package.dependencies] -referencing = ">=0.31.0" - -[[package]] -name = "jupyter" -version = "1.1.1" -description = "Jupyter metapackage. Install all the Jupyter components in one go." -optional = false -python-versions = "*" -files = [ - {file = "jupyter-1.1.1-py2.py3-none-any.whl", hash = "sha256:7a59533c22af65439b24bbe60373a4e95af8f16ac65a6c00820ad378e3f7cc83"}, - {file = "jupyter-1.1.1.tar.gz", hash = "sha256:d55467bceabdea49d7e3624af7e33d59c37fff53ed3a350e1ac957bed731de7a"}, -] - -[package.dependencies] -ipykernel = "*" -ipywidgets = "*" -jupyter-console = "*" -jupyterlab = "*" -nbconvert = "*" -notebook = "*" - -[[package]] -name = "jupyter-client" -version = "8.6.3" -description = "Jupyter protocol implementation and client libraries" -optional = false -python-versions = ">=3.8" -files = [ - {file = "jupyter_client-8.6.3-py3-none-any.whl", hash = "sha256:e8a19cc986cc45905ac3362915f410f3af85424b4c0905e94fa5f2cb08e8f23f"}, - {file = "jupyter_client-8.6.3.tar.gz", hash = "sha256:35b3a0947c4a6e9d589eb97d7d4cd5e90f910ee73101611f01283732bd6d9419"}, -] - -[package.dependencies] -jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" -python-dateutil = ">=2.8.2" -pyzmq = ">=23.0" -tornado = ">=6.2" -traitlets = ">=5.3" - -[package.extras] -docs = ["ipykernel", "myst-parser", "pydata-sphinx-theme", "sphinx (>=4)", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling"] -test = ["coverage", "ipykernel (>=6.14)", "mypy", "paramiko", "pre-commit", "pytest (<8.2.0)", "pytest-cov", "pytest-jupyter[client] (>=0.4.1)", "pytest-timeout"] - -[[package]] -name = "jupyter-console" -version = "6.6.3" -description = "Jupyter terminal console" -optional = false -python-versions = ">=3.7" -files = [ - {file = "jupyter_console-6.6.3-py3-none-any.whl", hash = "sha256:309d33409fcc92ffdad25f0bcdf9a4a9daa61b6f341177570fdac03de5352485"}, - {file = "jupyter_console-6.6.3.tar.gz", hash = "sha256:566a4bf31c87adbfadf22cdf846e3069b59a71ed5da71d6ba4d8aaad14a53539"}, -] - -[package.dependencies] -ipykernel = ">=6.14" -ipython = "*" -jupyter-client = ">=7.0.0" -jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" -prompt-toolkit = ">=3.0.30" -pygments = "*" -pyzmq = ">=17" -traitlets = ">=5.4" - -[package.extras] -test = ["flaky", "pexpect", "pytest"] - -[[package]] -name = "jupyter-core" -version = "5.8.1" -description = "Jupyter core package. A base package on which Jupyter projects rely." -optional = false -python-versions = ">=3.8" -files = [ - {file = "jupyter_core-5.8.1-py3-none-any.whl", hash = "sha256:c28d268fc90fb53f1338ded2eb410704c5449a358406e8a948b75706e24863d0"}, - {file = "jupyter_core-5.8.1.tar.gz", hash = "sha256:0a5f9706f70e64786b75acba995988915ebd4601c8a52e534a40b51c95f59941"}, -] - -[package.dependencies] -platformdirs = ">=2.5" -pywin32 = {version = ">=300", markers = "sys_platform == \"win32\" and platform_python_implementation != \"PyPy\""} -traitlets = ">=5.3" - -[package.extras] -docs = ["intersphinx-registry", "myst-parser", "pydata-sphinx-theme", "sphinx-autodoc-typehints", "sphinxcontrib-spelling", "traitlets"] -test = ["ipykernel", "pre-commit", "pytest (<9)", "pytest-cov", "pytest-timeout"] - -[[package]] -name = "jupyter-events" -version = "0.12.0" -description = "Jupyter Event System library" -optional = false -python-versions = ">=3.9" -files = [ - {file = "jupyter_events-0.12.0-py3-none-any.whl", hash = "sha256:6464b2fa5ad10451c3d35fabc75eab39556ae1e2853ad0c0cc31b656731a97fb"}, - {file = "jupyter_events-0.12.0.tar.gz", hash = "sha256:fc3fce98865f6784c9cd0a56a20644fc6098f21c8c33834a8d9fe383c17e554b"}, -] - -[package.dependencies] -jsonschema = {version = ">=4.18.0", extras = ["format-nongpl"]} -packaging = "*" -python-json-logger = ">=2.0.4" -pyyaml = ">=5.3" -referencing = "*" -rfc3339-validator = "*" -rfc3986-validator = ">=0.1.1" -traitlets = ">=5.3" - -[package.extras] -cli = ["click", "rich"] -docs = ["jupyterlite-sphinx", "myst-parser", "pydata-sphinx-theme (>=0.16)", "sphinx (>=8)", "sphinxcontrib-spelling"] -test = ["click", "pre-commit", "pytest (>=7.0)", "pytest-asyncio (>=0.19.0)", "pytest-console-scripts", "rich"] - -[[package]] -name = "jupyter-lsp" -version = "2.3.0" -description = "Multi-Language Server WebSocket proxy for Jupyter Notebook/Lab server" -optional = false -python-versions = ">=3.8" -files = [ - {file = "jupyter_lsp-2.3.0-py3-none-any.whl", hash = "sha256:e914a3cb2addf48b1c7710914771aaf1819d46b2e5a79b0f917b5478ec93f34f"}, - {file = "jupyter_lsp-2.3.0.tar.gz", hash = "sha256:458aa59339dc868fb784d73364f17dbce8836e906cd75fd471a325cba02e0245"}, -] - -[package.dependencies] -jupyter_server = ">=1.1.2" - -[[package]] -name = "jupyter-server" -version = "2.17.0" -description = "The backend—i.e. core services, APIs, and REST endpoints—to Jupyter web applications." -optional = false -python-versions = ">=3.9" -files = [ - {file = "jupyter_server-2.17.0-py3-none-any.whl", hash = "sha256:e8cb9c7db4251f51ed307e329b81b72ccf2056ff82d50524debde1ee1870e13f"}, - {file = "jupyter_server-2.17.0.tar.gz", hash = "sha256:c38ea898566964c888b4772ae1ed58eca84592e88251d2cfc4d171f81f7e99d5"}, -] - -[package.dependencies] -anyio = ">=3.1.0" -argon2-cffi = ">=21.1" -jinja2 = ">=3.0.3" -jupyter-client = ">=7.4.4" -jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" -jupyter-events = ">=0.11.0" -jupyter-server-terminals = ">=0.4.4" -nbconvert = ">=6.4.4" -nbformat = ">=5.3.0" -overrides = {version = ">=5.0", markers = "python_version < \"3.12\""} -packaging = ">=22.0" -prometheus-client = ">=0.9" -pywinpty = {version = ">=2.0.1", markers = "os_name == \"nt\""} -pyzmq = ">=24" -send2trash = ">=1.8.2" -terminado = ">=0.8.3" -tornado = ">=6.2.0" -traitlets = ">=5.6.0" -websocket-client = ">=1.7" - -[package.extras] -docs = ["ipykernel", "jinja2", "jupyter-client", "myst-parser", "nbformat", "prometheus-client", "pydata-sphinx-theme", "send2trash", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-openapi (>=0.8.0)", "sphinxcontrib-spelling", "sphinxemoji", "tornado", "typing-extensions"] -test = ["flaky", "ipykernel", "pre-commit", "pytest (>=7.0,<9)", "pytest-console-scripts", "pytest-jupyter[server] (>=0.7)", "pytest-timeout", "requests"] - -[[package]] -name = "jupyter-server-terminals" -version = "0.5.3" -description = "A Jupyter Server Extension Providing Terminals." -optional = false -python-versions = ">=3.8" -files = [ - {file = "jupyter_server_terminals-0.5.3-py3-none-any.whl", hash = "sha256:41ee0d7dc0ebf2809c668e0fc726dfaf258fcd3e769568996ca731b6194ae9aa"}, - {file = "jupyter_server_terminals-0.5.3.tar.gz", hash = "sha256:5ae0295167220e9ace0edcfdb212afd2b01ee8d179fe6f23c899590e9b8a5269"}, -] - -[package.dependencies] -pywinpty = {version = ">=2.0.3", markers = "os_name == \"nt\""} -terminado = ">=0.8.3" - -[package.extras] -docs = ["jinja2", "jupyter-server", "mistune (<4.0)", "myst-parser", "nbformat", "packaging", "pydata-sphinx-theme", "sphinxcontrib-github-alt", "sphinxcontrib-openapi", "sphinxcontrib-spelling", "sphinxemoji", "tornado"] -test = ["jupyter-server (>=2.0.0)", "pytest (>=7.0)", "pytest-jupyter[server] (>=0.5.3)", "pytest-timeout"] - -[[package]] -name = "jupyterlab" -version = "4.4.9" -description = "JupyterLab computational environment" -optional = false -python-versions = ">=3.9" -files = [ - {file = "jupyterlab-4.4.9-py3-none-any.whl", hash = "sha256:394c902827350c017430a8370b9f40c03c098773084bc53930145c146d3d2cb2"}, - {file = "jupyterlab-4.4.9.tar.gz", hash = "sha256:ea55aca8269909016d5fde2dc09b97128bc931230183fe7e2920ede5154ad9c2"}, -] - -[package.dependencies] -async-lru = ">=1.0.0" -httpx = ">=0.25.0,<1" -ipykernel = ">=6.5.0,<6.30.0 || >6.30.0" -jinja2 = ">=3.0.3" -jupyter-core = "*" -jupyter-lsp = ">=2.0.0" -jupyter-server = ">=2.4.0,<3" -jupyterlab-server = ">=2.27.1,<3" -notebook-shim = ">=0.2" -packaging = "*" -setuptools = ">=41.1.0" -tornado = ">=6.2.0" -traitlets = "*" - -[package.extras] -dev = ["build", "bump2version", "coverage", "hatch", "pre-commit", "pytest-cov", "ruff (==0.11.4)"] -docs = ["jsx-lexer", "myst-parser", "pydata-sphinx-theme (>=0.13.0)", "pytest", "pytest-check-links", "pytest-jupyter", "sphinx (>=1.8,<8.2.0)", "sphinx-copybutton"] -docs-screenshots = ["altair (==5.5.0)", "ipython (==8.16.1)", "ipywidgets (==8.1.5)", "jupyterlab-geojson (==3.4.0)", "jupyterlab-language-pack-zh-cn (==4.3.post1)", "matplotlib (==3.10.0)", "nbconvert (>=7.0.0)", "pandas (==2.2.3)", "scipy (==1.15.1)", "vega-datasets (==0.9.0)"] -test = ["coverage", "pytest (>=7.0)", "pytest-check-links (>=0.7)", "pytest-console-scripts", "pytest-cov", "pytest-jupyter (>=0.5.3)", "pytest-timeout", "pytest-tornasync", "requests", "requests-cache", "virtualenv"] -upgrade-extension = ["copier (>=9,<10)", "jinja2-time (<0.3)", "pydantic (<3.0)", "pyyaml-include (<3.0)", "tomli-w (<2.0)"] - -[[package]] -name = "jupyterlab-pygments" -version = "0.3.0" -description = "Pygments theme using JupyterLab CSS variables" -optional = false -python-versions = ">=3.8" -files = [ - {file = "jupyterlab_pygments-0.3.0-py3-none-any.whl", hash = "sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780"}, - {file = "jupyterlab_pygments-0.3.0.tar.gz", hash = "sha256:721aca4d9029252b11cfa9d185e5b5af4d54772bb8072f9b7036f4170054d35d"}, -] - -[[package]] -name = "jupyterlab-server" -version = "2.27.3" -description = "A set of server components for JupyterLab and JupyterLab like applications." -optional = false -python-versions = ">=3.8" -files = [ - {file = "jupyterlab_server-2.27.3-py3-none-any.whl", hash = "sha256:e697488f66c3db49df675158a77b3b017520d772c6e1548c7d9bcc5df7944ee4"}, - {file = "jupyterlab_server-2.27.3.tar.gz", hash = "sha256:eb36caca59e74471988f0ae25c77945610b887f777255aa21f8065def9e51ed4"}, -] - -[package.dependencies] -babel = ">=2.10" -jinja2 = ">=3.0.3" -json5 = ">=0.9.0" -jsonschema = ">=4.18.0" -jupyter-server = ">=1.21,<3" -packaging = ">=21.3" -requests = ">=2.31" - -[package.extras] -docs = ["autodoc-traits", "jinja2 (<3.2.0)", "mistune (<4)", "myst-parser", "pydata-sphinx-theme", "sphinx", "sphinx-copybutton", "sphinxcontrib-openapi (>0.8)"] -openapi = ["openapi-core (>=0.18.0,<0.19.0)", "ruamel-yaml"] -test = ["hatch", "ipykernel", "openapi-core (>=0.18.0,<0.19.0)", "openapi-spec-validator (>=0.6.0,<0.8.0)", "pytest (>=7.0,<8)", "pytest-console-scripts", "pytest-cov", "pytest-jupyter[server] (>=0.6.2)", "pytest-timeout", "requests-mock", "ruamel-yaml", "sphinxcontrib-spelling", "strict-rfc3339", "werkzeug"] - -[[package]] -name = "jupyterlab-widgets" -version = "3.0.15" -description = "Jupyter interactive widgets for JupyterLab" -optional = false -python-versions = ">=3.7" -files = [ - {file = "jupyterlab_widgets-3.0.15-py3-none-any.whl", hash = "sha256:d59023d7d7ef71400d51e6fee9a88867f6e65e10a4201605d2d7f3e8f012a31c"}, - {file = "jupyterlab_widgets-3.0.15.tar.gz", hash = "sha256:2920888a0c2922351a9202817957a68c07d99673504d6cd37345299e971bb08b"}, -] - -[[package]] -name = "kiwisolver" -version = "1.4.9" -description = "A fast implementation of the Cassowary constraint solver" -optional = false -python-versions = ">=3.10" -files = [ - {file = "kiwisolver-1.4.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b4b4d74bda2b8ebf4da5bd42af11d02d04428b2c32846e4c2c93219df8a7987b"}, - {file = "kiwisolver-1.4.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fb3b8132019ea572f4611d770991000d7f58127560c4889729248eb5852a102f"}, - {file = "kiwisolver-1.4.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84fd60810829c27ae375114cd379da1fa65e6918e1da405f356a775d49a62bcf"}, - {file = "kiwisolver-1.4.9-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b78efa4c6e804ecdf727e580dbb9cba85624d2e1c6b5cb059c66290063bd99a9"}, - {file = "kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4efec7bcf21671db6a3294ff301d2fc861c31faa3c8740d1a94689234d1b415"}, - {file = "kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:90f47e70293fc3688b71271100a1a5453aa9944a81d27ff779c108372cf5567b"}, - {file = "kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fdca1def57a2e88ef339de1737a1449d6dbf5fab184c54a1fca01d541317154"}, - {file = "kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9cf554f21be770f5111a1690d42313e140355e687e05cf82cb23d0a721a64a48"}, - {file = "kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fc1795ac5cd0510207482c3d1d3ed781143383b8cfd36f5c645f3897ce066220"}, - {file = "kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:ccd09f20ccdbbd341b21a67ab50a119b64a403b09288c27481575105283c1586"}, - {file = "kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:540c7c72324d864406a009d72f5d6856f49693db95d1fbb46cf86febef873634"}, - {file = "kiwisolver-1.4.9-cp310-cp310-win_amd64.whl", hash = "sha256:ede8c6d533bc6601a47ad4046080d36b8fc99f81e6f1c17b0ac3c2dc91ac7611"}, - {file = "kiwisolver-1.4.9-cp310-cp310-win_arm64.whl", hash = "sha256:7b4da0d01ac866a57dd61ac258c5607b4cd677f63abaec7b148354d2b2cdd536"}, - {file = "kiwisolver-1.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eb14a5da6dc7642b0f3a18f13654847cd8b7a2550e2645a5bda677862b03ba16"}, - {file = "kiwisolver-1.4.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:39a219e1c81ae3b103643d2aedb90f1ef22650deb266ff12a19e7773f3e5f089"}, - {file = "kiwisolver-1.4.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2405a7d98604b87f3fc28b1716783534b1b4b8510d8142adca34ee0bc3c87543"}, - {file = "kiwisolver-1.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dc1ae486f9abcef254b5618dfb4113dd49f94c68e3e027d03cf0143f3f772b61"}, - {file = "kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a1f570ce4d62d718dce3f179ee78dac3b545ac16c0c04bb363b7607a949c0d1"}, - {file = "kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb27e7b78d716c591e88e0a09a2139c6577865d7f2e152488c2cc6257f460872"}, - {file = "kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:15163165efc2f627eb9687ea5f3a28137217d217ac4024893d753f46bce9de26"}, - {file = "kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bdee92c56a71d2b24c33a7d4c2856bd6419d017e08caa7802d2963870e315028"}, - {file = "kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:412f287c55a6f54b0650bd9b6dce5aceddb95864a1a90c87af16979d37c89771"}, - {file = "kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2c93f00dcba2eea70af2be5f11a830a742fe6b579a1d4e00f47760ef13be247a"}, - {file = "kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f117e1a089d9411663a3207ba874f31be9ac8eaa5b533787024dc07aeb74f464"}, - {file = "kiwisolver-1.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:be6a04e6c79819c9a8c2373317d19a96048e5a3f90bec587787e86a1153883c2"}, - {file = "kiwisolver-1.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:0ae37737256ba2de764ddc12aed4956460277f00c4996d51a197e72f62f5eec7"}, - {file = "kiwisolver-1.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ac5a486ac389dddcc5bef4f365b6ae3ffff2c433324fb38dd35e3fab7c957999"}, - {file = "kiwisolver-1.4.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2ba92255faa7309d06fe44c3a4a97efe1c8d640c2a79a5ef728b685762a6fd2"}, - {file = "kiwisolver-1.4.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a2899935e724dd1074cb568ce7ac0dce28b2cd6ab539c8e001a8578eb106d14"}, - {file = "kiwisolver-1.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f6008a4919fdbc0b0097089f67a1eb55d950ed7e90ce2cc3e640abadd2757a04"}, - {file = "kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67bb8b474b4181770f926f7b7d2f8c0248cbcb78b660fdd41a47054b28d2a752"}, - {file = "kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2327a4a30d3ee07d2fbe2e7933e8a37c591663b96ce42a00bc67461a87d7df77"}, - {file = "kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a08b491ec91b1d5053ac177afe5290adacf1f0f6307d771ccac5de30592d198"}, - {file = "kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8fc5c867c22b828001b6a38d2eaeb88160bf5783c6cb4a5e440efc981ce286d"}, - {file = "kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3b3115b2581ea35bb6d1f24a4c90af37e5d9b49dcff267eeed14c3893c5b86ab"}, - {file = "kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858e4c22fb075920b96a291928cb7dea5644e94c0ee4fcd5af7e865655e4ccf2"}, - {file = "kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ed0fecd28cc62c54b262e3736f8bb2512d8dcfdc2bcf08be5f47f96bf405b145"}, - {file = "kiwisolver-1.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:f68208a520c3d86ea51acf688a3e3002615a7f0238002cccc17affecc86a8a54"}, - {file = "kiwisolver-1.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:2c1a4f57df73965f3f14df20b80ee29e6a7930a57d2d9e8491a25f676e197c60"}, - {file = "kiwisolver-1.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5d0432ccf1c7ab14f9949eec60c5d1f924f17c037e9f8b33352fa05799359b8"}, - {file = "kiwisolver-1.4.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efb3a45b35622bb6c16dbfab491a8f5a391fe0e9d45ef32f4df85658232ca0e2"}, - {file = "kiwisolver-1.4.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a12cf6398e8a0a001a059747a1cbf24705e18fe413bc22de7b3d15c67cffe3f"}, - {file = "kiwisolver-1.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b67e6efbf68e077dd71d1a6b37e43e1a99d0bff1a3d51867d45ee8908b931098"}, - {file = "kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5656aa670507437af0207645273ccdfee4f14bacd7f7c67a4306d0dcaeaf6eed"}, - {file = "kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bfc08add558155345129c7803b3671cf195e6a56e7a12f3dde7c57d9b417f525"}, - {file = "kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:40092754720b174e6ccf9e845d0d8c7d8e12c3d71e7fc35f55f3813e96376f78"}, - {file = "kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:497d05f29a1300d14e02e6441cf0f5ee81c1ff5a304b0d9fb77423974684e08b"}, - {file = "kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bdd1a81a1860476eb41ac4bc1e07b3f07259e6d55bbf739b79c8aaedcf512799"}, - {file = "kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e6b93f13371d341afee3be9f7c5964e3fe61d5fa30f6a30eb49856935dfe4fc3"}, - {file = "kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d75aa530ccfaa593da12834b86a0724f58bff12706659baa9227c2ccaa06264c"}, - {file = "kiwisolver-1.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:dd0a578400839256df88c16abddf9ba14813ec5f21362e1fe65022e00c883d4d"}, - {file = "kiwisolver-1.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:d4188e73af84ca82468f09cadc5ac4db578109e52acb4518d8154698d3a87ca2"}, - {file = "kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:5a0f2724dfd4e3b3ac5a82436a8e6fd16baa7d507117e4279b660fe8ca38a3a1"}, - {file = "kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b11d6a633e4ed84fc0ddafd4ebfd8ea49b3f25082c04ad12b8315c11d504dc1"}, - {file = "kiwisolver-1.4.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61874cdb0a36016354853593cffc38e56fc9ca5aa97d2c05d3dcf6922cd55a11"}, - {file = "kiwisolver-1.4.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:60c439763a969a6af93b4881db0eed8fadf93ee98e18cbc35bc8da868d0c4f0c"}, - {file = "kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92a2f997387a1b79a75e7803aa7ded2cfbe2823852ccf1ba3bcf613b62ae3197"}, - {file = "kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31d512c812daea6d8b3be3b2bfcbeb091dbb09177706569bcfc6240dcf8b41c"}, - {file = "kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:52a15b0f35dad39862d376df10c5230155243a2c1a436e39eb55623ccbd68185"}, - {file = "kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a30fd6fdef1430fd9e1ba7b3398b5ee4e2887783917a687d86ba69985fb08748"}, - {file = "kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cc9617b46837c6468197b5945e196ee9ca43057bb7d9d1ae688101e4e1dddf64"}, - {file = "kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:0ab74e19f6a2b027ea4f845a78827969af45ce790e6cb3e1ebab71bdf9f215ff"}, - {file = "kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dba5ee5d3981160c28d5490f0d1b7ed730c22470ff7f6cc26cfcfaacb9896a07"}, - {file = "kiwisolver-1.4.9-cp313-cp313t-win_arm64.whl", hash = "sha256:0749fd8f4218ad2e851e11cc4dc05c7cbc0cbc4267bdfdb31782e65aace4ee9c"}, - {file = "kiwisolver-1.4.9-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:9928fe1eb816d11ae170885a74d074f57af3a0d65777ca47e9aeb854a1fba386"}, - {file = "kiwisolver-1.4.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d0005b053977e7b43388ddec89fa567f43d4f6d5c2c0affe57de5ebf290dc552"}, - {file = "kiwisolver-1.4.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2635d352d67458b66fd0667c14cb1d4145e9560d503219034a18a87e971ce4f3"}, - {file = "kiwisolver-1.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:767c23ad1c58c9e827b649a9ab7809fd5fd9db266a9cf02b0e926ddc2c680d58"}, - {file = "kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72d0eb9fba308b8311685c2268cf7d0a0639a6cd027d8128659f72bdd8a024b4"}, - {file = "kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f68e4f3eeca8fb22cc3d731f9715a13b652795ef657a13df1ad0c7dc0e9731df"}, - {file = "kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d84cd4061ae292d8ac367b2c3fa3aad11cb8625a95d135fe93f286f914f3f5a6"}, - {file = "kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a60ea74330b91bd22a29638940d115df9dc00af5035a9a2a6ad9399ffb4ceca5"}, - {file = "kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ce6a3a4e106cf35c2d9c4fa17c05ce0b180db622736845d4315519397a77beaf"}, - {file = "kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:77937e5e2a38a7b48eef0585114fe7930346993a88060d0bf886086d2aa49ef5"}, - {file = "kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:24c175051354f4a28c5d6a31c93906dc653e2bf234e8a4bbfb964892078898ce"}, - {file = "kiwisolver-1.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:0763515d4df10edf6d06a3c19734e2566368980d21ebec439f33f9eb936c07b7"}, - {file = "kiwisolver-1.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:0e4e2bf29574a6a7b7f6cb5fa69293b9f96c928949ac4a53ba3f525dffb87f9c"}, - {file = "kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d976bbb382b202f71c67f77b0ac11244021cfa3f7dfd9e562eefcea2df711548"}, - {file = "kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2489e4e5d7ef9a1c300a5e0196e43d9c739f066ef23270607d45aba368b91f2d"}, - {file = "kiwisolver-1.4.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e2ea9f7ab7fbf18fffb1b5434ce7c69a07582f7acc7717720f1d69f3e806f90c"}, - {file = "kiwisolver-1.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b34e51affded8faee0dfdb705416153819d8ea9250bbbf7ea1b249bdeb5f1122"}, - {file = "kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8aacd3d4b33b772542b2e01beb50187536967b514b00003bdda7589722d2a64"}, - {file = "kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7cf974dd4e35fa315563ac99d6287a1024e4dc2077b8a7d7cd3d2fb65d283134"}, - {file = "kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85bd218b5ecfbee8c8a82e121802dcb519a86044c9c3b2e4aef02fa05c6da370"}, - {file = "kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0856e241c2d3df4efef7c04a1e46b1936b6120c9bcf36dd216e3acd84bc4fb21"}, - {file = "kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9af39d6551f97d31a4deebeac6f45b156f9755ddc59c07b402c148f5dbb6482a"}, - {file = "kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:bb4ae2b57fc1d8cbd1cf7b1d9913803681ffa903e7488012be5b76dedf49297f"}, - {file = "kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:aedff62918805fb62d43a4aa2ecd4482c380dc76cd31bd7c8878588a61bd0369"}, - {file = "kiwisolver-1.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:1fa333e8b2ce4d9660f2cda9c0e1b6bafcfb2457a9d259faa82289e73ec24891"}, - {file = "kiwisolver-1.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:4a48a2ce79d65d363597ef7b567ce3d14d68783d2b2263d98db3d9477805ba32"}, - {file = "kiwisolver-1.4.9-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4d1d9e582ad4d63062d34077a9a1e9f3c34088a2ec5135b1f7190c07cf366527"}, - {file = "kiwisolver-1.4.9-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:deed0c7258ceb4c44ad5ec7d9918f9f14fd05b2be86378d86cf50e63d1e7b771"}, - {file = "kiwisolver-1.4.9-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0a590506f303f512dff6b7f75fd2fd18e16943efee932008fe7140e5fa91d80e"}, - {file = "kiwisolver-1.4.9-pp310-pypy310_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e09c2279a4d01f099f52d5c4b3d9e208e91edcbd1a175c9662a8b16e000fece9"}, - {file = "kiwisolver-1.4.9-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c9e7cdf45d594ee04d5be1b24dd9d49f3d1590959b2271fb30b5ca2b262c00fb"}, - {file = "kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:720e05574713db64c356e86732c0f3c5252818d05f9df320f0ad8380641acea5"}, - {file = "kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:17680d737d5335b552994a2008fab4c851bcd7de33094a82067ef3a576ff02fa"}, - {file = "kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:85b5352f94e490c028926ea567fc569c52ec79ce131dadb968d3853e809518c2"}, - {file = "kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:464415881e4801295659462c49461a24fb107c140de781d55518c4b80cb6790f"}, - {file = "kiwisolver-1.4.9-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:fb940820c63a9590d31d88b815e7a3aa5915cad3ce735ab45f0c730b39547de1"}, - {file = "kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d"}, -] - -[[package]] -name = "lark" -version = "1.3.0" -description = "a modern parsing library" -optional = false -python-versions = ">=3.8" -files = [ - {file = "lark-1.3.0-py3-none-any.whl", hash = "sha256:80661f261fb2584a9828a097a2432efd575af27d20be0fd35d17f0fe37253831"}, - {file = "lark-1.3.0.tar.gz", hash = "sha256:9a3839d0ca5e1faf7cfa3460e420e859b66bcbde05b634e73c369c8244c5fa48"}, -] - -[package.extras] -atomic-cache = ["atomicwrites"] -interegular = ["interegular (>=0.3.1,<0.4.0)"] -nearley = ["js2py"] -regex = ["regex"] - -[[package]] -name = "markupsafe" -version = "3.0.3" -description = "Safely add untrusted strings to HTML/XML markup." -optional = false -python-versions = ">=3.9" -files = [ - {file = "markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559"}, - {file = "markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419"}, - {file = "markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695"}, - {file = "markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591"}, - {file = "markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c"}, - {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f"}, - {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6"}, - {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1"}, - {file = "markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa"}, - {file = "markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8"}, - {file = "markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1"}, - {file = "markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad"}, - {file = "markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a"}, - {file = "markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50"}, - {file = "markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf"}, - {file = "markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f"}, - {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a"}, - {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115"}, - {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a"}, - {file = "markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19"}, - {file = "markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01"}, - {file = "markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c"}, - {file = "markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e"}, - {file = "markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce"}, - {file = "markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d"}, - {file = "markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d"}, - {file = "markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a"}, - {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b"}, - {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f"}, - {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b"}, - {file = "markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d"}, - {file = "markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c"}, - {file = "markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f"}, - {file = "markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795"}, - {file = "markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219"}, - {file = "markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6"}, - {file = "markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676"}, - {file = "markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9"}, - {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1"}, - {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc"}, - {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12"}, - {file = "markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed"}, - {file = "markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5"}, - {file = "markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485"}, - {file = "markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73"}, - {file = "markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37"}, - {file = "markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19"}, - {file = "markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025"}, - {file = "markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6"}, - {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f"}, - {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb"}, - {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009"}, - {file = "markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354"}, - {file = "markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218"}, - {file = "markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287"}, - {file = "markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe"}, - {file = "markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026"}, - {file = "markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737"}, - {file = "markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97"}, - {file = "markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d"}, - {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda"}, - {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf"}, - {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe"}, - {file = "markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9"}, - {file = "markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581"}, - {file = "markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4"}, - {file = "markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab"}, - {file = "markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175"}, - {file = "markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634"}, - {file = "markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50"}, - {file = "markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e"}, - {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5"}, - {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523"}, - {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc"}, - {file = "markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d"}, - {file = "markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9"}, - {file = "markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa"}, - {file = "markupsafe-3.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26"}, - {file = "markupsafe-3.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc"}, - {file = "markupsafe-3.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c"}, - {file = "markupsafe-3.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42"}, - {file = "markupsafe-3.0.3-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b"}, - {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758"}, - {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2"}, - {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d"}, - {file = "markupsafe-3.0.3-cp39-cp39-win32.whl", hash = "sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7"}, - {file = "markupsafe-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e"}, - {file = "markupsafe-3.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8"}, - {file = "markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698"}, -] - -[[package]] -name = "matplotlib" -version = "3.10.7" -description = "Python plotting package" -optional = false -python-versions = ">=3.10" -files = [ - {file = "matplotlib-3.10.7-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:7ac81eee3b7c266dd92cee1cd658407b16c57eed08c7421fa354ed68234de380"}, - {file = "matplotlib-3.10.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:667ecd5d8d37813a845053d8f5bf110b534c3c9f30e69ebd25d4701385935a6d"}, - {file = "matplotlib-3.10.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc1c51b846aca49a5a8b44fbba6a92d583a35c64590ad9e1e950dc88940a4297"}, - {file = "matplotlib-3.10.7-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a11c2e9e72e7de09b7b72e62f3df23317c888299c875e2b778abf1eda8c0a42"}, - {file = "matplotlib-3.10.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f19410b486fdd139885ace124e57f938c1e6a3210ea13dd29cab58f5d4bc12c7"}, - {file = "matplotlib-3.10.7-cp310-cp310-win_amd64.whl", hash = "sha256:b498e9e4022f93de2d5a37615200ca01297ceebbb56fe4c833f46862a490f9e3"}, - {file = "matplotlib-3.10.7-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:53b492410a6cd66c7a471de6c924f6ede976e963c0f3097a3b7abfadddc67d0a"}, - {file = "matplotlib-3.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d9749313deb729f08207718d29c86246beb2ea3fdba753595b55901dee5d2fd6"}, - {file = "matplotlib-3.10.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2222c7ba2cbde7fe63032769f6eb7e83ab3227f47d997a8453377709b7fe3a5a"}, - {file = "matplotlib-3.10.7-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e91f61a064c92c307c5a9dc8c05dc9f8a68f0a3be199d9a002a0622e13f874a1"}, - {file = "matplotlib-3.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6f1851eab59ca082c95df5a500106bad73672645625e04538b3ad0f69471ffcc"}, - {file = "matplotlib-3.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:6516ce375109c60ceec579e699524e9d504cd7578506f01150f7a6bc174a775e"}, - {file = "matplotlib-3.10.7-cp311-cp311-win_arm64.whl", hash = "sha256:b172db79759f5f9bc13ef1c3ef8b9ee7b37b0247f987fbbbdaa15e4f87fd46a9"}, - {file = "matplotlib-3.10.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7a0edb7209e21840e8361e91ea84ea676658aa93edd5f8762793dec77a4a6748"}, - {file = "matplotlib-3.10.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c380371d3c23e0eadf8ebff114445b9f970aff2010198d498d4ab4c3b41eea4f"}, - {file = "matplotlib-3.10.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d5f256d49fea31f40f166a5e3131235a5d2f4b7f44520b1cf0baf1ce568ccff0"}, - {file = "matplotlib-3.10.7-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11ae579ac83cdf3fb72573bb89f70e0534de05266728740d478f0f818983c695"}, - {file = "matplotlib-3.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4c14b6acd16cddc3569a2d515cfdd81c7a68ac5639b76548cfc1a9e48b20eb65"}, - {file = "matplotlib-3.10.7-cp312-cp312-win_amd64.whl", hash = "sha256:0d8c32b7ea6fb80b1aeff5a2ceb3fb9778e2759e899d9beff75584714afcc5ee"}, - {file = "matplotlib-3.10.7-cp312-cp312-win_arm64.whl", hash = "sha256:5f3f6d315dcc176ba7ca6e74c7768fb7e4cf566c49cb143f6bc257b62e634ed8"}, - {file = "matplotlib-3.10.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1d9d3713a237970569156cfb4de7533b7c4eacdd61789726f444f96a0d28f57f"}, - {file = "matplotlib-3.10.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:37a1fea41153dd6ee061d21ab69c9cf2cf543160b1b85d89cd3d2e2a7902ca4c"}, - {file = "matplotlib-3.10.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b3c4ea4948d93c9c29dc01c0c23eef66f2101bf75158c291b88de6525c55c3d1"}, - {file = "matplotlib-3.10.7-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22df30ffaa89f6643206cf13877191c63a50e8f800b038bc39bee9d2d4957632"}, - {file = "matplotlib-3.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b69676845a0a66f9da30e87f48be36734d6748024b525ec4710be40194282c84"}, - {file = "matplotlib-3.10.7-cp313-cp313-win_amd64.whl", hash = "sha256:744991e0cc863dd669c8dc9136ca4e6e0082be2070b9d793cbd64bec872a6815"}, - {file = "matplotlib-3.10.7-cp313-cp313-win_arm64.whl", hash = "sha256:fba2974df0bf8ce3c995fa84b79cde38326e0f7b5409e7a3a481c1141340bcf7"}, - {file = "matplotlib-3.10.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:932c55d1fa7af4423422cb6a492a31cbcbdbe68fd1a9a3f545aa5e7a143b5355"}, - {file = "matplotlib-3.10.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e38c2d581d62ee729a6e144c47a71b3f42fb4187508dbbf4fe71d5612c3433b"}, - {file = "matplotlib-3.10.7-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:786656bb13c237bbcebcd402f65f44dd61ead60ee3deb045af429d889c8dbc67"}, - {file = "matplotlib-3.10.7-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09d7945a70ea43bf9248f4b6582734c2fe726723204a76eca233f24cffc7ef67"}, - {file = "matplotlib-3.10.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d0b181e9fa8daf1d9f2d4c547527b167cb8838fc587deabca7b5c01f97199e84"}, - {file = "matplotlib-3.10.7-cp313-cp313t-win_amd64.whl", hash = "sha256:31963603041634ce1a96053047b40961f7a29eb8f9a62e80cc2c0427aa1d22a2"}, - {file = "matplotlib-3.10.7-cp313-cp313t-win_arm64.whl", hash = "sha256:aebed7b50aa6ac698c90f60f854b47e48cd2252b30510e7a1feddaf5a3f72cbf"}, - {file = "matplotlib-3.10.7-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d883460c43e8c6b173fef244a2341f7f7c0e9725c7fe68306e8e44ed9c8fb100"}, - {file = "matplotlib-3.10.7-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:07124afcf7a6504eafcb8ce94091c5898bbdd351519a1beb5c45f7a38c67e77f"}, - {file = "matplotlib-3.10.7-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c17398b709a6cce3d9fdb1595c33e356d91c098cd9486cb2cc21ea2ea418e715"}, - {file = "matplotlib-3.10.7-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7146d64f561498764561e9cd0ed64fcf582e570fc519e6f521e2d0cfd43365e1"}, - {file = "matplotlib-3.10.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:90ad854c0a435da3104c01e2c6f0028d7e719b690998a2333d7218db80950722"}, - {file = "matplotlib-3.10.7-cp314-cp314-win_amd64.whl", hash = "sha256:4645fc5d9d20ffa3a39361fcdbcec731382763b623b72627806bf251b6388866"}, - {file = "matplotlib-3.10.7-cp314-cp314-win_arm64.whl", hash = "sha256:9257be2f2a03415f9105c486d304a321168e61ad450f6153d77c69504ad764bb"}, - {file = "matplotlib-3.10.7-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1e4bbad66c177a8fdfa53972e5ef8be72a5f27e6a607cec0d8579abd0f3102b1"}, - {file = "matplotlib-3.10.7-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d8eb7194b084b12feb19142262165832fc6ee879b945491d1c3d4660748020c4"}, - {file = "matplotlib-3.10.7-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4d41379b05528091f00e1728004f9a8d7191260f3862178b88e8fd770206318"}, - {file = "matplotlib-3.10.7-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4a74f79fafb2e177f240579bc83f0b60f82cc47d2f1d260f422a0627207008ca"}, - {file = "matplotlib-3.10.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:702590829c30aada1e8cef0568ddbffa77ca747b4d6e36c6d173f66e301f89cc"}, - {file = "matplotlib-3.10.7-cp314-cp314t-win_amd64.whl", hash = "sha256:f79d5de970fc90cd5591f60053aecfce1fcd736e0303d9f0bf86be649fa68fb8"}, - {file = "matplotlib-3.10.7-cp314-cp314t-win_arm64.whl", hash = "sha256:cb783436e47fcf82064baca52ce748af71725d0352e1d31564cbe9c95df92b9c"}, - {file = "matplotlib-3.10.7-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5c09cf8f2793f81368f49f118b6f9f937456362bee282eac575cca7f84cda537"}, - {file = "matplotlib-3.10.7-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:de66744b2bb88d5cd27e80dfc2ec9f0517d0a46d204ff98fe9e5f2864eb67657"}, - {file = "matplotlib-3.10.7-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:53cc80662dd197ece414dd5b66e07370201515a3eaf52e7c518c68c16814773b"}, - {file = "matplotlib-3.10.7-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:15112bcbaef211bd663fa935ec33313b948e214454d949b723998a43357b17b0"}, - {file = "matplotlib-3.10.7-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d2a959c640cdeecdd2ec3136e8ea0441da59bcaf58d67e9c590740addba2cb68"}, - {file = "matplotlib-3.10.7-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3886e47f64611046bc1db523a09dd0a0a6bed6081e6f90e13806dd1d1d1b5e91"}, - {file = "matplotlib-3.10.7.tar.gz", hash = "sha256:a06ba7e2a2ef9131c79c49e63dad355d2d878413a0376c1727c8b9335ff731c7"}, -] - -[package.dependencies] -contourpy = ">=1.0.1" -cycler = ">=0.10" -fonttools = ">=4.22.0" -kiwisolver = ">=1.3.1" -numpy = ">=1.23" -packaging = ">=20.0" -pillow = ">=8" -pyparsing = ">=3" -python-dateutil = ">=2.7" - -[package.extras] -dev = ["meson-python (>=0.13.1,<0.17.0)", "pybind11 (>=2.13.2,!=2.13.3)", "setuptools (>=64)", "setuptools_scm (>=7)"] - -[[package]] -name = "matplotlib-inline" -version = "0.1.7" -description = "Inline Matplotlib backend for Jupyter" -optional = false -python-versions = ">=3.8" -files = [ - {file = "matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca"}, - {file = "matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90"}, -] - -[package.dependencies] -traitlets = "*" - -[[package]] -name = "mistune" -version = "3.1.4" -description = "A sane and fast Markdown parser with useful plugins and renderers" -optional = false -python-versions = ">=3.8" -files = [ - {file = "mistune-3.1.4-py3-none-any.whl", hash = "sha256:93691da911e5d9d2e23bc54472892aff676df27a75274962ff9edc210364266d"}, - {file = "mistune-3.1.4.tar.gz", hash = "sha256:b5a7f801d389f724ec702840c11d8fc48f2b33519102fc7ee739e8177b672164"}, -] - -[[package]] -name = "nbclient" -version = "0.10.2" -description = "A client library for executing notebooks. Formerly nbconvert's ExecutePreprocessor." -optional = false -python-versions = ">=3.9.0" -files = [ - {file = "nbclient-0.10.2-py3-none-any.whl", hash = "sha256:4ffee11e788b4a27fabeb7955547e4318a5298f34342a4bfd01f2e1faaeadc3d"}, - {file = "nbclient-0.10.2.tar.gz", hash = "sha256:90b7fc6b810630db87a6d0c2250b1f0ab4cf4d3c27a299b0cde78a4ed3fd9193"}, -] - -[package.dependencies] -jupyter-client = ">=6.1.12" -jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" -nbformat = ">=5.1" -traitlets = ">=5.4" - -[package.extras] -dev = ["pre-commit"] -docs = ["autodoc-traits", "flaky", "ipykernel (>=6.19.3)", "ipython", "ipywidgets", "mock", "moto", "myst-parser", "nbconvert (>=7.1.0)", "pytest (>=7.0,<8)", "pytest-asyncio", "pytest-cov (>=4.0)", "sphinx (>=1.7)", "sphinx-book-theme", "sphinxcontrib-spelling", "testpath", "xmltodict"] -test = ["flaky", "ipykernel (>=6.19.3)", "ipython", "ipywidgets", "nbconvert (>=7.1.0)", "pytest (>=7.0,<8)", "pytest-asyncio", "pytest-cov (>=4.0)", "testpath", "xmltodict"] - -[[package]] -name = "nbconvert" -version = "7.16.6" -description = "Converting Jupyter Notebooks (.ipynb files) to other formats. Output formats include asciidoc, html, latex, markdown, pdf, py, rst, script. nbconvert can be used both as a Python library (`import nbconvert`) or as a command line tool (invoked as `jupyter nbconvert ...`)." -optional = false -python-versions = ">=3.8" -files = [ - {file = "nbconvert-7.16.6-py3-none-any.whl", hash = "sha256:1375a7b67e0c2883678c48e506dc320febb57685e5ee67faa51b18a90f3a712b"}, - {file = "nbconvert-7.16.6.tar.gz", hash = "sha256:576a7e37c6480da7b8465eefa66c17844243816ce1ccc372633c6b71c3c0f582"}, -] - -[package.dependencies] -beautifulsoup4 = "*" -bleach = {version = "!=5.0.0", extras = ["css"]} -defusedxml = "*" -jinja2 = ">=3.0" -jupyter-core = ">=4.7" -jupyterlab-pygments = "*" -markupsafe = ">=2.0" -mistune = ">=2.0.3,<4" -nbclient = ">=0.5.0" -nbformat = ">=5.7" -packaging = "*" -pandocfilters = ">=1.4.1" -pygments = ">=2.4.1" -traitlets = ">=5.1" - -[package.extras] -all = ["flaky", "ipykernel", "ipython", "ipywidgets (>=7.5)", "myst-parser", "nbsphinx (>=0.2.12)", "playwright", "pydata-sphinx-theme", "pyqtwebengine (>=5.15)", "pytest (>=7)", "sphinx (==5.0.2)", "sphinxcontrib-spelling", "tornado (>=6.1)"] -docs = ["ipykernel", "ipython", "myst-parser", "nbsphinx (>=0.2.12)", "pydata-sphinx-theme", "sphinx (==5.0.2)", "sphinxcontrib-spelling"] -qtpdf = ["pyqtwebengine (>=5.15)"] -qtpng = ["pyqtwebengine (>=5.15)"] -serve = ["tornado (>=6.1)"] -test = ["flaky", "ipykernel", "ipywidgets (>=7.5)", "pytest (>=7)"] -webpdf = ["playwright"] - -[[package]] -name = "nbformat" -version = "5.10.4" -description = "The Jupyter Notebook format" -optional = false -python-versions = ">=3.8" -files = [ - {file = "nbformat-5.10.4-py3-none-any.whl", hash = "sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b"}, - {file = "nbformat-5.10.4.tar.gz", hash = "sha256:322168b14f937a5d11362988ecac2a4952d3d8e3a2cbeb2319584631226d5b3a"}, -] - -[package.dependencies] -fastjsonschema = ">=2.15" -jsonschema = ">=2.6" -jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" -traitlets = ">=5.1" - -[package.extras] -docs = ["myst-parser", "pydata-sphinx-theme", "sphinx", "sphinxcontrib-github-alt", "sphinxcontrib-spelling"] -test = ["pep440", "pre-commit", "pytest", "testpath"] - -[[package]] -name = "nest-asyncio" -version = "1.6.0" -description = "Patch asyncio to allow nested event loops" -optional = false -python-versions = ">=3.5" -files = [ - {file = "nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c"}, - {file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"}, -] - -[[package]] -name = "notebook" -version = "7.4.7" -description = "Jupyter Notebook - A web-based notebook environment for interactive computing" -optional = false -python-versions = ">=3.8" -files = [ - {file = "notebook-7.4.7-py3-none-any.whl", hash = "sha256:362b7c95527f7dd3c4c84d410b782872fd9c734fb2524c11dd92758527b6eda6"}, - {file = "notebook-7.4.7.tar.gz", hash = "sha256:3f0a04027dfcee8a876de48fba13ab77ec8c12f72f848a222ed7f5081b9e342a"}, -] - -[package.dependencies] -jupyter-server = ">=2.4.0,<3" -jupyterlab = ">=4.4.9,<4.5" -jupyterlab-server = ">=2.27.1,<3" -notebook-shim = ">=0.2,<0.3" -tornado = ">=6.2.0" - -[package.extras] -dev = ["hatch", "pre-commit"] -docs = ["myst-parser", "nbsphinx", "pydata-sphinx-theme", "sphinx (>=1.3.6)", "sphinxcontrib-github-alt", "sphinxcontrib-spelling"] -test = ["importlib-resources (>=5.0)", "ipykernel", "jupyter-server[test] (>=2.4.0,<3)", "jupyterlab-server[test] (>=2.27.1,<3)", "nbval", "pytest (>=7.0)", "pytest-console-scripts", "pytest-timeout", "pytest-tornasync", "requests"] - -[[package]] -name = "notebook-shim" -version = "0.2.4" -description = "A shim layer for notebook traits and config" -optional = false -python-versions = ">=3.7" -files = [ - {file = "notebook_shim-0.2.4-py3-none-any.whl", hash = "sha256:411a5be4e9dc882a074ccbcae671eda64cceb068767e9a3419096986560e1cef"}, - {file = "notebook_shim-0.2.4.tar.gz", hash = "sha256:b4b2cfa1b65d98307ca24361f5b30fe785b53c3fd07b7a47e89acb5e6ac638cb"}, -] - -[package.dependencies] -jupyter-server = ">=1.8,<3" - -[package.extras] -test = ["pytest", "pytest-console-scripts", "pytest-jupyter", "pytest-tornasync"] - -[[package]] -name = "numpy" -version = "2.3.3" -description = "Fundamental package for array computing in Python" -optional = false -python-versions = ">=3.11" -files = [ - {file = "numpy-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0ffc4f5caba7dfcbe944ed674b7eef683c7e94874046454bb79ed7ee0236f59d"}, - {file = "numpy-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e7e946c7170858a0295f79a60214424caac2ffdb0063d4d79cb681f9aa0aa569"}, - {file = "numpy-2.3.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:cd4260f64bc794c3390a63bf0728220dd1a68170c169088a1e0dfa2fde1be12f"}, - {file = "numpy-2.3.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:f0ddb4b96a87b6728df9362135e764eac3cfa674499943ebc44ce96c478ab125"}, - {file = "numpy-2.3.3-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:afd07d377f478344ec6ca2b8d4ca08ae8bd44706763d1efb56397de606393f48"}, - {file = "numpy-2.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bc92a5dedcc53857249ca51ef29f5e5f2f8c513e22cfb90faeb20343b8c6f7a6"}, - {file = "numpy-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7af05ed4dc19f308e1d9fc759f36f21921eb7bbfc82843eeec6b2a2863a0aefa"}, - {file = "numpy-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:433bf137e338677cebdd5beac0199ac84712ad9d630b74eceeb759eaa45ddf30"}, - {file = "numpy-2.3.3-cp311-cp311-win32.whl", hash = "sha256:eb63d443d7b4ffd1e873f8155260d7f58e7e4b095961b01c91062935c2491e57"}, - {file = "numpy-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:ec9d249840f6a565f58d8f913bccac2444235025bbb13e9a4681783572ee3caa"}, - {file = "numpy-2.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:74c2a948d02f88c11a3c075d9733f1ae67d97c6bdb97f2bb542f980458b257e7"}, - {file = "numpy-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cfdd09f9c84a1a934cde1eec2267f0a43a7cd44b2cca4ff95b7c0d14d144b0bf"}, - {file = "numpy-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb32e3cf0f762aee47ad1ddc6672988f7f27045b0783c887190545baba73aa25"}, - {file = "numpy-2.3.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:396b254daeb0a57b1fe0ecb5e3cff6fa79a380fa97c8f7781a6d08cd429418fe"}, - {file = "numpy-2.3.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:067e3d7159a5d8f8a0b46ee11148fc35ca9b21f61e3c49fbd0a027450e65a33b"}, - {file = "numpy-2.3.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c02d0629d25d426585fb2e45a66154081b9fa677bc92a881ff1d216bc9919a8"}, - {file = "numpy-2.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9192da52b9745f7f0766531dcfa978b7763916f158bb63bdb8a1eca0068ab20"}, - {file = "numpy-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cd7de500a5b66319db419dc3c345244404a164beae0d0937283b907d8152e6ea"}, - {file = "numpy-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:93d4962d8f82af58f0b2eb85daaf1b3ca23fe0a85d0be8f1f2b7bb46034e56d7"}, - {file = "numpy-2.3.3-cp312-cp312-win32.whl", hash = "sha256:5534ed6b92f9b7dca6c0a19d6df12d41c68b991cef051d108f6dbff3babc4ebf"}, - {file = "numpy-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:497d7cad08e7092dba36e3d296fe4c97708c93daf26643a1ae4b03f6294d30eb"}, - {file = "numpy-2.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:ca0309a18d4dfea6fc6262a66d06c26cfe4640c3926ceec90e57791a82b6eee5"}, - {file = "numpy-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f5415fb78995644253370985342cd03572ef8620b934da27d77377a2285955bf"}, - {file = "numpy-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d00de139a3324e26ed5b95870ce63be7ec7352171bc69a4cf1f157a48e3eb6b7"}, - {file = "numpy-2.3.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:9dc13c6a5829610cc07422bc74d3ac083bd8323f14e2827d992f9e52e22cd6a6"}, - {file = "numpy-2.3.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d79715d95f1894771eb4e60fb23f065663b2298f7d22945d66877aadf33d00c7"}, - {file = "numpy-2.3.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:952cfd0748514ea7c3afc729a0fc639e61655ce4c55ab9acfab14bda4f402b4c"}, - {file = "numpy-2.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5b83648633d46f77039c29078751f80da65aa64d5622a3cd62aaef9d835b6c93"}, - {file = "numpy-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b001bae8cea1c7dfdb2ae2b017ed0a6f2102d7a70059df1e338e307a4c78a8ae"}, - {file = "numpy-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8e9aced64054739037d42fb84c54dd38b81ee238816c948c8f3ed134665dcd86"}, - {file = "numpy-2.3.3-cp313-cp313-win32.whl", hash = "sha256:9591e1221db3f37751e6442850429b3aabf7026d3b05542d102944ca7f00c8a8"}, - {file = "numpy-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f0dadeb302887f07431910f67a14d57209ed91130be0adea2f9793f1a4f817cf"}, - {file = "numpy-2.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:3c7cf302ac6e0b76a64c4aecf1a09e51abd9b01fc7feee80f6c43e3ab1b1dbc5"}, - {file = "numpy-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:eda59e44957d272846bb407aad19f89dc6f58fecf3504bd144f4c5cf81a7eacc"}, - {file = "numpy-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:823d04112bc85ef5c4fda73ba24e6096c8f869931405a80aa8b0e604510a26bc"}, - {file = "numpy-2.3.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:40051003e03db4041aa325da2a0971ba41cf65714e65d296397cc0e32de6018b"}, - {file = "numpy-2.3.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:6ee9086235dd6ab7ae75aba5662f582a81ced49f0f1c6de4260a78d8f2d91a19"}, - {file = "numpy-2.3.3-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94fcaa68757c3e2e668ddadeaa86ab05499a70725811e582b6a9858dd472fb30"}, - {file = "numpy-2.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da1a74b90e7483d6ce5244053399a614b1d6b7bc30a60d2f570e5071f8959d3e"}, - {file = "numpy-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2990adf06d1ecee3b3dcbb4977dfab6e9f09807598d647f04d385d29e7a3c3d3"}, - {file = "numpy-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ed635ff692483b8e3f0fcaa8e7eb8a75ee71aa6d975388224f70821421800cea"}, - {file = "numpy-2.3.3-cp313-cp313t-win32.whl", hash = "sha256:a333b4ed33d8dc2b373cc955ca57babc00cd6f9009991d9edc5ddbc1bac36bcd"}, - {file = "numpy-2.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:4384a169c4d8f97195980815d6fcad04933a7e1ab3b530921c3fef7a1c63426d"}, - {file = "numpy-2.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:75370986cc0bc66f4ce5110ad35aae6d182cc4ce6433c40ad151f53690130bf1"}, - {file = "numpy-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cd052f1fa6a78dee696b58a914b7229ecfa41f0a6d96dc663c1220a55e137593"}, - {file = "numpy-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:414a97499480067d305fcac9716c29cf4d0d76db6ebf0bf3cbce666677f12652"}, - {file = "numpy-2.3.3-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:50a5fe69f135f88a2be9b6ca0481a68a136f6febe1916e4920e12f1a34e708a7"}, - {file = "numpy-2.3.3-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:b912f2ed2b67a129e6a601e9d93d4fa37bef67e54cac442a2f588a54afe5c67a"}, - {file = "numpy-2.3.3-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9e318ee0596d76d4cb3d78535dc005fa60e5ea348cd131a51e99d0bdbe0b54fe"}, - {file = "numpy-2.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce020080e4a52426202bdb6f7691c65bb55e49f261f31a8f506c9f6bc7450421"}, - {file = "numpy-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e6687dc183aa55dae4a705b35f9c0f8cb178bcaa2f029b241ac5356221d5c021"}, - {file = "numpy-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d8f3b1080782469fdc1718c4ed1d22549b5fb12af0d57d35e992158a772a37cf"}, - {file = "numpy-2.3.3-cp314-cp314-win32.whl", hash = "sha256:cb248499b0bc3be66ebd6578b83e5acacf1d6cb2a77f2248ce0e40fbec5a76d0"}, - {file = "numpy-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:691808c2b26b0f002a032c73255d0bd89751425f379f7bcd22d140db593a96e8"}, - {file = "numpy-2.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:9ad12e976ca7b10f1774b03615a2a4bab8addce37ecc77394d8e986927dc0dfe"}, - {file = "numpy-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9cc48e09feb11e1db00b320e9d30a4151f7369afb96bd0e48d942d09da3a0d00"}, - {file = "numpy-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:901bf6123879b7f251d3631967fd574690734236075082078e0571977c6a8e6a"}, - {file = "numpy-2.3.3-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:7f025652034199c301049296b59fa7d52c7e625017cae4c75d8662e377bf487d"}, - {file = "numpy-2.3.3-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:533ca5f6d325c80b6007d4d7fb1984c303553534191024ec6a524a4c92a5935a"}, - {file = "numpy-2.3.3-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0edd58682a399824633b66885d699d7de982800053acf20be1eaa46d92009c54"}, - {file = "numpy-2.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:367ad5d8fbec5d9296d18478804a530f1191e24ab4d75ab408346ae88045d25e"}, - {file = "numpy-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8f6ac61a217437946a1fa48d24c47c91a0c4f725237871117dea264982128097"}, - {file = "numpy-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:179a42101b845a816d464b6fe9a845dfaf308fdfc7925387195570789bb2c970"}, - {file = "numpy-2.3.3-cp314-cp314t-win32.whl", hash = "sha256:1250c5d3d2562ec4174bce2e3a1523041595f9b651065e4a4473f5f48a6bc8a5"}, - {file = "numpy-2.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:b37a0b2e5935409daebe82c1e42274d30d9dd355852529eab91dab8dcca7419f"}, - {file = "numpy-2.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:78c9f6560dc7e6b3990e32df7ea1a50bbd0e2a111e05209963f5ddcab7073b0b"}, - {file = "numpy-2.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1e02c7159791cd481e1e6d5ddd766b62a4d5acf8df4d4d1afe35ee9c5c33a41e"}, - {file = "numpy-2.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:dca2d0fc80b3893ae72197b39f69d55a3cd8b17ea1b50aa4c62de82419936150"}, - {file = "numpy-2.3.3-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:99683cbe0658f8271b333a1b1b4bb3173750ad59c0c61f5bbdc5b318918fffe3"}, - {file = "numpy-2.3.3-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:d9d537a39cc9de668e5cd0e25affb17aec17b577c6b3ae8a3d866b479fbe88d0"}, - {file = "numpy-2.3.3-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8596ba2f8af5f93b01d97563832686d20206d303024777f6dfc2e7c7c3f1850e"}, - {file = "numpy-2.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1ec5615b05369925bd1125f27df33f3b6c8bc10d788d5999ecd8769a1fa04db"}, - {file = "numpy-2.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2e267c7da5bf7309670523896df97f93f6e469fb931161f483cd6882b3b1a5dc"}, - {file = "numpy-2.3.3.tar.gz", hash = "sha256:ddc7c39727ba62b80dfdbedf400d1c10ddfa8eefbd7ec8dcb118be8b56d31029"}, -] - -[[package]] -name = "overrides" -version = "7.7.0" -description = "A decorator to automatically detect mismatch when overriding a method." -optional = false -python-versions = ">=3.6" -files = [ - {file = "overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49"}, - {file = "overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a"}, -] - -[[package]] -name = "packaging" -version = "25.0" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, - {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, -] - -[[package]] -name = "pandas" -version = "2.3.3" -description = "Powerful data structures for data analysis, time series, and statistics" -optional = false -python-versions = ">=3.9" -files = [ - {file = "pandas-2.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:376c6446ae31770764215a6c937f72d917f214b43560603cd60da6408f183b6c"}, - {file = "pandas-2.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e19d192383eab2f4ceb30b412b22ea30690c9e618f78870357ae1d682912015a"}, - {file = "pandas-2.3.3-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5caf26f64126b6c7aec964f74266f435afef1c1b13da3b0636c7518a1fa3e2b1"}, - {file = "pandas-2.3.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd7478f1463441ae4ca7308a70e90b33470fa593429f9d4c578dd00d1fa78838"}, - {file = "pandas-2.3.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4793891684806ae50d1288c9bae9330293ab4e083ccd1c5e383c34549c6e4250"}, - {file = "pandas-2.3.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:28083c648d9a99a5dd035ec125d42439c6c1c525098c58af0fc38dd1a7a1b3d4"}, - {file = "pandas-2.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:503cf027cf9940d2ceaa1a93cfb5f8c8c7e6e90720a2850378f0b3f3b1e06826"}, - {file = "pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523"}, - {file = "pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45"}, - {file = "pandas-2.3.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b468d3dad6ff947df92dcb32ede5b7bd41a9b3cceef0a30ed925f6d01fb8fa66"}, - {file = "pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b"}, - {file = "pandas-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37b5848ba49824e5c30bedb9c830ab9b7751fd049bc7914533e01c65f79791"}, - {file = "pandas-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db4301b2d1f926ae677a751eb2bd0e8c5f5319c9cb3f88b0becbbb0b07b34151"}, - {file = "pandas-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c"}, - {file = "pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53"}, - {file = "pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35"}, - {file = "pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908"}, - {file = "pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89"}, - {file = "pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98"}, - {file = "pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084"}, - {file = "pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b"}, - {file = "pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713"}, - {file = "pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8"}, - {file = "pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d"}, - {file = "pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac"}, - {file = "pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c"}, - {file = "pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493"}, - {file = "pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee"}, - {file = "pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5"}, - {file = "pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21"}, - {file = "pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78"}, - {file = "pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110"}, - {file = "pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86"}, - {file = "pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc"}, - {file = "pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0"}, - {file = "pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593"}, - {file = "pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c"}, - {file = "pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b"}, - {file = "pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6"}, - {file = "pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3"}, - {file = "pandas-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5"}, - {file = "pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec"}, - {file = "pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7"}, - {file = "pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450"}, - {file = "pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5"}, - {file = "pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788"}, - {file = "pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87"}, - {file = "pandas-2.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c503ba5216814e295f40711470446bc3fd00f0faea8a086cbc688808e26f92a2"}, - {file = "pandas-2.3.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a637c5cdfa04b6d6e2ecedcb81fc52ffb0fd78ce2ebccc9ea964df9f658de8c8"}, - {file = "pandas-2.3.3-cp39-cp39-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:854d00d556406bffe66a4c0802f334c9ad5a96b4f1f868adf036a21b11ef13ff"}, - {file = "pandas-2.3.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf1f8a81d04ca90e32a0aceb819d34dbd378a98bf923b6398b9a3ec0bf44de29"}, - {file = "pandas-2.3.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:23ebd657a4d38268c7dfbdf089fbc31ea709d82e4923c5ffd4fbd5747133ce73"}, - {file = "pandas-2.3.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5554c929ccc317d41a5e3d1234f3be588248e61f08a74dd17c9eabb535777dc9"}, - {file = "pandas-2.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:d3e28b3e83862ccf4d85ff19cf8c20b2ae7e503881711ff2d534dc8f761131aa"}, - {file = "pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b"}, -] - -[package.dependencies] -numpy = [ - {version = ">=1.23.2", markers = "python_version == \"3.11\""}, - {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, -] -python-dateutil = ">=2.8.2" -pytz = ">=2020.1" -tzdata = ">=2022.7" - -[package.extras] -all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)", "beautifulsoup4 (>=4.11.2)", "bottleneck (>=1.3.6)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=2022.12.0)", "fsspec (>=2022.11.0)", "gcsfs (>=2022.11.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.9.2)", "matplotlib (>=3.6.3)", "numba (>=0.56.4)", "numexpr (>=2.8.4)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "pandas-gbq (>=0.19.0)", "psycopg2 (>=2.9.6)", "pyarrow (>=10.0.1)", "pymysql (>=1.0.2)", "pyreadstat (>=1.2.0)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "qtpy (>=2.3.0)", "s3fs (>=2022.11.0)", "scipy (>=1.10.0)", "tables (>=3.8.0)", "tabulate (>=0.9.0)", "xarray (>=2022.12.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)", "zstandard (>=0.19.0)"] -aws = ["s3fs (>=2022.11.0)"] -clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"] -compression = ["zstandard (>=0.19.0)"] -computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"] -consortium-standard = ["dataframe-api-compat (>=0.1.7)"] -excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)"] -feather = ["pyarrow (>=10.0.1)"] -fss = ["fsspec (>=2022.11.0)"] -gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"] -hdf5 = ["tables (>=3.8.0)"] -html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"] -mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"] -output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"] -parquet = ["pyarrow (>=10.0.1)"] -performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"] -plot = ["matplotlib (>=3.6.3)"] -postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"] -pyarrow = ["pyarrow (>=10.0.1)"] -spss = ["pyreadstat (>=1.2.0)"] -sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"] -test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] -xml = ["lxml (>=4.9.2)"] - -[[package]] -name = "pandocfilters" -version = "1.5.1" -description = "Utilities for writing pandoc filters in python" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "pandocfilters-1.5.1-py2.py3-none-any.whl", hash = "sha256:93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc"}, - {file = "pandocfilters-1.5.1.tar.gz", hash = "sha256:002b4a555ee4ebc03f8b66307e287fa492e4a77b4ea14d3f934328297bb4939e"}, -] - -[[package]] -name = "parso" -version = "0.8.5" -description = "A Python Parser" -optional = false -python-versions = ">=3.6" -files = [ - {file = "parso-0.8.5-py2.py3-none-any.whl", hash = "sha256:646204b5ee239c396d040b90f9e272e9a8017c630092bf59980beb62fd033887"}, - {file = "parso-0.8.5.tar.gz", hash = "sha256:034d7354a9a018bdce352f48b2a8a450f05e9d6ee85db84764e9b6bd96dafe5a"}, -] - -[package.extras] -qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] -testing = ["docopt", "pytest"] - -[[package]] -name = "patsy" -version = "1.0.1" -description = "A Python package for describing statistical models and for building design matrices." -optional = false -python-versions = ">=3.6" -files = [ - {file = "patsy-1.0.1-py2.py3-none-any.whl", hash = "sha256:751fb38f9e97e62312e921a1954b81e1bb2bcda4f5eeabaf94db251ee791509c"}, - {file = "patsy-1.0.1.tar.gz", hash = "sha256:e786a9391eec818c054e359b737bbce692f051aee4c661f4141cc88fb459c0c4"}, -] - -[package.dependencies] -numpy = ">=1.4" - -[package.extras] -test = ["pytest", "pytest-cov", "scipy"] - -[[package]] -name = "pexpect" -version = "4.9.0" -description = "Pexpect allows easy control of interactive console applications." -optional = false -python-versions = "*" -files = [ - {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, - {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, -] - -[package.dependencies] -ptyprocess = ">=0.5" - -[[package]] -name = "pillow" -version = "11.3.0" -description = "Python Imaging Library (Fork)" -optional = false -python-versions = ">=3.9" -files = [ - {file = "pillow-11.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b9c17fd4ace828b3003dfd1e30bff24863e0eb59b535e8f80194d9cc7ecf860"}, - {file = "pillow-11.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:65dc69160114cdd0ca0f35cb434633c75e8e7fad4cf855177a05bf38678f73ad"}, - {file = "pillow-11.3.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7107195ddc914f656c7fc8e4a5e1c25f32e9236ea3ea860f257b0436011fddd0"}, - {file = "pillow-11.3.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc3e831b563b3114baac7ec2ee86819eb03caa1a2cef0b481a5675b59c4fe23b"}, - {file = "pillow-11.3.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f1f182ebd2303acf8c380a54f615ec883322593320a9b00438eb842c1f37ae50"}, - {file = "pillow-11.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4445fa62e15936a028672fd48c4c11a66d641d2c05726c7ec1f8ba6a572036ae"}, - {file = "pillow-11.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:71f511f6b3b91dd543282477be45a033e4845a40278fa8dcdbfdb07109bf18f9"}, - {file = "pillow-11.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:040a5b691b0713e1f6cbe222e0f4f74cd233421e105850ae3b3c0ceda520f42e"}, - {file = "pillow-11.3.0-cp310-cp310-win32.whl", hash = "sha256:89bd777bc6624fe4115e9fac3352c79ed60f3bb18651420635f26e643e3dd1f6"}, - {file = "pillow-11.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:19d2ff547c75b8e3ff46f4d9ef969a06c30ab2d4263a9e287733aa8b2429ce8f"}, - {file = "pillow-11.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:819931d25e57b513242859ce1876c58c59dc31587847bf74cfe06b2e0cb22d2f"}, - {file = "pillow-11.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1cd110edf822773368b396281a2293aeb91c90a2db00d78ea43e7e861631b722"}, - {file = "pillow-11.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c412fddd1b77a75aa904615ebaa6001f169b26fd467b4be93aded278266b288"}, - {file = "pillow-11.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1aa4de119a0ecac0a34a9c8bde33f34022e2e8f99104e47a3ca392fd60e37d"}, - {file = "pillow-11.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:91da1d88226663594e3f6b4b8c3c8d85bd504117d043740a8e0ec449087cc494"}, - {file = "pillow-11.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:643f189248837533073c405ec2f0bb250ba54598cf80e8c1e043381a60632f58"}, - {file = "pillow-11.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:106064daa23a745510dabce1d84f29137a37224831d88eb4ce94bb187b1d7e5f"}, - {file = "pillow-11.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd8ff254faf15591e724dc7c4ddb6bf4793efcbe13802a4ae3e863cd300b493e"}, - {file = "pillow-11.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:932c754c2d51ad2b2271fd01c3d121daaa35e27efae2a616f77bf164bc0b3e94"}, - {file = "pillow-11.3.0-cp311-cp311-win32.whl", hash = "sha256:b4b8f3efc8d530a1544e5962bd6b403d5f7fe8b9e08227c6b255f98ad82b4ba0"}, - {file = "pillow-11.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:1a992e86b0dd7aeb1f053cd506508c0999d710a8f07b4c791c63843fc6a807ac"}, - {file = "pillow-11.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:30807c931ff7c095620fe04448e2c2fc673fcbb1ffe2a7da3fb39613489b1ddd"}, - {file = "pillow-11.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdae223722da47b024b867c1ea0be64e0df702c5e0a60e27daad39bf960dd1e4"}, - {file = "pillow-11.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:921bd305b10e82b4d1f5e802b6850677f965d8394203d182f078873851dada69"}, - {file = "pillow-11.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb76541cba2f958032d79d143b98a3a6b3ea87f0959bbe256c0b5e416599fd5d"}, - {file = "pillow-11.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67172f2944ebba3d4a7b54f2e95c786a3a50c21b88456329314caaa28cda70f6"}, - {file = "pillow-11.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f07ed9f56a3b9b5f49d3661dc9607484e85c67e27f3e8be2c7d28ca032fec7"}, - {file = "pillow-11.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:676b2815362456b5b3216b4fd5bd89d362100dc6f4945154ff172e206a22c024"}, - {file = "pillow-11.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3e184b2f26ff146363dd07bde8b711833d7b0202e27d13540bfe2e35a323a809"}, - {file = "pillow-11.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6be31e3fc9a621e071bc17bb7de63b85cbe0bfae91bb0363c893cbe67247780d"}, - {file = "pillow-11.3.0-cp312-cp312-win32.whl", hash = "sha256:7b161756381f0918e05e7cb8a371fff367e807770f8fe92ecb20d905d0e1c149"}, - {file = "pillow-11.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a6444696fce635783440b7f7a9fc24b3ad10a9ea3f0ab66c5905be1c19ccf17d"}, - {file = "pillow-11.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:2aceea54f957dd4448264f9bf40875da0415c83eb85f55069d89c0ed436e3542"}, - {file = "pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd"}, - {file = "pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8"}, - {file = "pillow-11.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f"}, - {file = "pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c"}, - {file = "pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd"}, - {file = "pillow-11.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2d6fcc902a24ac74495df63faad1884282239265c6839a0a6416d33faedfae7e"}, - {file = "pillow-11.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1"}, - {file = "pillow-11.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805"}, - {file = "pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8"}, - {file = "pillow-11.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2"}, - {file = "pillow-11.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b"}, - {file = "pillow-11.3.0-cp313-cp313-win32.whl", hash = "sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3"}, - {file = "pillow-11.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51"}, - {file = "pillow-11.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580"}, - {file = "pillow-11.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e"}, - {file = "pillow-11.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d"}, - {file = "pillow-11.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1019b04af07fc0163e2810167918cb5add8d74674b6267616021ab558dc98ced"}, - {file = "pillow-11.3.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c"}, - {file = "pillow-11.3.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8"}, - {file = "pillow-11.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59"}, - {file = "pillow-11.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe"}, - {file = "pillow-11.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c"}, - {file = "pillow-11.3.0-cp313-cp313t-win32.whl", hash = "sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788"}, - {file = "pillow-11.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31"}, - {file = "pillow-11.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e"}, - {file = "pillow-11.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d9da3df5f9ea2a89b81bb6087177fb1f4d1c7146d583a3fe5c672c0d94e55e12"}, - {file = "pillow-11.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0b275ff9b04df7b640c59ec5a3cb113eefd3795a8df80bac69646ef699c6981a"}, - {file = "pillow-11.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0743841cabd3dba6a83f38a92672cccbd69af56e3e91777b0ee7f4dba4385632"}, - {file = "pillow-11.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2465a69cf967b8b49ee1b96d76718cd98c4e925414ead59fdf75cf0fd07df673"}, - {file = "pillow-11.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41742638139424703b4d01665b807c6468e23e699e8e90cffefe291c5832b027"}, - {file = "pillow-11.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93efb0b4de7e340d99057415c749175e24c8864302369e05914682ba642e5d77"}, - {file = "pillow-11.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7966e38dcd0fa11ca390aed7c6f20454443581d758242023cf36fcb319b1a874"}, - {file = "pillow-11.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:98a9afa7b9007c67ed84c57c9e0ad86a6000da96eaa638e4f8abe5b65ff83f0a"}, - {file = "pillow-11.3.0-cp314-cp314-win32.whl", hash = "sha256:02a723e6bf909e7cea0dac1b0e0310be9d7650cd66222a5f1c571455c0a45214"}, - {file = "pillow-11.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:a418486160228f64dd9e9efcd132679b7a02a5f22c982c78b6fc7dab3fefb635"}, - {file = "pillow-11.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:155658efb5e044669c08896c0c44231c5e9abcaadbc5cd3648df2f7c0b96b9a6"}, - {file = "pillow-11.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:59a03cdf019efbfeeed910bf79c7c93255c3d54bc45898ac2a4140071b02b4ae"}, - {file = "pillow-11.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f8a5827f84d973d8636e9dc5764af4f0cf2318d26744b3d902931701b0d46653"}, - {file = "pillow-11.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ee92f2fd10f4adc4b43d07ec5e779932b4eb3dbfbc34790ada5a6669bc095aa6"}, - {file = "pillow-11.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c96d333dcf42d01f47b37e0979b6bd73ec91eae18614864622d9b87bbd5bbf36"}, - {file = "pillow-11.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c96f993ab8c98460cd0c001447bff6194403e8b1d7e149ade5f00594918128b"}, - {file = "pillow-11.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41342b64afeba938edb034d122b2dda5db2139b9a4af999729ba8818e0056477"}, - {file = "pillow-11.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:068d9c39a2d1b358eb9f245ce7ab1b5c3246c7c8c7d9ba58cfa5b43146c06e50"}, - {file = "pillow-11.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a1bc6ba083b145187f648b667e05a2534ecc4b9f2784c2cbe3089e44868f2b9b"}, - {file = "pillow-11.3.0-cp314-cp314t-win32.whl", hash = "sha256:118ca10c0d60b06d006be10a501fd6bbdfef559251ed31b794668ed569c87e12"}, - {file = "pillow-11.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8924748b688aa210d79883357d102cd64690e56b923a186f35a82cbc10f997db"}, - {file = "pillow-11.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa"}, - {file = "pillow-11.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:48d254f8a4c776de343051023eb61ffe818299eeac478da55227d96e241de53f"}, - {file = "pillow-11.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7aee118e30a4cf54fdd873bd3a29de51e29105ab11f9aad8c32123f58c8f8081"}, - {file = "pillow-11.3.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:23cff760a9049c502721bdb743a7cb3e03365fafcdfc2ef9784610714166e5a4"}, - {file = "pillow-11.3.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6359a3bc43f57d5b375d1ad54a0074318a0844d11b76abccf478c37c986d3cfc"}, - {file = "pillow-11.3.0-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:092c80c76635f5ecb10f3f83d76716165c96f5229addbd1ec2bdbbda7d496e06"}, - {file = "pillow-11.3.0-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cadc9e0ea0a2431124cde7e1697106471fc4c1da01530e679b2391c37d3fbb3a"}, - {file = "pillow-11.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6a418691000f2a418c9135a7cf0d797c1bb7d9a485e61fe8e7722845b95ef978"}, - {file = "pillow-11.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:97afb3a00b65cc0804d1c7abddbf090a81eaac02768af58cbdcaaa0a931e0b6d"}, - {file = "pillow-11.3.0-cp39-cp39-win32.whl", hash = "sha256:ea944117a7974ae78059fcc1800e5d3295172bb97035c0c1d9345fca1419da71"}, - {file = "pillow-11.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:e5c5858ad8ec655450a7c7df532e9842cf8df7cc349df7225c60d5d348c8aada"}, - {file = "pillow-11.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:6abdbfd3aea42be05702a8dd98832329c167ee84400a1d1f61ab11437f1717eb"}, - {file = "pillow-11.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3cee80663f29e3843b68199b9d6f4f54bd1d4a6b59bdd91bceefc51238bcb967"}, - {file = "pillow-11.3.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b5f56c3f344f2ccaf0dd875d3e180f631dc60a51b314295a3e681fe8cf851fbe"}, - {file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e67d793d180c9df62f1f40aee3accca4829d3794c95098887edc18af4b8b780c"}, - {file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d000f46e2917c705e9fb93a3606ee4a819d1e3aa7a9b442f6444f07e77cf5e25"}, - {file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:527b37216b6ac3a12d7838dc3bd75208ec57c1c6d11ef01902266a5a0c14fc27"}, - {file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:be5463ac478b623b9dd3937afd7fb7ab3d79dd290a28e2b6df292dc75063eb8a"}, - {file = "pillow-11.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:8dc70ca24c110503e16918a658b869019126ecfe03109b754c402daff12b3d9f"}, - {file = "pillow-11.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7c8ec7a017ad1bd562f93dbd8505763e688d388cde6e4a010ae1486916e713e6"}, - {file = "pillow-11.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9ab6ae226de48019caa8074894544af5b53a117ccb9d3b3dcb2871464c829438"}, - {file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe27fb049cdcca11f11a7bfda64043c37b30e6b91f10cb5bab275806c32f6ab3"}, - {file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:465b9e8844e3c3519a983d58b80be3f668e2a7a5db97f2784e7079fbc9f9822c"}, - {file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5418b53c0d59b3824d05e029669efa023bbef0f3e92e75ec8428f3799487f361"}, - {file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:504b6f59505f08ae014f724b6207ff6222662aab5cc9542577fb084ed0676ac7"}, - {file = "pillow-11.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c84d689db21a1c397d001aa08241044aa2069e7587b398c8cc63020390b1c1b8"}, - {file = "pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523"}, -] - -[package.extras] -docs = ["furo", "olefile", "sphinx (>=8.2)", "sphinx-autobuild", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] -fpx = ["olefile"] -mic = ["olefile"] -test-arrow = ["pyarrow"] -tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "trove-classifiers (>=2024.10.12)"] -typing = ["typing-extensions"] -xmp = ["defusedxml"] - -[[package]] -name = "platformdirs" -version = "4.5.0" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." -optional = false -python-versions = ">=3.10" -files = [ - {file = "platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3"}, - {file = "platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312"}, -] - -[package.extras] -docs = ["furo (>=2025.9.25)", "proselint (>=0.14)", "sphinx (>=8.2.3)", "sphinx-autodoc-typehints (>=3.2)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.4.2)", "pytest-cov (>=7)", "pytest-mock (>=3.15.1)"] -type = ["mypy (>=1.18.2)"] - -[[package]] -name = "prometheus-client" -version = "0.23.1" -description = "Python client for the Prometheus monitoring system." -optional = false -python-versions = ">=3.9" -files = [ - {file = "prometheus_client-0.23.1-py3-none-any.whl", hash = "sha256:dd1913e6e76b59cfe44e7a4b83e01afc9873c1bdfd2ed8739f1e76aeca115f99"}, - {file = "prometheus_client-0.23.1.tar.gz", hash = "sha256:6ae8f9081eaaaf153a2e959d2e6c4f4fb57b12ef76c8c7980202f1e57b48b2ce"}, -] - -[package.extras] -twisted = ["twisted"] - -[[package]] -name = "prompt-toolkit" -version = "3.0.52" -description = "Library for building powerful interactive command lines in Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955"}, - {file = "prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855"}, -] - -[package.dependencies] -wcwidth = "*" - -[[package]] -name = "psutil" -version = "7.1.0" -description = "Cross-platform lib for process and system monitoring." -optional = false -python-versions = ">=3.6" -files = [ - {file = "psutil-7.1.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:76168cef4397494250e9f4e73eb3752b146de1dd950040b29186d0cce1d5ca13"}, - {file = "psutil-7.1.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:5d007560c8c372efdff9e4579c2846d71de737e4605f611437255e81efcca2c5"}, - {file = "psutil-7.1.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22e4454970b32472ce7deaa45d045b34d3648ce478e26a04c7e858a0a6e75ff3"}, - {file = "psutil-7.1.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c70e113920d51e89f212dd7be06219a9b88014e63a4cec69b684c327bc474e3"}, - {file = "psutil-7.1.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d4a113425c037300de3ac8b331637293da9be9713855c4fc9d2d97436d7259d"}, - {file = "psutil-7.1.0-cp37-abi3-win32.whl", hash = "sha256:09ad740870c8d219ed8daae0ad3b726d3bf9a028a198e7f3080f6a1888b99bca"}, - {file = "psutil-7.1.0-cp37-abi3-win_amd64.whl", hash = "sha256:57f5e987c36d3146c0dd2528cd42151cf96cd359b9d67cfff836995cc5df9a3d"}, - {file = "psutil-7.1.0-cp37-abi3-win_arm64.whl", hash = "sha256:6937cb68133e7c97b6cc9649a570c9a18ba0efebed46d8c5dae4c07fa1b67a07"}, - {file = "psutil-7.1.0.tar.gz", hash = "sha256:655708b3c069387c8b77b072fc429a57d0e214221d01c0a772df7dfedcb3bcd2"}, -] - -[package.extras] -dev = ["abi3audit", "black", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pyreadline", "pytest", "pytest-cov", "pytest-instafail", "pytest-subtests", "pytest-xdist", "pywin32", "requests", "rstcheck", "ruff", "setuptools", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "virtualenv", "vulture", "wheel", "wheel", "wmi"] -test = ["pytest", "pytest-instafail", "pytest-subtests", "pytest-xdist", "pywin32", "setuptools", "wheel", "wmi"] - -[[package]] -name = "ptyprocess" -version = "0.7.0" -description = "Run a subprocess in a pseudo terminal" -optional = false -python-versions = "*" -files = [ - {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, - {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, -] - -[[package]] -name = "pure-eval" -version = "0.2.3" -description = "Safely evaluate AST nodes without side effects" -optional = false -python-versions = "*" -files = [ - {file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"}, - {file = "pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"}, -] - -[package.extras] -tests = ["pytest"] - -[[package]] -name = "pycparser" -version = "2.23" -description = "C parser in Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934"}, - {file = "pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2"}, -] - -[[package]] -name = "pygments" -version = "2.19.2" -description = "Pygments is a syntax highlighting package written in Python." -optional = false -python-versions = ">=3.8" -files = [ - {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, - {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, -] - -[package.extras] -windows-terminal = ["colorama (>=0.4.6)"] - -[[package]] -name = "pyparsing" -version = "3.2.5" -description = "pyparsing - Classes and methods to define and execute parsing grammars" -optional = false -python-versions = ">=3.9" -files = [ - {file = "pyparsing-3.2.5-py3-none-any.whl", hash = "sha256:e38a4f02064cf41fe6593d328d0512495ad1f3d8a91c4f73fc401b3079a59a5e"}, - {file = "pyparsing-3.2.5.tar.gz", hash = "sha256:2df8d5b7b2802ef88e8d016a2eb9c7aeaa923529cd251ed0fe4608275d4105b6"}, -] - -[package.extras] -diagrams = ["jinja2", "railroad-diagrams"] - -[[package]] -name = "python-dateutil" -version = "2.9.0.post0" -description = "Extensions to the standard Python datetime module" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -files = [ - {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, - {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, -] - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "python-json-logger" -version = "4.0.0" -description = "JSON Log Formatter for the Python Logging Package" -optional = false -python-versions = ">=3.8" -files = [ - {file = "python_json_logger-4.0.0-py3-none-any.whl", hash = "sha256:af09c9daf6a813aa4cc7180395f50f2a9e5fa056034c9953aec92e381c5ba1e2"}, - {file = "python_json_logger-4.0.0.tar.gz", hash = "sha256:f58e68eb46e1faed27e0f574a55a0455eecd7b8a5b88b85a784519ba3cff047f"}, -] - -[package.extras] -dev = ["backports.zoneinfo", "black", "build", "freezegun", "mdx_truly_sane_lists", "mike", "mkdocs", "mkdocs-awesome-pages-plugin", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-material (>=8.5)", "mkdocstrings[python]", "msgspec", "mypy", "orjson", "pylint", "pytest", "tzdata", "validate-pyproject[all]"] - -[[package]] -name = "pytz" -version = "2025.2" -description = "World timezone definitions, modern and historical" -optional = false -python-versions = "*" -files = [ - {file = "pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00"}, - {file = "pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3"}, -] - -[[package]] -name = "pywin32" -version = "311" -description = "Python for Window Extensions" -optional = false -python-versions = "*" -files = [ - {file = "pywin32-311-cp310-cp310-win32.whl", hash = "sha256:d03ff496d2a0cd4a5893504789d4a15399133fe82517455e78bad62efbb7f0a3"}, - {file = "pywin32-311-cp310-cp310-win_amd64.whl", hash = "sha256:797c2772017851984b97180b0bebe4b620bb86328e8a884bb626156295a63b3b"}, - {file = "pywin32-311-cp310-cp310-win_arm64.whl", hash = "sha256:0502d1facf1fed4839a9a51ccbcc63d952cf318f78ffc00a7e78528ac27d7a2b"}, - {file = "pywin32-311-cp311-cp311-win32.whl", hash = "sha256:184eb5e436dea364dcd3d2316d577d625c0351bf237c4e9a5fabbcfa5a58b151"}, - {file = "pywin32-311-cp311-cp311-win_amd64.whl", hash = "sha256:3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503"}, - {file = "pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2"}, - {file = "pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31"}, - {file = "pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067"}, - {file = "pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852"}, - {file = "pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d"}, - {file = "pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d"}, - {file = "pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a"}, - {file = "pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee"}, - {file = "pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87"}, - {file = "pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42"}, - {file = "pywin32-311-cp38-cp38-win32.whl", hash = "sha256:6c6f2969607b5023b0d9ce2541f8d2cbb01c4f46bc87456017cf63b73f1e2d8c"}, - {file = "pywin32-311-cp38-cp38-win_amd64.whl", hash = "sha256:c8015b09fb9a5e188f83b7b04de91ddca4658cee2ae6f3bc483f0b21a77ef6cd"}, - {file = "pywin32-311-cp39-cp39-win32.whl", hash = "sha256:aba8f82d551a942cb20d4a83413ccbac30790b50efb89a75e4f586ac0bb8056b"}, - {file = "pywin32-311-cp39-cp39-win_amd64.whl", hash = "sha256:e0c4cfb0621281fe40387df582097fd796e80430597cb9944f0ae70447bacd91"}, - {file = "pywin32-311-cp39-cp39-win_arm64.whl", hash = "sha256:62ea666235135fee79bb154e695f3ff67370afefd71bd7fea7512fc70ef31e3d"}, -] - -[[package]] -name = "pywinpty" -version = "3.0.2" -description = "Pseudo terminal support for Windows from Python." -optional = false -python-versions = ">=3.9" -files = [ - {file = "pywinpty-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:65db57fd3387d71e8372b6a54269cbcd0f6dfa6d4616a29e0af749ec19f5c558"}, - {file = "pywinpty-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:327790d70e4c841ebd9d0f295a780177149aeb405bca44c7115a3de5c2054b23"}, - {file = "pywinpty-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:99fdd9b455f0ad6419aba6731a7a0d2f88ced83c3c94a80ff9533d95fa8d8a9e"}, - {file = "pywinpty-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:18f78b81e4cfee6aabe7ea8688441d30247b73e52cd9657138015c5f4ee13a51"}, - {file = "pywinpty-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:663383ecfab7fc382cc97ea5c4f7f0bb32c2f889259855df6ea34e5df42d305b"}, - {file = "pywinpty-3.0.2-cp314-cp314-win_amd64.whl", hash = "sha256:28297cecc37bee9f24d8889e47231972d6e9e84f7b668909de54f36ca785029a"}, - {file = "pywinpty-3.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:34b55ae9a1b671fe3eae071d86618110538e8eaad18fcb1531c0830b91a82767"}, - {file = "pywinpty-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:3962daf801bc38dd4de872108c424b5338c9a46c6efca5761854cd66370a9022"}, - {file = "pywinpty-3.0.2.tar.gz", hash = "sha256:1505cc4cb248af42cb6285a65c9c2086ee9e7e574078ee60933d5d7fa86fb004"}, -] - -[[package]] -name = "pyyaml" -version = "6.0.3" -description = "YAML parser and emitter for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "PyYAML-6.0.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f"}, - {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4"}, - {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efd7b85f94a6f21e4932043973a7ba2613b059c4a000551892ac9f1d11f5baf3"}, - {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22ba7cfcad58ef3ecddc7ed1db3409af68d023b7f940da23c6c2a1890976eda6"}, - {file = "PyYAML-6.0.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:6344df0d5755a2c9a276d4473ae6b90647e216ab4757f8426893b5dd2ac3f369"}, - {file = "PyYAML-6.0.3-cp38-cp38-win32.whl", hash = "sha256:3ff07ec89bae51176c0549bc4c63aa6202991da2d9a6129d7aef7f1407d3f295"}, - {file = "PyYAML-6.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:5cf4e27da7e3fbed4d6c3d8e797387aaad68102272f8f9752883bc32d61cb87b"}, - {file = "pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b"}, - {file = "pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956"}, - {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8"}, - {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198"}, - {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b"}, - {file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0"}, - {file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69"}, - {file = "pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e"}, - {file = "pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c"}, - {file = "pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e"}, - {file = "pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824"}, - {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c"}, - {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00"}, - {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d"}, - {file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a"}, - {file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4"}, - {file = "pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b"}, - {file = "pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf"}, - {file = "pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196"}, - {file = "pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0"}, - {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28"}, - {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c"}, - {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc"}, - {file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e"}, - {file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea"}, - {file = "pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5"}, - {file = "pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b"}, - {file = "pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd"}, - {file = "pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8"}, - {file = "pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1"}, - {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c"}, - {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5"}, - {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6"}, - {file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6"}, - {file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be"}, - {file = "pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26"}, - {file = "pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c"}, - {file = "pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb"}, - {file = "pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac"}, - {file = "pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310"}, - {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7"}, - {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788"}, - {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5"}, - {file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764"}, - {file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35"}, - {file = "pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac"}, - {file = "pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3"}, - {file = "pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3"}, - {file = "pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba"}, - {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c"}, - {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702"}, - {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c"}, - {file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065"}, - {file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65"}, - {file = "pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9"}, - {file = "pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b"}, - {file = "pyyaml-6.0.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da"}, - {file = "pyyaml-6.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917"}, - {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9"}, - {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5"}, - {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a"}, - {file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926"}, - {file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7"}, - {file = "pyyaml-6.0.3-cp39-cp39-win32.whl", hash = "sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0"}, - {file = "pyyaml-6.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007"}, - {file = "pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f"}, -] - -[[package]] -name = "pyzmq" -version = "27.1.0" -description = "Python bindings for 0MQ" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyzmq-27.1.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:508e23ec9bc44c0005c4946ea013d9317ae00ac67778bd47519fdf5a0e930ff4"}, - {file = "pyzmq-27.1.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:507b6f430bdcf0ee48c0d30e734ea89ce5567fd7b8a0f0044a369c176aa44556"}, - {file = "pyzmq-27.1.0-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf7b38f9fd7b81cb6d9391b2946382c8237fd814075c6aa9c3b746d53076023b"}, - {file = "pyzmq-27.1.0-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03ff0b279b40d687691a6217c12242ee71f0fba28bf8626ff50e3ef0f4410e1e"}, - {file = "pyzmq-27.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:677e744fee605753eac48198b15a2124016c009a11056f93807000ab11ce6526"}, - {file = "pyzmq-27.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dd2fec2b13137416a1c5648b7009499bcc8fea78154cd888855fa32514f3dad1"}, - {file = "pyzmq-27.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:08e90bb4b57603b84eab1d0ca05b3bbb10f60c1839dc471fc1c9e1507bef3386"}, - {file = "pyzmq-27.1.0-cp310-cp310-win32.whl", hash = "sha256:a5b42d7a0658b515319148875fcb782bbf118dd41c671b62dae33666c2213bda"}, - {file = "pyzmq-27.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:c0bb87227430ee3aefcc0ade2088100e528d5d3298a0a715a64f3d04c60ba02f"}, - {file = "pyzmq-27.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:9a916f76c2ab8d045b19f2286851a38e9ac94ea91faf65bd64735924522a8b32"}, - {file = "pyzmq-27.1.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:226b091818d461a3bef763805e75685e478ac17e9008f49fce2d3e52b3d58b86"}, - {file = "pyzmq-27.1.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0790a0161c281ca9723f804871b4027f2e8b5a528d357c8952d08cd1a9c15581"}, - {file = "pyzmq-27.1.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c895a6f35476b0c3a54e3eb6ccf41bf3018de937016e6e18748317f25d4e925f"}, - {file = "pyzmq-27.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bbf8d3630bf96550b3be8e1fc0fea5cbdc8d5466c1192887bd94869da17a63e"}, - {file = "pyzmq-27.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:15c8bd0fe0dabf808e2d7a681398c4e5ded70a551ab47482067a572c054c8e2e"}, - {file = "pyzmq-27.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bafcb3dd171b4ae9f19ee6380dfc71ce0390fefaf26b504c0e5f628d7c8c54f2"}, - {file = "pyzmq-27.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e829529fcaa09937189178115c49c504e69289abd39967cd8a4c215761373394"}, - {file = "pyzmq-27.1.0-cp311-cp311-win32.whl", hash = "sha256:6df079c47d5902af6db298ec92151db82ecb557af663098b92f2508c398bb54f"}, - {file = "pyzmq-27.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:190cbf120fbc0fc4957b56866830def56628934a9d112aec0e2507aa6a032b97"}, - {file = "pyzmq-27.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:eca6b47df11a132d1745eb3b5b5e557a7dae2c303277aa0e69c6ba91b8736e07"}, - {file = "pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:452631b640340c928fa343801b0d07eb0c3789a5ffa843f6e1a9cee0ba4eb4fc"}, - {file = "pyzmq-27.1.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1c179799b118e554b66da67d88ed66cd37a169f1f23b5d9f0a231b4e8d44a113"}, - {file = "pyzmq-27.1.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3837439b7f99e60312f0c926a6ad437b067356dc2bc2ec96eb395fd0fe804233"}, - {file = "pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43ad9a73e3da1fab5b0e7e13402f0b2fb934ae1c876c51d0afff0e7c052eca31"}, - {file = "pyzmq-27.1.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0de3028d69d4cdc475bfe47a6128eb38d8bc0e8f4d69646adfbcd840facbac28"}, - {file = "pyzmq-27.1.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:cf44a7763aea9298c0aa7dbf859f87ed7012de8bda0f3977b6fb1d96745df856"}, - {file = "pyzmq-27.1.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f30f395a9e6fbca195400ce833c731e7b64c3919aa481af4d88c3759e0cb7496"}, - {file = "pyzmq-27.1.0-cp312-abi3-win32.whl", hash = "sha256:250e5436a4ba13885494412b3da5d518cd0d3a278a1ae640e113c073a5f88edd"}, - {file = "pyzmq-27.1.0-cp312-abi3-win_amd64.whl", hash = "sha256:9ce490cf1d2ca2ad84733aa1d69ce6855372cb5ce9223802450c9b2a7cba0ccf"}, - {file = "pyzmq-27.1.0-cp312-abi3-win_arm64.whl", hash = "sha256:75a2f36223f0d535a0c919e23615fc85a1e23b71f40c7eb43d7b1dedb4d8f15f"}, - {file = "pyzmq-27.1.0-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:93ad4b0855a664229559e45c8d23797ceac03183c7b6f5b4428152a6b06684a5"}, - {file = "pyzmq-27.1.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:fbb4f2400bfda24f12f009cba62ad5734148569ff4949b1b6ec3b519444342e6"}, - {file = "pyzmq-27.1.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:e343d067f7b151cfe4eb3bb796a7752c9d369eed007b91231e817071d2c2fec7"}, - {file = "pyzmq-27.1.0-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:08363b2011dec81c354d694bdecaef4770e0ae96b9afea70b3f47b973655cc05"}, - {file = "pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d54530c8c8b5b8ddb3318f481297441af102517602b569146185fa10b63f4fa9"}, - {file = "pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f3afa12c392f0a44a2414056d730eebc33ec0926aae92b5ad5cf26ebb6cc128"}, - {file = "pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c65047adafe573ff023b3187bb93faa583151627bc9c51fc4fb2c561ed689d39"}, - {file = "pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:90e6e9441c946a8b0a667356f7078d96411391a3b8f80980315455574177ec97"}, - {file = "pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:add071b2d25f84e8189aaf0882d39a285b42fa3853016ebab234a5e78c7a43db"}, - {file = "pyzmq-27.1.0-cp313-cp313t-win32.whl", hash = "sha256:7ccc0700cfdf7bd487bea8d850ec38f204478681ea02a582a8da8171b7f90a1c"}, - {file = "pyzmq-27.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:8085a9fba668216b9b4323be338ee5437a235fe275b9d1610e422ccc279733e2"}, - {file = "pyzmq-27.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:6bb54ca21bcfe361e445256c15eedf083f153811c37be87e0514934d6913061e"}, - {file = "pyzmq-27.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ce980af330231615756acd5154f29813d553ea555485ae712c491cd483df6b7a"}, - {file = "pyzmq-27.1.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1779be8c549e54a1c38f805e56d2a2e5c009d26de10921d7d51cfd1c8d4632ea"}, - {file = "pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7200bb0f03345515df50d99d3db206a0a6bee1955fbb8c453c76f5bf0e08fb96"}, - {file = "pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01c0e07d558b06a60773744ea6251f769cd79a41a97d11b8bf4ab8f034b0424d"}, - {file = "pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:80d834abee71f65253c91540445d37c4c561e293ba6e741b992f20a105d69146"}, - {file = "pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:544b4e3b7198dde4a62b8ff6685e9802a9a1ebf47e77478a5eb88eca2a82f2fd"}, - {file = "pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cedc4c68178e59a4046f97eca31b148ddcf51e88677de1ef4e78cf06c5376c9a"}, - {file = "pyzmq-27.1.0-cp314-cp314t-win32.whl", hash = "sha256:1f0b2a577fd770aa6f053211a55d1c47901f4d537389a034c690291485e5fe92"}, - {file = "pyzmq-27.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:19c9468ae0437f8074af379e986c5d3d7d7bfe033506af442e8c879732bedbe0"}, - {file = "pyzmq-27.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dc5dbf68a7857b59473f7df42650c621d7e8923fb03fa74a526890f4d33cc4d7"}, - {file = "pyzmq-27.1.0-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:18339186c0ed0ce5835f2656cdfb32203125917711af64da64dbaa3d949e5a1b"}, - {file = "pyzmq-27.1.0-cp38-cp38-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:753d56fba8f70962cd8295fb3edb40b9b16deaa882dd2b5a3a2039f9ff7625aa"}, - {file = "pyzmq-27.1.0-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b721c05d932e5ad9ff9344f708c96b9e1a485418c6618d765fca95d4daacfbef"}, - {file = "pyzmq-27.1.0-cp38-cp38-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7be883ff3d722e6085ee3f4afc057a50f7f2e0c72d289fd54df5706b4e3d3a50"}, - {file = "pyzmq-27.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:b2e592db3a93128daf567de9650a2f3859017b3f7a66bc4ed6e4779d6034976f"}, - {file = "pyzmq-27.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ad68808a61cbfbbae7ba26d6233f2a4aa3b221de379ce9ee468aa7a83b9c36b0"}, - {file = "pyzmq-27.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:e2687c2d230e8d8584fbea433c24382edfeda0c60627aca3446aa5e58d5d1831"}, - {file = "pyzmq-27.1.0-cp38-cp38-win32.whl", hash = "sha256:a1aa0ee920fb3825d6c825ae3f6c508403b905b698b6460408ebd5bb04bbb312"}, - {file = "pyzmq-27.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:df7cd397ece96cf20a76fae705d40efbab217d217897a5053267cd88a700c266"}, - {file = "pyzmq-27.1.0-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:96c71c32fff75957db6ae33cd961439f386505c6e6b377370af9b24a1ef9eafb"}, - {file = "pyzmq-27.1.0-cp39-cp39-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:49d3980544447f6bd2968b6ac913ab963a49dcaa2d4a2990041f16057b04c429"}, - {file = "pyzmq-27.1.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:849ca054d81aa1c175c49484afaaa5db0622092b5eccb2055f9f3bb8f703782d"}, - {file = "pyzmq-27.1.0-cp39-cp39-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3970778e74cb7f85934d2b926b9900e92bfe597e62267d7499acc39c9c28e345"}, - {file = "pyzmq-27.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:da96ecdcf7d3919c3be2de91a8c513c186f6762aa6cf7c01087ed74fad7f0968"}, - {file = "pyzmq-27.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:9541c444cfe1b1c0156c5c86ece2bb926c7079a18e7b47b0b1b3b1b875e5d098"}, - {file = "pyzmq-27.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e30a74a39b93e2e1591b58eb1acef4902be27c957a8720b0e368f579b82dc22f"}, - {file = "pyzmq-27.1.0-cp39-cp39-win32.whl", hash = "sha256:b1267823d72d1e40701dcba7edc45fd17f71be1285557b7fe668887150a14b78"}, - {file = "pyzmq-27.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:0c996ded912812a2fcd7ab6574f4ad3edc27cb6510349431e4930d4196ade7db"}, - {file = "pyzmq-27.1.0-cp39-cp39-win_arm64.whl", hash = "sha256:346e9ba4198177a07e7706050f35d733e08c1c1f8ceacd5eb6389d653579ffbc"}, - {file = "pyzmq-27.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c17e03cbc9312bee223864f1a2b13a99522e0dc9f7c5df0177cd45210ac286e6"}, - {file = "pyzmq-27.1.0-pp310-pypy310_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f328d01128373cb6763823b2b4e7f73bdf767834268c565151eacb3b7a392f90"}, - {file = "pyzmq-27.1.0-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c1790386614232e1b3a40a958454bdd42c6d1811837b15ddbb052a032a43f62"}, - {file = "pyzmq-27.1.0-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:448f9cb54eb0cee4732b46584f2710c8bc178b0e5371d9e4fc8125201e413a74"}, - {file = "pyzmq-27.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:05b12f2d32112bf8c95ef2e74ec4f1d4beb01f8b5e703b38537f8849f92cb9ba"}, - {file = "pyzmq-27.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:18770c8d3563715387139060d37859c02ce40718d1faf299abddcdcc6a649066"}, - {file = "pyzmq-27.1.0-pp311-pypy311_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:ac25465d42f92e990f8d8b0546b01c391ad431c3bf447683fdc40565941d0604"}, - {file = "pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53b40f8ae006f2734ee7608d59ed661419f087521edbfc2149c3932e9c14808c"}, - {file = "pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f605d884e7c8be8fe1aa94e0a783bf3f591b84c24e4bc4f3e7564c82ac25e271"}, - {file = "pyzmq-27.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c9f7f6e13dff2e44a6afeaf2cf54cee5929ad64afaf4d40b50f93c58fc687355"}, - {file = "pyzmq-27.1.0-pp38-pypy38_pp73-macosx_10_15_x86_64.whl", hash = "sha256:50081a4e98472ba9f5a02850014b4c9b629da6710f8f14f3b15897c666a28f1b"}, - {file = "pyzmq-27.1.0-pp38-pypy38_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:510869f9df36ab97f89f4cff9d002a89ac554c7ac9cadd87d444aa4cf66abd27"}, - {file = "pyzmq-27.1.0-pp38-pypy38_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1f8426a01b1c4098a750973c37131cf585f61c7911d735f729935a0c701b68d3"}, - {file = "pyzmq-27.1.0-pp38-pypy38_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:726b6a502f2e34c6d2ada5e702929586d3ac948a4dbbb7fed9854ec8c0466027"}, - {file = "pyzmq-27.1.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:bd67e7c8f4654bef471c0b1ca6614af0b5202a790723a58b79d9584dc8022a78"}, - {file = "pyzmq-27.1.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:722ea791aa233ac0a819fc2c475e1292c76930b31f1d828cb61073e2fe5e208f"}, - {file = "pyzmq-27.1.0-pp39-pypy39_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:01f9437501886d3a1dd4b02ef59fb8cc384fa718ce066d52f175ee49dd5b7ed8"}, - {file = "pyzmq-27.1.0-pp39-pypy39_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4a19387a3dddcc762bfd2f570d14e2395b2c9701329b266f83dd87a2b3cbd381"}, - {file = "pyzmq-27.1.0-pp39-pypy39_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c618fbcd069e3a29dcd221739cacde52edcc681f041907867e0f5cc7e85f172"}, - {file = "pyzmq-27.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ff8d114d14ac671d88c89b9224c63d6c4e5a613fe8acd5594ce53d752a3aafe9"}, - {file = "pyzmq-27.1.0.tar.gz", hash = "sha256:ac0765e3d44455adb6ddbf4417dcce460fc40a05978c08efdf2948072f6db540"}, -] - -[package.dependencies] -cffi = {version = "*", markers = "implementation_name == \"pypy\""} - -[[package]] -name = "referencing" -version = "0.36.2" -description = "JSON Referencing + Python" -optional = false -python-versions = ">=3.9" -files = [ - {file = "referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0"}, - {file = "referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa"}, -] - -[package.dependencies] -attrs = ">=22.2.0" -rpds-py = ">=0.7.0" -typing-extensions = {version = ">=4.4.0", markers = "python_version < \"3.13\""} - -[[package]] -name = "requests" -version = "2.32.5" -description = "Python HTTP for Humans." -optional = false -python-versions = ">=3.9" -files = [ - {file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"}, - {file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"}, -] - -[package.dependencies] -certifi = ">=2017.4.17" -charset_normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -name = "rfc3339-validator" -version = "0.1.4" -description = "A pure python RFC3339 validator" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -files = [ - {file = "rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa"}, - {file = "rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b"}, -] - -[package.dependencies] -six = "*" - -[[package]] -name = "rfc3986-validator" -version = "0.1.1" -description = "Pure python rfc3986 validator" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -files = [ - {file = "rfc3986_validator-0.1.1-py2.py3-none-any.whl", hash = "sha256:2f235c432ef459970b4306369336b9d5dbdda31b510ca1e327636e01f528bfa9"}, - {file = "rfc3986_validator-0.1.1.tar.gz", hash = "sha256:3d44bde7921b3b9ec3ae4e3adca370438eccebc676456449b145d533b240d055"}, -] - -[[package]] -name = "rfc3987-syntax" -version = "1.1.0" -description = "Helper functions to syntactically validate strings according to RFC 3987." -optional = false -python-versions = ">=3.9" -files = [ - {file = "rfc3987_syntax-1.1.0-py3-none-any.whl", hash = "sha256:6c3d97604e4c5ce9f714898e05401a0445a641cfa276432b0a648c80856f6a3f"}, - {file = "rfc3987_syntax-1.1.0.tar.gz", hash = "sha256:717a62cbf33cffdd16dfa3a497d81ce48a660ea691b1ddd7be710c22f00b4a0d"}, -] - -[package.dependencies] -lark = ">=1.2.2" - -[package.extras] -testing = ["pytest (>=8.3.5)"] - -[[package]] -name = "rpds-py" -version = "0.27.1" -description = "Python bindings to Rust's persistent data structures (rpds)" -optional = false -python-versions = ">=3.9" -files = [ - {file = "rpds_py-0.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:68afeec26d42ab3b47e541b272166a0b4400313946871cba3ed3a4fc0cab1cef"}, - {file = "rpds_py-0.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74e5b2f7bb6fa38b1b10546d27acbacf2a022a8b5543efb06cfebc72a59c85be"}, - {file = "rpds_py-0.27.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9024de74731df54546fab0bfbcdb49fae19159ecaecfc8f37c18d2c7e2c0bd61"}, - {file = "rpds_py-0.27.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:31d3ebadefcd73b73928ed0b2fd696f7fefda8629229f81929ac9c1854d0cffb"}, - {file = "rpds_py-0.27.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2e7f8f169d775dd9092a1743768d771f1d1300453ddfe6325ae3ab5332b4657"}, - {file = "rpds_py-0.27.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d905d16f77eb6ab2e324e09bfa277b4c8e5e6b8a78a3e7ff8f3cdf773b4c013"}, - {file = "rpds_py-0.27.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50c946f048209e6362e22576baea09193809f87687a95a8db24e5fbdb307b93a"}, - {file = "rpds_py-0.27.1-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:3deab27804d65cd8289eb814c2c0e807c4b9d9916c9225e363cb0cf875eb67c1"}, - {file = "rpds_py-0.27.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8b61097f7488de4be8244c89915da8ed212832ccf1e7c7753a25a394bf9b1f10"}, - {file = "rpds_py-0.27.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8a3f29aba6e2d7d90528d3c792555a93497fe6538aa65eb675b44505be747808"}, - {file = "rpds_py-0.27.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dd6cd0485b7d347304067153a6dc1d73f7d4fd995a396ef32a24d24b8ac63ac8"}, - {file = "rpds_py-0.27.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6f4461bf931108c9fa226ffb0e257c1b18dc2d44cd72b125bec50ee0ab1248a9"}, - {file = "rpds_py-0.27.1-cp310-cp310-win32.whl", hash = "sha256:ee5422d7fb21f6a00c1901bf6559c49fee13a5159d0288320737bbf6585bd3e4"}, - {file = "rpds_py-0.27.1-cp310-cp310-win_amd64.whl", hash = "sha256:3e039aabf6d5f83c745d5f9a0a381d031e9ed871967c0a5c38d201aca41f3ba1"}, - {file = "rpds_py-0.27.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:be898f271f851f68b318872ce6ebebbc62f303b654e43bf72683dbdc25b7c881"}, - {file = "rpds_py-0.27.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:62ac3d4e3e07b58ee0ddecd71d6ce3b1637de2d373501412df395a0ec5f9beb5"}, - {file = "rpds_py-0.27.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4708c5c0ceb2d034f9991623631d3d23cb16e65c83736ea020cdbe28d57c0a0e"}, - {file = "rpds_py-0.27.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:abfa1171a9952d2e0002aba2ad3780820b00cc3d9c98c6630f2e93271501f66c"}, - {file = "rpds_py-0.27.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b507d19f817ebaca79574b16eb2ae412e5c0835542c93fe9983f1e432aca195"}, - {file = "rpds_py-0.27.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:168b025f8fd8d8d10957405f3fdcef3dc20f5982d398f90851f4abc58c566c52"}, - {file = "rpds_py-0.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb56c6210ef77caa58e16e8c17d35c63fe3f5b60fd9ba9d424470c3400bcf9ed"}, - {file = "rpds_py-0.27.1-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:d252f2d8ca0195faa707f8eb9368955760880b2b42a8ee16d382bf5dd807f89a"}, - {file = "rpds_py-0.27.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6e5e54da1e74b91dbc7996b56640f79b195d5925c2b78efaa8c5d53e1d88edde"}, - {file = "rpds_py-0.27.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ffce0481cc6e95e5b3f0a47ee17ffbd234399e6d532f394c8dce320c3b089c21"}, - {file = "rpds_py-0.27.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a205fdfe55c90c2cd8e540ca9ceba65cbe6629b443bc05db1f590a3db8189ff9"}, - {file = "rpds_py-0.27.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:689fb5200a749db0415b092972e8eba85847c23885c8543a8b0f5c009b1a5948"}, - {file = "rpds_py-0.27.1-cp311-cp311-win32.whl", hash = "sha256:3182af66048c00a075010bc7f4860f33913528a4b6fc09094a6e7598e462fe39"}, - {file = "rpds_py-0.27.1-cp311-cp311-win_amd64.whl", hash = "sha256:b4938466c6b257b2f5c4ff98acd8128ec36b5059e5c8f8372d79316b1c36bb15"}, - {file = "rpds_py-0.27.1-cp311-cp311-win_arm64.whl", hash = "sha256:2f57af9b4d0793e53266ee4325535a31ba48e2f875da81a9177c9926dfa60746"}, - {file = "rpds_py-0.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ae2775c1973e3c30316892737b91f9283f9908e3cc7625b9331271eaaed7dc90"}, - {file = "rpds_py-0.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2643400120f55c8a96f7c9d858f7be0c88d383cd4653ae2cf0d0c88f668073e5"}, - {file = "rpds_py-0.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16323f674c089b0360674a4abd28d5042947d54ba620f72514d69be4ff64845e"}, - {file = "rpds_py-0.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9a1f4814b65eacac94a00fc9a526e3fdafd78e439469644032032d0d63de4881"}, - {file = "rpds_py-0.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ba32c16b064267b22f1850a34051121d423b6f7338a12b9459550eb2096e7ec"}, - {file = "rpds_py-0.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5c20f33fd10485b80f65e800bbe5f6785af510b9f4056c5a3c612ebc83ba6cb"}, - {file = "rpds_py-0.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:466bfe65bd932da36ff279ddd92de56b042f2266d752719beb97b08526268ec5"}, - {file = "rpds_py-0.27.1-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:41e532bbdcb57c92ba3be62c42e9f096431b4cf478da9bc3bc6ce5c38ab7ba7a"}, - {file = "rpds_py-0.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f149826d742b406579466283769a8ea448eed82a789af0ed17b0cd5770433444"}, - {file = "rpds_py-0.27.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:80c60cfb5310677bd67cb1e85a1e8eb52e12529545441b43e6f14d90b878775a"}, - {file = "rpds_py-0.27.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7ee6521b9baf06085f62ba9c7a3e5becffbc32480d2f1b351559c001c38ce4c1"}, - {file = "rpds_py-0.27.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a512c8263249a9d68cac08b05dd59d2b3f2061d99b322813cbcc14c3c7421998"}, - {file = "rpds_py-0.27.1-cp312-cp312-win32.whl", hash = "sha256:819064fa048ba01b6dadc5116f3ac48610435ac9a0058bbde98e569f9e785c39"}, - {file = "rpds_py-0.27.1-cp312-cp312-win_amd64.whl", hash = "sha256:d9199717881f13c32c4046a15f024971a3b78ad4ea029e8da6b86e5aa9cf4594"}, - {file = "rpds_py-0.27.1-cp312-cp312-win_arm64.whl", hash = "sha256:33aa65b97826a0e885ef6e278fbd934e98cdcfed80b63946025f01e2f5b29502"}, - {file = "rpds_py-0.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e4b9fcfbc021633863a37e92571d6f91851fa656f0180246e84cbd8b3f6b329b"}, - {file = "rpds_py-0.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1441811a96eadca93c517d08df75de45e5ffe68aa3089924f963c782c4b898cf"}, - {file = "rpds_py-0.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55266dafa22e672f5a4f65019015f90336ed31c6383bd53f5e7826d21a0e0b83"}, - {file = "rpds_py-0.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d78827d7ac08627ea2c8e02c9e5b41180ea5ea1f747e9db0915e3adf36b62dcf"}, - {file = "rpds_py-0.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae92443798a40a92dc5f0b01d8a7c93adde0c4dc965310a29ae7c64d72b9fad2"}, - {file = "rpds_py-0.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c46c9dd2403b66a2a3b9720ec4b74d4ab49d4fabf9f03dfdce2d42af913fe8d0"}, - {file = "rpds_py-0.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2efe4eb1d01b7f5f1939f4ef30ecea6c6b3521eec451fb93191bf84b2a522418"}, - {file = "rpds_py-0.27.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:15d3b4d83582d10c601f481eca29c3f138d44c92187d197aff663a269197c02d"}, - {file = "rpds_py-0.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4ed2e16abbc982a169d30d1a420274a709949e2cbdef119fe2ec9d870b42f274"}, - {file = "rpds_py-0.27.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a75f305c9b013289121ec0f1181931975df78738cdf650093e6b86d74aa7d8dd"}, - {file = "rpds_py-0.27.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:67ce7620704745881a3d4b0ada80ab4d99df390838839921f99e63c474f82cf2"}, - {file = "rpds_py-0.27.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d992ac10eb86d9b6f369647b6a3f412fc0075cfd5d799530e84d335e440a002"}, - {file = "rpds_py-0.27.1-cp313-cp313-win32.whl", hash = "sha256:4f75e4bd8ab8db624e02c8e2fc4063021b58becdbe6df793a8111d9343aec1e3"}, - {file = "rpds_py-0.27.1-cp313-cp313-win_amd64.whl", hash = "sha256:f9025faafc62ed0b75a53e541895ca272815bec18abe2249ff6501c8f2e12b83"}, - {file = "rpds_py-0.27.1-cp313-cp313-win_arm64.whl", hash = "sha256:ed10dc32829e7d222b7d3b93136d25a406ba9788f6a7ebf6809092da1f4d279d"}, - {file = "rpds_py-0.27.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:92022bbbad0d4426e616815b16bc4127f83c9a74940e1ccf3cfe0b387aba0228"}, - {file = "rpds_py-0.27.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:47162fdab9407ec3f160805ac3e154df042e577dd53341745fc7fb3f625e6d92"}, - {file = "rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb89bec23fddc489e5d78b550a7b773557c9ab58b7946154a10a6f7a214a48b2"}, - {file = "rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e48af21883ded2b3e9eb48cb7880ad8598b31ab752ff3be6457001d78f416723"}, - {file = "rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f5b7bd8e219ed50299e58551a410b64daafb5017d54bbe822e003856f06a802"}, - {file = "rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08f1e20bccf73b08d12d804d6e1c22ca5530e71659e6673bce31a6bb71c1e73f"}, - {file = "rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dc5dceeaefcc96dc192e3a80bbe1d6c410c469e97bdd47494a7d930987f18b2"}, - {file = "rpds_py-0.27.1-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:d76f9cc8665acdc0c9177043746775aa7babbf479b5520b78ae4002d889f5c21"}, - {file = "rpds_py-0.27.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:134fae0e36022edad8290a6661edf40c023562964efea0cc0ec7f5d392d2aaef"}, - {file = "rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb11a4f1b2b63337cfd3b4d110af778a59aae51c81d195768e353d8b52f88081"}, - {file = "rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:13e608ac9f50a0ed4faec0e90ece76ae33b34c0e8656e3dceb9a7db994c692cd"}, - {file = "rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dd2135527aa40f061350c3f8f89da2644de26cd73e4de458e79606384f4f68e7"}, - {file = "rpds_py-0.27.1-cp313-cp313t-win32.whl", hash = "sha256:3020724ade63fe320a972e2ffd93b5623227e684315adce194941167fee02688"}, - {file = "rpds_py-0.27.1-cp313-cp313t-win_amd64.whl", hash = "sha256:8ee50c3e41739886606388ba3ab3ee2aae9f35fb23f833091833255a31740797"}, - {file = "rpds_py-0.27.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:acb9aafccaae278f449d9c713b64a9e68662e7799dbd5859e2c6b3c67b56d334"}, - {file = "rpds_py-0.27.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b7fb801aa7f845ddf601c49630deeeccde7ce10065561d92729bfe81bd21fb33"}, - {file = "rpds_py-0.27.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe0dd05afb46597b9a2e11c351e5e4283c741237e7f617ffb3252780cca9336a"}, - {file = "rpds_py-0.27.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b6dfb0e058adb12d8b1d1b25f686e94ffa65d9995a5157afe99743bf7369d62b"}, - {file = "rpds_py-0.27.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed090ccd235f6fa8bb5861684567f0a83e04f52dfc2e5c05f2e4b1309fcf85e7"}, - {file = "rpds_py-0.27.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf876e79763eecf3e7356f157540d6a093cef395b65514f17a356f62af6cc136"}, - {file = "rpds_py-0.27.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12ed005216a51b1d6e2b02a7bd31885fe317e45897de81d86dcce7d74618ffff"}, - {file = "rpds_py-0.27.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:ee4308f409a40e50593c7e3bb8cbe0b4d4c66d1674a316324f0c2f5383b486f9"}, - {file = "rpds_py-0.27.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b08d152555acf1f455154d498ca855618c1378ec810646fcd7c76416ac6dc60"}, - {file = "rpds_py-0.27.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:dce51c828941973a5684d458214d3a36fcd28da3e1875d659388f4f9f12cc33e"}, - {file = "rpds_py-0.27.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:c1476d6f29eb81aa4151c9a31219b03f1f798dc43d8af1250a870735516a1212"}, - {file = "rpds_py-0.27.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3ce0cac322b0d69b63c9cdb895ee1b65805ec9ffad37639f291dd79467bee675"}, - {file = "rpds_py-0.27.1-cp314-cp314-win32.whl", hash = "sha256:dfbfac137d2a3d0725758cd141f878bf4329ba25e34979797c89474a89a8a3a3"}, - {file = "rpds_py-0.27.1-cp314-cp314-win_amd64.whl", hash = "sha256:a6e57b0abfe7cc513450fcf529eb486b6e4d3f8aee83e92eb5f1ef848218d456"}, - {file = "rpds_py-0.27.1-cp314-cp314-win_arm64.whl", hash = "sha256:faf8d146f3d476abfee026c4ae3bdd9ca14236ae4e4c310cbd1cf75ba33d24a3"}, - {file = "rpds_py-0.27.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:ba81d2b56b6d4911ce735aad0a1d4495e808b8ee4dc58715998741a26874e7c2"}, - {file = "rpds_py-0.27.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:84f7d509870098de0e864cad0102711c1e24e9b1a50ee713b65928adb22269e4"}, - {file = "rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e960fc78fecd1100539f14132425e1d5fe44ecb9239f8f27f079962021523e"}, - {file = "rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:62f85b665cedab1a503747617393573995dac4600ff51869d69ad2f39eb5e817"}, - {file = "rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fed467af29776f6556250c9ed85ea5a4dd121ab56a5f8b206e3e7a4c551e48ec"}, - {file = "rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2729615f9d430af0ae6b36cf042cb55c0936408d543fb691e1a9e36648fd35a"}, - {file = "rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b207d881a9aef7ba753d69c123a35d96ca7cb808056998f6b9e8747321f03b8"}, - {file = "rpds_py-0.27.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:639fd5efec029f99b79ae47e5d7e00ad8a773da899b6309f6786ecaf22948c48"}, - {file = "rpds_py-0.27.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fecc80cb2a90e28af8a9b366edacf33d7a91cbfe4c2c4544ea1246e949cfebeb"}, - {file = "rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:42a89282d711711d0a62d6f57d81aa43a1368686c45bc1c46b7f079d55692734"}, - {file = "rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:cf9931f14223de59551ab9d38ed18d92f14f055a5f78c1d8ad6493f735021bbb"}, - {file = "rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f39f58a27cc6e59f432b568ed8429c7e1641324fbe38131de852cd77b2d534b0"}, - {file = "rpds_py-0.27.1-cp314-cp314t-win32.whl", hash = "sha256:d5fa0ee122dc09e23607a28e6d7b150da16c662e66409bbe85230e4c85bb528a"}, - {file = "rpds_py-0.27.1-cp314-cp314t-win_amd64.whl", hash = "sha256:6567d2bb951e21232c2f660c24cf3470bb96de56cdcb3f071a83feeaff8a2772"}, - {file = "rpds_py-0.27.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c918c65ec2e42c2a78d19f18c553d77319119bf43aa9e2edf7fb78d624355527"}, - {file = "rpds_py-0.27.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1fea2b1a922c47c51fd07d656324531adc787e415c8b116530a1d29c0516c62d"}, - {file = "rpds_py-0.27.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbf94c58e8e0cd6b6f38d8de67acae41b3a515c26169366ab58bdca4a6883bb8"}, - {file = "rpds_py-0.27.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c2a8fed130ce946d5c585eddc7c8eeef0051f58ac80a8ee43bd17835c144c2cc"}, - {file = "rpds_py-0.27.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:037a2361db72ee98d829bc2c5b7cc55598ae0a5e0ec1823a56ea99374cfd73c1"}, - {file = "rpds_py-0.27.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5281ed1cc1d49882f9997981c88df1a22e140ab41df19071222f7e5fc4e72125"}, - {file = "rpds_py-0.27.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fd50659a069c15eef8aa3d64bbef0d69fd27bb4a50c9ab4f17f83a16cbf8905"}, - {file = "rpds_py-0.27.1-cp39-cp39-manylinux_2_31_riscv64.whl", hash = "sha256:c4b676c4ae3921649a15d28ed10025548e9b561ded473aa413af749503c6737e"}, - {file = "rpds_py-0.27.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:079bc583a26db831a985c5257797b2b5d3affb0386e7ff886256762f82113b5e"}, - {file = "rpds_py-0.27.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4e44099bd522cba71a2c6b97f68e19f40e7d85399de899d66cdb67b32d7cb786"}, - {file = "rpds_py-0.27.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e202e6d4188e53c6661af813b46c37ca2c45e497fc558bacc1a7630ec2695aec"}, - {file = "rpds_py-0.27.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f41f814b8eaa48768d1bb551591f6ba45f87ac76899453e8ccd41dba1289b04b"}, - {file = "rpds_py-0.27.1-cp39-cp39-win32.whl", hash = "sha256:9e71f5a087ead99563c11fdaceee83ee982fd39cf67601f4fd66cb386336ee52"}, - {file = "rpds_py-0.27.1-cp39-cp39-win_amd64.whl", hash = "sha256:71108900c9c3c8590697244b9519017a400d9ba26a36c48381b3f64743a44aab"}, - {file = "rpds_py-0.27.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7ba22cb9693df986033b91ae1d7a979bc399237d45fccf875b76f62bb9e52ddf"}, - {file = "rpds_py-0.27.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5b640501be9288c77738b5492b3fd3abc4ba95c50c2e41273c8a1459f08298d3"}, - {file = "rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb08b65b93e0c6dd70aac7f7890a9c0938d5ec71d5cb32d45cf844fb8ae47636"}, - {file = "rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d7ff07d696a7a38152ebdb8212ca9e5baab56656749f3d6004b34ab726b550b8"}, - {file = "rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fb7c72262deae25366e3b6c0c0ba46007967aea15d1eea746e44ddba8ec58dcc"}, - {file = "rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b002cab05d6339716b03a4a3a2ce26737f6231d7b523f339fa061d53368c9d8"}, - {file = "rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23f6b69d1c26c4704fec01311963a41d7de3ee0570a84ebde4d544e5a1859ffc"}, - {file = "rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:530064db9146b247351f2a0250b8f00b289accea4596a033e94be2389977de71"}, - {file = "rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7b90b0496570bd6b0321724a330d8b545827c4df2034b6ddfc5f5275f55da2ad"}, - {file = "rpds_py-0.27.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:879b0e14a2da6a1102a3fc8af580fc1ead37e6d6692a781bd8c83da37429b5ab"}, - {file = "rpds_py-0.27.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:0d807710df3b5faa66c731afa162ea29717ab3be17bdc15f90f2d9f183da4059"}, - {file = "rpds_py-0.27.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:3adc388fc3afb6540aec081fa59e6e0d3908722771aa1e37ffe22b220a436f0b"}, - {file = "rpds_py-0.27.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c796c0c1cc68cb08b0284db4229f5af76168172670c74908fdbd4b7d7f515819"}, - {file = "rpds_py-0.27.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cdfe4bb2f9fe7458b7453ad3c33e726d6d1c7c0a72960bcc23800d77384e42df"}, - {file = "rpds_py-0.27.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:8fabb8fd848a5f75a2324e4a84501ee3a5e3c78d8603f83475441866e60b94a3"}, - {file = "rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eda8719d598f2f7f3e0f885cba8646644b55a187762bec091fa14a2b819746a9"}, - {file = "rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c64d07e95606ec402a0a1c511fe003873fa6af630bda59bac77fac8b4318ebc"}, - {file = "rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:93a2ed40de81bcff59aabebb626562d48332f3d028ca2036f1d23cbb52750be4"}, - {file = "rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:387ce8c44ae94e0ec50532d9cb0edce17311024c9794eb196b90e1058aadeb66"}, - {file = "rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aaf94f812c95b5e60ebaf8bfb1898a7d7cb9c1af5744d4a67fa47796e0465d4e"}, - {file = "rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:4848ca84d6ded9b58e474dfdbad4b8bfb450344c0551ddc8d958bf4b36aa837c"}, - {file = "rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2bde09cbcf2248b73c7c323be49b280180ff39fadcfe04e7b6f54a678d02a7cf"}, - {file = "rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:94c44ee01fd21c9058f124d2d4f0c9dc7634bec93cd4b38eefc385dabe71acbf"}, - {file = "rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:df8b74962e35c9249425d90144e721eed198e6555a0e22a563d29fe4486b51f6"}, - {file = "rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:dc23e6820e3b40847e2f4a7726462ba0cf53089512abe9ee16318c366494c17a"}, - {file = "rpds_py-0.27.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:aa8933159edc50be265ed22b401125c9eebff3171f570258854dbce3ecd55475"}, - {file = "rpds_py-0.27.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a50431bf02583e21bf273c71b89d710e7a710ad5e39c725b14e685610555926f"}, - {file = "rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78af06ddc7fe5cc0e967085a9115accee665fb912c22a3f54bad70cc65b05fe6"}, - {file = "rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:70d0738ef8fee13c003b100c2fbd667ec4f133468109b3472d249231108283a3"}, - {file = "rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2f6fd8a1cea5bbe599b6e78a6e5ee08db434fc8ffea51ff201c8765679698b3"}, - {file = "rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8177002868d1426305bb5de1e138161c2ec9eb2d939be38291d7c431c4712df8"}, - {file = "rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:008b839781d6c9bf3b6a8984d1d8e56f0ec46dc56df61fd669c49b58ae800400"}, - {file = "rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:a55b9132bb1ade6c734ddd2759c8dc132aa63687d259e725221f106b83a0e485"}, - {file = "rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a46fdec0083a26415f11d5f236b79fa1291c32aaa4a17684d82f7017a1f818b1"}, - {file = "rpds_py-0.27.1-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:8a63b640a7845f2bdd232eb0d0a4a2dd939bcdd6c57e6bb134526487f3160ec5"}, - {file = "rpds_py-0.27.1-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:7e32721e5d4922deaaf963469d795d5bde6093207c52fec719bd22e5d1bedbc4"}, - {file = "rpds_py-0.27.1-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:2c426b99a068601b5f4623573df7a7c3d72e87533a2dd2253353a03e7502566c"}, - {file = "rpds_py-0.27.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4fc9b7fe29478824361ead6e14e4f5aed570d477e06088826537e202d25fe859"}, - {file = "rpds_py-0.27.1.tar.gz", hash = "sha256:26a1c73171d10b7acccbded82bf6a586ab8203601e565badc74bbbf8bc5a10f8"}, -] - -[[package]] -name = "scikit-base" -version = "0.12.6" -description = "Base classes for sklearn-like parametric objects" -optional = false -python-versions = "<3.14,>=3.9" -files = [ - {file = "scikit_base-0.12.6-py3-none-any.whl", hash = "sha256:68369cf633502e7649c6e029a78e30063154ee2eaccad34a48e19e64272a820f"}, - {file = "scikit_base-0.12.6.tar.gz", hash = "sha256:553e2bafaf30cd91d873bf424a9d16772d7638ea02ded6fb649b5d4b4c236d14"}, -] - -[package.extras] -all-extras = ["numpy", "pandas"] -binder = ["jupyter"] -dev = ["pre-commit", "pytest", "pytest-cov", "scikit-learn (>=0.24.0)"] -docs = ["Sphinx (!=7.2.0,<9.0.0)", "jupyter", "myst-parser", "nbsphinx (>=0.8.6)", "numpydoc", "pydata-sphinx-theme", "sphinx-design (<0.7.0)", "sphinx-gallery (<0.20.0)", "sphinx-issues (<6.0.0)", "sphinx-panels", "tabulate"] -linters = ["black", "doc8", "flake8", "flake8-bugbear", "flake8-builtins", "flake8-comprehensions", "flake8-print", "flake8-quotes", "isort", "mypy", "nbqa", "pandas-vet", "pep8-naming", "pydocstyle"] -test = ["coverage", "numpy", "pandas", "pytest", "pytest-cov", "safety", "scikit-learn (>=0.24.0)", "scipy"] - -[[package]] -name = "scikit-learn" -version = "1.7.2" -description = "A set of python modules for machine learning and data mining" -optional = false -python-versions = ">=3.10" -files = [ - {file = "scikit_learn-1.7.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b33579c10a3081d076ab403df4a4190da4f4432d443521674637677dc91e61f"}, - {file = "scikit_learn-1.7.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:36749fb62b3d961b1ce4fedf08fa57a1986cd409eff2d783bca5d4b9b5fce51c"}, - {file = "scikit_learn-1.7.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7a58814265dfc52b3295b1900cfb5701589d30a8bb026c7540f1e9d3499d5ec8"}, - {file = "scikit_learn-1.7.2-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a847fea807e278f821a0406ca01e387f97653e284ecbd9750e3ee7c90347f18"}, - {file = "scikit_learn-1.7.2-cp310-cp310-win_amd64.whl", hash = "sha256:ca250e6836d10e6f402436d6463d6c0e4d8e0234cfb6a9a47835bd392b852ce5"}, - {file = "scikit_learn-1.7.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7509693451651cd7361d30ce4e86a1347493554f172b1c72a39300fa2aea79e"}, - {file = "scikit_learn-1.7.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:0486c8f827c2e7b64837c731c8feff72c0bd2b998067a8a9cbc10643c31f0fe1"}, - {file = "scikit_learn-1.7.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:89877e19a80c7b11a2891a27c21c4894fb18e2c2e077815bcade10d34287b20d"}, - {file = "scikit_learn-1.7.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8da8bf89d4d79aaec192d2bda62f9b56ae4e5b4ef93b6a56b5de4977e375c1f1"}, - {file = "scikit_learn-1.7.2-cp311-cp311-win_amd64.whl", hash = "sha256:9b7ed8d58725030568523e937c43e56bc01cadb478fc43c042a9aca1dacb3ba1"}, - {file = "scikit_learn-1.7.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8d91a97fa2b706943822398ab943cde71858a50245e31bc71dba62aab1d60a96"}, - {file = "scikit_learn-1.7.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:acbc0f5fd2edd3432a22c69bed78e837c70cf896cd7993d71d51ba6708507476"}, - {file = "scikit_learn-1.7.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e5bf3d930aee75a65478df91ac1225ff89cd28e9ac7bd1196853a9229b6adb0b"}, - {file = "scikit_learn-1.7.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4d6e9deed1a47aca9fe2f267ab8e8fe82ee20b4526b2c0cd9e135cea10feb44"}, - {file = "scikit_learn-1.7.2-cp312-cp312-win_amd64.whl", hash = "sha256:6088aa475f0785e01bcf8529f55280a3d7d298679f50c0bb70a2364a82d0b290"}, - {file = "scikit_learn-1.7.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0b7dacaa05e5d76759fb071558a8b5130f4845166d88654a0f9bdf3eb57851b7"}, - {file = "scikit_learn-1.7.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:abebbd61ad9e1deed54cca45caea8ad5f79e1b93173dece40bb8e0c658dbe6fe"}, - {file = "scikit_learn-1.7.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:502c18e39849c0ea1a5d681af1dbcf15f6cce601aebb657aabbfe84133c1907f"}, - {file = "scikit_learn-1.7.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a4c328a71785382fe3fe676a9ecf2c86189249beff90bf85e22bdb7efaf9ae0"}, - {file = "scikit_learn-1.7.2-cp313-cp313-win_amd64.whl", hash = "sha256:63a9afd6f7b229aad94618c01c252ce9e6fa97918c5ca19c9a17a087d819440c"}, - {file = "scikit_learn-1.7.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9acb6c5e867447b4e1390930e3944a005e2cb115922e693c08a323421a6966e8"}, - {file = "scikit_learn-1.7.2-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:2a41e2a0ef45063e654152ec9d8bcfc39f7afce35b08902bfe290c2498a67a6a"}, - {file = "scikit_learn-1.7.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98335fb98509b73385b3ab2bd0639b1f610541d3988ee675c670371d6a87aa7c"}, - {file = "scikit_learn-1.7.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:191e5550980d45449126e23ed1d5e9e24b2c68329ee1f691a3987476e115e09c"}, - {file = "scikit_learn-1.7.2-cp313-cp313t-win_amd64.whl", hash = "sha256:57dc4deb1d3762c75d685507fbd0bc17160144b2f2ba4ccea5dc285ab0d0e973"}, - {file = "scikit_learn-1.7.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fa8f63940e29c82d1e67a45d5297bdebbcb585f5a5a50c4914cc2e852ab77f33"}, - {file = "scikit_learn-1.7.2-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:f95dc55b7902b91331fa4e5845dd5bde0580c9cd9612b1b2791b7e80c3d32615"}, - {file = "scikit_learn-1.7.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9656e4a53e54578ad10a434dc1f993330568cfee176dff07112b8785fb413106"}, - {file = "scikit_learn-1.7.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96dc05a854add0e50d3f47a1ef21a10a595016da5b007c7d9cd9d0bffd1fcc61"}, - {file = "scikit_learn-1.7.2-cp314-cp314-win_amd64.whl", hash = "sha256:bb24510ed3f9f61476181e4db51ce801e2ba37541def12dc9333b946fc7a9cf8"}, - {file = "scikit_learn-1.7.2.tar.gz", hash = "sha256:20e9e49ecd130598f1ca38a1d85090e1a600147b9c02fa6f15d69cb53d968fda"}, -] - -[package.dependencies] -joblib = ">=1.2.0" -numpy = ">=1.22.0" -scipy = ">=1.8.0" -threadpoolctl = ">=3.1.0" - -[package.extras] -benchmark = ["matplotlib (>=3.5.0)", "memory_profiler (>=0.57.0)", "pandas (>=1.4.0)"] -build = ["cython (>=3.0.10)", "meson-python (>=0.17.1)", "numpy (>=1.22.0)", "scipy (>=1.8.0)"] -docs = ["Pillow (>=8.4.0)", "matplotlib (>=3.5.0)", "memory_profiler (>=0.57.0)", "numpydoc (>=1.2.0)", "pandas (>=1.4.0)", "plotly (>=5.14.0)", "polars (>=0.20.30)", "pooch (>=1.6.0)", "pydata-sphinx-theme (>=0.15.3)", "scikit-image (>=0.19.0)", "seaborn (>=0.9.0)", "sphinx (>=7.3.7)", "sphinx-copybutton (>=0.5.2)", "sphinx-design (>=0.5.0)", "sphinx-design (>=0.6.0)", "sphinx-gallery (>=0.17.1)", "sphinx-prompt (>=1.4.0)", "sphinx-remove-toctrees (>=1.0.0.post1)", "sphinxcontrib-sass (>=0.3.4)", "sphinxext-opengraph (>=0.9.1)", "towncrier (>=24.8.0)"] -examples = ["matplotlib (>=3.5.0)", "pandas (>=1.4.0)", "plotly (>=5.14.0)", "pooch (>=1.6.0)", "scikit-image (>=0.19.0)", "seaborn (>=0.9.0)"] -install = ["joblib (>=1.2.0)", "numpy (>=1.22.0)", "scipy (>=1.8.0)", "threadpoolctl (>=3.1.0)"] -maintenance = ["conda-lock (==3.0.1)"] -tests = ["matplotlib (>=3.5.0)", "mypy (>=1.15)", "numpydoc (>=1.2.0)", "pandas (>=1.4.0)", "polars (>=0.20.30)", "pooch (>=1.6.0)", "pyamg (>=4.2.1)", "pyarrow (>=12.0.0)", "pytest (>=7.1.2)", "pytest-cov (>=2.9.0)", "ruff (>=0.11.7)", "scikit-image (>=0.19.0)"] - -[[package]] -name = "scipy" -version = "1.16.2" -description = "Fundamental algorithms for scientific computing in Python" -optional = false -python-versions = ">=3.11" -files = [ - {file = "scipy-1.16.2-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:6ab88ea43a57da1af33292ebd04b417e8e2eaf9d5aa05700be8d6e1b6501cd92"}, - {file = "scipy-1.16.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:c95e96c7305c96ede73a7389f46ccd6c659c4da5ef1b2789466baeaed3622b6e"}, - {file = "scipy-1.16.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:87eb178db04ece7c698220d523c170125dbffebb7af0345e66c3554f6f60c173"}, - {file = "scipy-1.16.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:4e409eac067dcee96a57fbcf424c13f428037827ec7ee3cb671ff525ca4fc34d"}, - {file = "scipy-1.16.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e574be127bb760f0dad24ff6e217c80213d153058372362ccb9555a10fc5e8d2"}, - {file = "scipy-1.16.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f5db5ba6188d698ba7abab982ad6973265b74bb40a1efe1821b58c87f73892b9"}, - {file = "scipy-1.16.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ec6e74c4e884104ae006d34110677bfe0098203a3fec2f3faf349f4cb05165e3"}, - {file = "scipy-1.16.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:912f46667d2d3834bc3d57361f854226475f695eb08c08a904aadb1c936b6a88"}, - {file = "scipy-1.16.2-cp311-cp311-win_amd64.whl", hash = "sha256:91e9e8a37befa5a69e9cacbe0bcb79ae5afb4a0b130fd6db6ee6cc0d491695fa"}, - {file = "scipy-1.16.2-cp311-cp311-win_arm64.whl", hash = "sha256:f3bf75a6dcecab62afde4d1f973f1692be013110cad5338007927db8da73249c"}, - {file = "scipy-1.16.2-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:89d6c100fa5c48472047632e06f0876b3c4931aac1f4291afc81a3644316bb0d"}, - {file = "scipy-1.16.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:ca748936cd579d3f01928b30a17dc474550b01272d8046e3e1ee593f23620371"}, - {file = "scipy-1.16.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:fac4f8ce2ddb40e2e3d0f7ec36d2a1e7f92559a2471e59aec37bd8d9de01fec0"}, - {file = "scipy-1.16.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:033570f1dcefd79547a88e18bccacff025c8c647a330381064f561d43b821232"}, - {file = "scipy-1.16.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ea3421209bf00c8a5ef2227de496601087d8f638a2363ee09af059bd70976dc1"}, - {file = "scipy-1.16.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f66bd07ba6f84cd4a380b41d1bf3c59ea488b590a2ff96744845163309ee8e2f"}, - {file = "scipy-1.16.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e9feab931bd2aea4a23388c962df6468af3d808ddf2d40f94a81c5dc38f32ef"}, - {file = "scipy-1.16.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03dfc75e52f72cf23ec2ced468645321407faad8f0fe7b1f5b49264adbc29cb1"}, - {file = "scipy-1.16.2-cp312-cp312-win_amd64.whl", hash = "sha256:0ce54e07bbb394b417457409a64fd015be623f36e330ac49306433ffe04bc97e"}, - {file = "scipy-1.16.2-cp312-cp312-win_arm64.whl", hash = "sha256:2a8ffaa4ac0df81a0b94577b18ee079f13fecdb924df3328fc44a7dc5ac46851"}, - {file = "scipy-1.16.2-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:84f7bf944b43e20b8a894f5fe593976926744f6c185bacfcbdfbb62736b5cc70"}, - {file = "scipy-1.16.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:5c39026d12edc826a1ef2ad35ad1e6d7f087f934bb868fc43fa3049c8b8508f9"}, - {file = "scipy-1.16.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e52729ffd45b68777c5319560014d6fd251294200625d9d70fd8626516fc49f5"}, - {file = "scipy-1.16.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:024dd4a118cccec09ca3209b7e8e614931a6ffb804b2a601839499cb88bdf925"}, - {file = "scipy-1.16.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7a5dc7ee9c33019973a470556081b0fd3c9f4c44019191039f9769183141a4d9"}, - {file = "scipy-1.16.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c2275ff105e508942f99d4e3bc56b6ef5e4b3c0af970386ca56b777608ce95b7"}, - {file = "scipy-1.16.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:af80196eaa84f033e48444d2e0786ec47d328ba00c71e4299b602235ffef9acb"}, - {file = "scipy-1.16.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9fb1eb735fe3d6ed1f89918224e3385fbf6f9e23757cacc35f9c78d3b712dd6e"}, - {file = "scipy-1.16.2-cp313-cp313-win_amd64.whl", hash = "sha256:fda714cf45ba43c9d3bae8f2585c777f64e3f89a2e073b668b32ede412d8f52c"}, - {file = "scipy-1.16.2-cp313-cp313-win_arm64.whl", hash = "sha256:2f5350da923ccfd0b00e07c3e5cfb316c1c0d6c1d864c07a72d092e9f20db104"}, - {file = "scipy-1.16.2-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:53d8d2ee29b925344c13bda64ab51785f016b1b9617849dac10897f0701b20c1"}, - {file = "scipy-1.16.2-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:9e05e33657efb4c6a9d23bd8300101536abd99c85cca82da0bffff8d8764d08a"}, - {file = "scipy-1.16.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:7fe65b36036357003b3ef9d37547abeefaa353b237e989c21027b8ed62b12d4f"}, - {file = "scipy-1.16.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:6406d2ac6d40b861cccf57f49592f9779071655e9f75cd4f977fa0bdd09cb2e4"}, - {file = "scipy-1.16.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ff4dc42bd321991fbf611c23fc35912d690f731c9914bf3af8f417e64aca0f21"}, - {file = "scipy-1.16.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:654324826654d4d9133e10675325708fb954bc84dae6e9ad0a52e75c6b1a01d7"}, - {file = "scipy-1.16.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:63870a84cd15c44e65220eaed2dac0e8f8b26bbb991456a033c1d9abfe8a94f8"}, - {file = "scipy-1.16.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:fa01f0f6a3050fa6a9771a95d5faccc8e2f5a92b4a2e5440a0fa7264a2398472"}, - {file = "scipy-1.16.2-cp313-cp313t-win_amd64.whl", hash = "sha256:116296e89fba96f76353a8579820c2512f6e55835d3fad7780fece04367de351"}, - {file = "scipy-1.16.2-cp313-cp313t-win_arm64.whl", hash = "sha256:98e22834650be81d42982360382b43b17f7ba95e0e6993e2a4f5b9ad9283a94d"}, - {file = "scipy-1.16.2-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:567e77755019bb7461513c87f02bb73fb65b11f049aaaa8ca17cfaa5a5c45d77"}, - {file = "scipy-1.16.2-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:17d9bb346194e8967296621208fcdfd39b55498ef7d2f376884d5ac47cec1a70"}, - {file = "scipy-1.16.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:0a17541827a9b78b777d33b623a6dcfe2ef4a25806204d08ead0768f4e529a88"}, - {file = "scipy-1.16.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:d7d4c6ba016ffc0f9568d012f5f1eb77ddd99412aea121e6fa8b4c3b7cbad91f"}, - {file = "scipy-1.16.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9702c4c023227785c779cba2e1d6f7635dbb5b2e0936cdd3a4ecb98d78fd41eb"}, - {file = "scipy-1.16.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d1cdf0ac28948d225decdefcc45ad7dd91716c29ab56ef32f8e0d50657dffcc7"}, - {file = "scipy-1.16.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:70327d6aa572a17c2941cdfb20673f82e536e91850a2e4cb0c5b858b690e1548"}, - {file = "scipy-1.16.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5221c0b2a4b58aa7c4ed0387d360fd90ee9086d383bb34d9f2789fafddc8a936"}, - {file = "scipy-1.16.2-cp314-cp314-win_amd64.whl", hash = "sha256:f5a85d7b2b708025af08f060a496dd261055b617d776fc05a1a1cc69e09fe9ff"}, - {file = "scipy-1.16.2-cp314-cp314-win_arm64.whl", hash = "sha256:2cc73a33305b4b24556957d5857d6253ce1e2dcd67fa0ff46d87d1670b3e1e1d"}, - {file = "scipy-1.16.2-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:9ea2a3fed83065d77367775d689401a703d0f697420719ee10c0780bcab594d8"}, - {file = "scipy-1.16.2-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:7280d926f11ca945c3ef92ba960fa924e1465f8d07ce3a9923080363390624c4"}, - {file = "scipy-1.16.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:8afae1756f6a1fe04636407ef7dbece33d826a5d462b74f3d0eb82deabefd831"}, - {file = "scipy-1.16.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:5c66511f29aa8d233388e7416a3f20d5cae7a2744d5cee2ecd38c081f4e861b3"}, - {file = "scipy-1.16.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:efe6305aeaa0e96b0ccca5ff647a43737d9a092064a3894e46c414db84bc54ac"}, - {file = "scipy-1.16.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f3a337d9ae06a1e8d655ee9d8ecb835ea5ddcdcbd8d23012afa055ab014f374"}, - {file = "scipy-1.16.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bab3605795d269067d8ce78a910220262711b753de8913d3deeaedb5dded3bb6"}, - {file = "scipy-1.16.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b0348d8ddb55be2a844c518cd8cc8deeeb8aeba707cf834db5758fc89b476a2c"}, - {file = "scipy-1.16.2-cp314-cp314t-win_amd64.whl", hash = "sha256:26284797e38b8a75e14ea6631d29bda11e76ceaa6ddb6fdebbfe4c4d90faf2f9"}, - {file = "scipy-1.16.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d2a4472c231328d4de38d5f1f68fdd6d28a615138f842580a8a321b5845cf779"}, - {file = "scipy-1.16.2.tar.gz", hash = "sha256:af029b153d243a80afb6eabe40b0a07f8e35c9adc269c019f364ad747f826a6b"}, -] - -[package.dependencies] -numpy = ">=1.25.2,<2.6" - -[package.extras] -dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodestyle", "pydevtool", "rich-click", "ruff (>=0.0.292)", "types-psutil", "typing_extensions"] -doc = ["intersphinx_registry", "jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.19.1)", "jupytext", "linkify-it-py", "matplotlib (>=3.5)", "myst-nb (>=1.2.0)", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<8.2.0)", "sphinx-copybutton", "sphinx-design (>=0.4.0)"] -test = ["Cython", "array-api-strict (>=2.3.1)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja", "pooch", "pytest (>=8.0.0)", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] - -[[package]] -name = "seaborn" -version = "0.13.2" -description = "Statistical data visualization" -optional = false -python-versions = ">=3.8" -files = [ - {file = "seaborn-0.13.2-py3-none-any.whl", hash = "sha256:636f8336facf092165e27924f223d3c62ca560b1f2bb5dff7ab7fad265361987"}, - {file = "seaborn-0.13.2.tar.gz", hash = "sha256:93e60a40988f4d65e9f4885df477e2fdaff6b73a9ded434c1ab356dd57eefff7"}, -] - -[package.dependencies] -matplotlib = ">=3.4,<3.6.1 || >3.6.1" -numpy = ">=1.20,<1.24.0 || >1.24.0" -pandas = ">=1.2" - -[package.extras] -dev = ["flake8", "flit", "mypy", "pandas-stubs", "pre-commit", "pytest", "pytest-cov", "pytest-xdist"] -docs = ["ipykernel", "nbconvert", "numpydoc", "pydata_sphinx_theme (==0.10.0rc2)", "pyyaml", "sphinx (<6.0.0)", "sphinx-copybutton", "sphinx-design", "sphinx-issues"] -stats = ["scipy (>=1.7)", "statsmodels (>=0.12)"] - -[[package]] -name = "send2trash" -version = "1.8.3" -description = "Send file to trash natively under Mac OS X, Windows and Linux" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" -files = [ - {file = "Send2Trash-1.8.3-py3-none-any.whl", hash = "sha256:0c31227e0bd08961c7665474a3d1ef7193929fedda4233843689baa056be46c9"}, - {file = "Send2Trash-1.8.3.tar.gz", hash = "sha256:b18e7a3966d99871aefeb00cfbcfdced55ce4871194810fc71f4aa484b953abf"}, -] - -[package.extras] -nativelib = ["pyobjc-framework-Cocoa", "pywin32"] -objc = ["pyobjc-framework-Cocoa"] -win32 = ["pywin32"] - -[[package]] -name = "setuptools" -version = "80.9.0" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -optional = false -python-versions = ">=3.9" -files = [ - {file = "setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922"}, - {file = "setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c"}, -] - -[package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.8.0)"] -core = ["importlib_metadata (>=6)", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] -cover = ["pytest-cov"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] -enabler = ["pytest-enabler (>=2.2)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.14.*)", "pytest-mypy"] - -[[package]] -name = "six" -version = "1.17.0" -description = "Python 2 and 3 compatibility utilities" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -files = [ - {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, - {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, -] - -[[package]] -name = "sktime" -version = "0.39.0" -description = "A unified framework for machine learning with time series" -optional = false -python-versions = "<3.14,>=3.10" -files = [ - {file = "sktime-0.39.0-py3-none-any.whl", hash = "sha256:4fa722866336d7f43f82c9239cd2c9d3a89aab553aa5c59ce4bfbc212d4d56bd"}, - {file = "sktime-0.39.0.tar.gz", hash = "sha256:1ab976faa55b2a118f93d15828d1a13be3d963f693fdd05fdf82f882405b9d13"}, -] - -[package.dependencies] -joblib = ">=1.2.0,<1.6" -numpy = ">=1.21,<2.4" -packaging = "*" -pandas = ">=1.1,<2.4.0" -scikit-base = ">=0.6.1,<0.13.0" -scikit-learn = ">=0.24,<1.8.0" -scipy = ">=1.2,<2.0.0" - -[package.extras] -alignment = ["dtaidistance (<2.4)", "dtw-python (>=1.3,<1.6)", "numba (>=0.53,<0.63)"] -all-extras = ["arch (>=5.6,<7.3.0)", "autots (>=0.6.1,<0.7)", "cloudpickle", "dash (!=2.9.0)", "dask (>2024.8.2,<2025.2.1)", "dtw-python", "gluonts (>=0.9)", "h5py", "hmmlearn (>=0.2.7)", "holidays", "matplotlib (>=3.3.2,!=3.9.1)", "numba (>=0.53,<0.63)", "optuna (<4.5)", "pmdarima (>=1.8,!=1.8.1,<3.0.0)", "polars[pandas] (>=0.20,<2.0)", "prophet (>=1.1)", "pycatch22 (<0.4.6)", "pyod (>=0.8)", "pyts (<0.14.0)", "ray (>=2.40.0)", "scikit-optimize", "scikit_posthocs (>=0.6.5)", "seaborn (>=0.11)", "simdkalman", "skforecast (>=0.12.1,<0.15)", "skpro (>=2,<2.10.0)", "statsforecast (>=1.0.0,<2.1.0)", "statsmodels (>=0.12.1)", "tensorflow (>=2,<2.20)", "tsfresh (>=0.17)", "tslearn (>=0.5.2,!=0.6.0,<0.7.0)", "xarray"] -all-extras-pandas2 = ["arch (>=5.6,<7.1.0)", "autots (>=0.6.1,<0.7)", "cloudpickle", "dash (!=2.9.0)", "dask (>2024.8.2,<2025.2.1)", "dtw-python", "gluonts (>=0.9)", "h5py", "hmmlearn (>=0.2.7)", "holidays", "matplotlib (>=3.3.2,!=3.9.1)", "numba (>=0.53,<0.63)", "optuna (<4.5)", "pmdarima (>=1.8,!=1.8.1,<3.0.0)", "polars[pandas] (>=0.20,<2.0)", "prophet (>=1.1)", "pycatch22 (<0.4.6)", "pyod (>=0.8)", "ray (>=2.40.0)", "scikit_posthocs (>=0.6.5)", "seaborn (>=0.11)", "simdkalman", "skforecast (>=0.12.1,<0.15)", "skpro (>=2,<2.10.0)", "statsforecast (>=1.0.0,<2.1.0)", "statsmodels (>=0.12.1)", "tensorflow (>=2,<2.20)", "tsfresh (>=0.17)", "tslearn (>=0.5.2,!=0.6.0,<0.7.0)", "xarray"] -annotation = ["hmmlearn (>=0.2.7,<0.4)", "numba (>=0.53,<0.63)", "pyod (>=0.8,<1.2)"] -binder = ["jupyter", "skchange"] -classification = ["numba (>=0.53,<0.63)", "tensorflow (>=2,<2.20)", "tsfresh (>=0.17,<0.21)"] -clustering = ["networkx (<3.5)", "numba (>=0.53,<0.63)", "ts2vg (<1.3)", "tslearn (>=0.5.2,!=0.6.0,<0.7.0)"] -compatibility-tests = ["catboost"] -cython-extras = ["mrseql (<0.0.3)", "mrsqm", "numba (<0.63)"] -dataframe = ["dask (>2024.8.2,<2025.2.1)", "dask (>2024.8.2,<2025.2.1)"] -datasets = ["huggingface-hub", "rdata", "requests"] -dependencies-lower = ["numpy (==1.25.0)", "pandas (==2.0.2)", "scikit-learn (==1.3.0)", "scipy (==1.10.1)"] -dependencies-lowest = ["numpy (==1.21.0)", "pandas (==1.1.0)", "scikit-learn (==0.24.0)", "scipy (==1.4.0)"] -detection = ["hmmlearn (>=0.2.7,<0.4)", "numba (>=0.53,<0.63)", "pyod (>=0.8,<1.2)"] -dev = ["backoff", "httpx", "pre-commit", "pytest", "pytest-randomly", "pytest-timeout", "pytest-xdist", "wheel"] -dl = ["FrEIA", "accelerate", "einops (>0.7.0)", "gluonts (>=0.14.3)", "huggingface-hub (>=0.23.0)", "hydra-core", "lightning (>=2.0)", "neuralforecast (>=1.6.4,<1.8.0)", "peft (>=0.10.0,<0.14.0)", "pykan (>=0.2.1,<0.2.9)", "pytorch-forecasting (>=1.0.0,<1.5.0)", "tensorflow (>=2,<2.20)", "torch", "tqdm", "transformers[torch] (<4.41.0)"] -docs = ["Sphinx (!=7.2.0,<9.0.0)", "jupyter", "myst-parser", "nbsphinx (>=0.8.6)", "numpydoc", "pydata-sphinx-theme", "sphinx-copybutton", "sphinx-design (<0.7.0)", "sphinx-gallery (<0.20.0)", "sphinx-issues (<6.0.0)", "tabulate"] -forecasting = ["arch (>=5.6,<7.1)", "autots (>=0.6.1,<0.7)", "pmdarima (>=1.8,!=1.8.1,<2.1)", "prophet (>=1.1,<1.2)", "skforecast (>=0.12.1,<0.15)", "skpro (>=2,<2.10.0)", "statsforecast (>=1.0.0,<2.1.0)", "statsmodels (>=0.12.1,<0.15)"] -mlflow = ["mlflow (<4.0)"] -mlflow-tests = ["boto3", "botocore", "mlflow (<4.0)", "moto"] -mlflow2 = ["mlflow (<3.0)"] -networks = ["tensorflow (>=2,<2.20)"] -notebooks = ["dtw-python", "matplotlib", "numpy (<2)", "pmdarima", "prophet", "pytorch-forecasting", "seaborn", "skpro", "statsforecast", "tbats"] -numpy1 = ["numpy (<2.0.0)"] -pandas1 = ["pandas (<2.0.0)"] -param-est = ["seasonal (>=0.3.1,<0.4)", "statsmodels (>=0.12.1,<0.15)"] -regression = ["numba (>=0.53,<0.63)", "tensorflow (>=2,<2.20)"] -tests = ["pytest (>=7.4,<8.5)", "pytest-randomly (>=3.15,<4.1)", "pytest-timeout (>=2.1,<2.5)", "pytest-xdist (>=3.3,<3.9)"] -transformations = ["holidays (>=0.29,<0.59)", "numba (>=0.53,<0.63)", "pycatch22 (>=0.4,<0.4.6)", "simdkalman", "statsmodels (>=0.12.1,<0.15)", "tsfresh (>=0.17,<0.21)"] - -[[package]] -name = "sniffio" -version = "1.3.1" -description = "Sniff out which async library your code is running under" -optional = false -python-versions = ">=3.7" -files = [ - {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, - {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, -] - -[[package]] -name = "soupsieve" -version = "2.8" -description = "A modern CSS selector implementation for Beautiful Soup." -optional = false -python-versions = ">=3.9" -files = [ - {file = "soupsieve-2.8-py3-none-any.whl", hash = "sha256:0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c"}, - {file = "soupsieve-2.8.tar.gz", hash = "sha256:e2dd4a40a628cb5f28f6d4b0db8800b8f581b65bb380b97de22ba5ca8d72572f"}, -] - -[[package]] -name = "stack-data" -version = "0.6.3" -description = "Extract data from python stack frames and tracebacks for informative displays" -optional = false -python-versions = "*" -files = [ - {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"}, - {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"}, -] - -[package.dependencies] -asttokens = ">=2.1.0" -executing = ">=1.2.0" -pure-eval = "*" - -[package.extras] -tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] - -[[package]] -name = "statsmodels" -version = "0.14.5" -description = "Statistical computations and models for Python" -optional = false -python-versions = ">=3.9" -files = [ - {file = "statsmodels-0.14.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9fc2b5cdc0c95cba894849651fec1fa1511d365e3eb72b0cc75caac44077cd48"}, - {file = "statsmodels-0.14.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b8d96b0bbaeabd3a557c35cc7249baa9cfbc6dd305c32a9f2cbdd7f46c037e7f"}, - {file = "statsmodels-0.14.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:145bc39b2cb201efb6c83cc3f2163c269e63b0d4809801853dec6f440bd3bc37"}, - {file = "statsmodels-0.14.5-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7c14fb2617bb819fb2532e1424e1da2b98a3419a80e95f33365a72d437d474e"}, - {file = "statsmodels-0.14.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1e9742d8a5ac38a3bfc4b7f4b0681903920f20cbbf466d72b1fd642033846108"}, - {file = "statsmodels-0.14.5-cp310-cp310-win_amd64.whl", hash = "sha256:1cab9e6fce97caf4239cdb2df375806937da5d0b7ba2699b13af33a07f438464"}, - {file = "statsmodels-0.14.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4b7091a8442076c708c926de3603653a160955e80a2b6d931475b7bb8ddc02e5"}, - {file = "statsmodels-0.14.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:128872be8f3208f4446d91ea9e4261823902fc7997fee7e1a983eb62fd3b7c6e"}, - {file = "statsmodels-0.14.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f2ad5aee04ae7196c429df2174df232c057e478c5fa63193d01c8ec9aae04d31"}, - {file = "statsmodels-0.14.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f402fc793458dd6d96e099acb44cd1de1428565bf7ef3030878a8daff091f08a"}, - {file = "statsmodels-0.14.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:26c028832730aebfbfd4e7501694e1f9ad31ec8536e776716673f4e7afd4059a"}, - {file = "statsmodels-0.14.5-cp311-cp311-win_amd64.whl", hash = "sha256:ec56f771d9529cdc17ed2fb2a950d100b6e83a7c5372aae8ac5bb065c474b856"}, - {file = "statsmodels-0.14.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:37e7364a39f9aa3b51d15a208c2868b90aadb8412f868530f5cba9197cb00eaa"}, - {file = "statsmodels-0.14.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4263d7f4d0f1d5ac6eb4db22e1ee34264a14d634b9332c975c9d9109b6b46e12"}, - {file = "statsmodels-0.14.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:86224f6e36f38486e471e75759d241fe2912d8bc25ab157d54ee074c6aedbf45"}, - {file = "statsmodels-0.14.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c3dd760a6fa80cd5e0371685c697bb9c2c0e6e1f394d975e596a1e6d0bbb9372"}, - {file = "statsmodels-0.14.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6264fb00e02f858b86bd01ef2dc05055a71d4a0cc7551b9976b07b0f0e6cf24f"}, - {file = "statsmodels-0.14.5-cp312-cp312-win_amd64.whl", hash = "sha256:b2ed065bfbaf8bb214c7201656df840457c2c8c65e1689e3eb09dc7440f9c61c"}, - {file = "statsmodels-0.14.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:906263134dd1a640e55ecb01fda4a9be7b9e08558dba9e4c4943a486fdb0c9c8"}, - {file = "statsmodels-0.14.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9118f76344f77cffbb3a9cbcff8682b325be5eed54a4b3253e09da77a74263d3"}, - {file = "statsmodels-0.14.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9dc4ee159070557c9a6c000625d85f653de437772fe7086857cff68f501afe45"}, - {file = "statsmodels-0.14.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a085d47c8ef5387279a991633883d0e700de2b0acc812d7032d165888627bef"}, - {file = "statsmodels-0.14.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9f866b2ebb2904b47c342d00def83c526ef2eb1df6a9a3c94ba5fe63d0005aec"}, - {file = "statsmodels-0.14.5-cp313-cp313-win_amd64.whl", hash = "sha256:2a06bca03b7a492f88c8106103ab75f1a5ced25de90103a89f3a287518017939"}, - {file = "statsmodels-0.14.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b23b8f646dd78ef5e8d775d879208f8dc0a73418b41c16acac37361ff9ab7738"}, - {file = "statsmodels-0.14.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4e5e26b21d2920905764fb0860957d08b5ba2fae4466ef41b1f7c53ecf9fc7fa"}, - {file = "statsmodels-0.14.5-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a060c7e0841c549c8ce2825fd6687e6757e305d9c11c9a73f6c5a0ce849bb69"}, - {file = "statsmodels-0.14.5-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:56da20def5350d676388213a330fd40ed15d0e8dd0bb1b92c0e4b0f2a65d3ad2"}, - {file = "statsmodels-0.14.5-cp39-cp39-win_amd64.whl", hash = "sha256:afb37ca1d70d99b5fd876e8574ea46372298ae0f0a8b17e4cf0a9afd2373ae62"}, - {file = "statsmodels-0.14.5.tar.gz", hash = "sha256:de260e58cccfd2ceddf835b55a357233d6ca853a1aa4f90f7553a52cc71c6ddf"}, -] - -[package.dependencies] -numpy = ">=1.22.3,<3" -packaging = ">=21.3" -pandas = ">=1.4,<2.1.0 || >2.1.0" -patsy = ">=0.5.6" -scipy = ">=1.8,<1.9.2 || >1.9.2" - -[package.extras] -build = ["cython (>=3.0.10)"] -develop = ["colorama", "cython (>=3.0.10)", "cython (>=3.0.10,<4)", "flake8", "isort", "jinja2", "joblib", "matplotlib (>=3)", "pytest (>=7.3.0,<8)", "pytest-cov", "pytest-randomly", "pytest-xdist", "pywinpty", "setuptools_scm[toml] (>=8.0,<9.0)"] -docs = ["ipykernel", "jupyter_client", "matplotlib", "nbconvert", "nbformat", "numpydoc", "pandas-datareader", "sphinx"] - -[[package]] -name = "terminado" -version = "0.18.1" -description = "Tornado websocket backend for the Xterm.js Javascript terminal emulator library." -optional = false -python-versions = ">=3.8" -files = [ - {file = "terminado-0.18.1-py3-none-any.whl", hash = "sha256:a4468e1b37bb318f8a86514f65814e1afc977cf29b3992a4500d9dd305dcceb0"}, - {file = "terminado-0.18.1.tar.gz", hash = "sha256:de09f2c4b85de4765f7714688fff57d3e75bad1f909b589fde880460c753fd2e"}, -] - -[package.dependencies] -ptyprocess = {version = "*", markers = "os_name != \"nt\""} -pywinpty = {version = ">=1.1.0", markers = "os_name == \"nt\""} -tornado = ">=6.1.0" - -[package.extras] -docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] -test = ["pre-commit", "pytest (>=7.0)", "pytest-timeout"] -typing = ["mypy (>=1.6,<2.0)", "traitlets (>=5.11.1)"] - -[[package]] -name = "threadpoolctl" -version = "3.6.0" -description = "threadpoolctl" -optional = false -python-versions = ">=3.9" -files = [ - {file = "threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb"}, - {file = "threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e"}, -] - -[[package]] -name = "tinycss2" -version = "1.4.0" -description = "A tiny CSS parser" -optional = false -python-versions = ">=3.8" -files = [ - {file = "tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289"}, - {file = "tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7"}, -] - -[package.dependencies] -webencodings = ">=0.4" - -[package.extras] -doc = ["sphinx", "sphinx_rtd_theme"] -test = ["pytest", "ruff"] - -[[package]] -name = "tornado" -version = "6.5.2" -description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." -optional = false -python-versions = ">=3.9" -files = [ - {file = "tornado-6.5.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:2436822940d37cde62771cff8774f4f00b3c8024fe482e16ca8387b8a2724db6"}, - {file = "tornado-6.5.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:583a52c7aa94ee046854ba81d9ebb6c81ec0fd30386d96f7640c96dad45a03ef"}, - {file = "tornado-6.5.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0fe179f28d597deab2842b86ed4060deec7388f1fd9c1b4a41adf8af058907e"}, - {file = "tornado-6.5.2-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b186e85d1e3536d69583d2298423744740986018e393d0321df7340e71898882"}, - {file = "tornado-6.5.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e792706668c87709709c18b353da1f7662317b563ff69f00bab83595940c7108"}, - {file = "tornado-6.5.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:06ceb1300fd70cb20e43b1ad8aaee0266e69e7ced38fa910ad2e03285009ce7c"}, - {file = "tornado-6.5.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:74db443e0f5251be86cbf37929f84d8c20c27a355dd452a5cfa2aada0d001ec4"}, - {file = "tornado-6.5.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b5e735ab2889d7ed33b32a459cac490eda71a1ba6857b0118de476ab6c366c04"}, - {file = "tornado-6.5.2-cp39-abi3-win32.whl", hash = "sha256:c6f29e94d9b37a95013bb669616352ddb82e3bfe8326fccee50583caebc8a5f0"}, - {file = "tornado-6.5.2-cp39-abi3-win_amd64.whl", hash = "sha256:e56a5af51cc30dd2cae649429af65ca2f6571da29504a07995175df14c18f35f"}, - {file = "tornado-6.5.2-cp39-abi3-win_arm64.whl", hash = "sha256:d6c33dc3672e3a1f3618eb63b7ef4683a7688e7b9e6e8f0d9aa5726360a004af"}, - {file = "tornado-6.5.2.tar.gz", hash = "sha256:ab53c8f9a0fa351e2c0741284e06c7a45da86afb544133201c5cc8578eb076a0"}, -] - -[[package]] -name = "traitlets" -version = "5.14.3" -description = "Traitlets Python configuration system" -optional = false -python-versions = ">=3.8" -files = [ - {file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"}, - {file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"}, -] - -[package.extras] -docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] -test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"] - -[[package]] -name = "types-python-dateutil" -version = "2.9.0.20251008" -description = "Typing stubs for python-dateutil" -optional = false -python-versions = ">=3.9" -files = [ - {file = "types_python_dateutil-2.9.0.20251008-py3-none-any.whl", hash = "sha256:b9a5232c8921cf7661b29c163ccc56055c418ab2c6eabe8f917cbcc73a4c4157"}, - {file = "types_python_dateutil-2.9.0.20251008.tar.gz", hash = "sha256:c3826289c170c93ebd8360c3485311187df740166dbab9dd3b792e69f2bc1f9c"}, -] - -[[package]] -name = "typing-extensions" -version = "4.15.0" -description = "Backported and Experimental Type Hints for Python 3.9+" -optional = false -python-versions = ">=3.9" -files = [ - {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, - {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, -] - -[[package]] -name = "tzdata" -version = "2025.2" -description = "Provider of IANA time zone data" -optional = false -python-versions = ">=2" -files = [ - {file = "tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8"}, - {file = "tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9"}, -] - -[[package]] -name = "uri-template" -version = "1.3.0" -description = "RFC 6570 URI Template Processor" -optional = false -python-versions = ">=3.7" -files = [ - {file = "uri-template-1.3.0.tar.gz", hash = "sha256:0e00f8eb65e18c7de20d595a14336e9f337ead580c70934141624b6d1ffdacc7"}, - {file = "uri_template-1.3.0-py3-none-any.whl", hash = "sha256:a44a133ea12d44a0c0f06d7d42a52d71282e77e2f937d8abd5655b8d56fc1363"}, -] - -[package.extras] -dev = ["flake8", "flake8-annotations", "flake8-bandit", "flake8-bugbear", "flake8-commas", "flake8-comprehensions", "flake8-continuation", "flake8-datetimez", "flake8-docstrings", "flake8-import-order", "flake8-literal", "flake8-modern-annotations", "flake8-noqa", "flake8-pyproject", "flake8-requirements", "flake8-typechecking-import", "flake8-use-fstring", "mypy", "pep8-naming", "types-PyYAML"] - -[[package]] -name = "urllib3" -version = "2.5.0" -description = "HTTP library with thread-safe connection pooling, file post, and more." -optional = false -python-versions = ">=3.9" -files = [ - {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, - {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, -] - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] -h2 = ["h2 (>=4,<5)"] -socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] - -[[package]] -name = "wcwidth" -version = "0.2.14" -description = "Measures the displayed width of unicode strings in a terminal" -optional = false -python-versions = ">=3.6" -files = [ - {file = "wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1"}, - {file = "wcwidth-0.2.14.tar.gz", hash = "sha256:4d478375d31bc5395a3c55c40ccdf3354688364cd61c4f6adacaa9215d0b3605"}, -] - -[[package]] -name = "webcolors" -version = "24.11.1" -description = "A library for working with the color formats defined by HTML and CSS." -optional = false -python-versions = ">=3.9" -files = [ - {file = "webcolors-24.11.1-py3-none-any.whl", hash = "sha256:515291393b4cdf0eb19c155749a096f779f7d909f7cceea072791cb9095b92e9"}, - {file = "webcolors-24.11.1.tar.gz", hash = "sha256:ecb3d768f32202af770477b8b65f318fa4f566c22948673a977b00d589dd80f6"}, -] - -[[package]] -name = "webencodings" -version = "0.5.1" -description = "Character encoding aliases for legacy web content" -optional = false -python-versions = "*" -files = [ - {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, - {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, -] - -[[package]] -name = "websocket-client" -version = "1.9.0" -description = "WebSocket client for Python with low level API options" -optional = false -python-versions = ">=3.9" -files = [ - {file = "websocket_client-1.9.0-py3-none-any.whl", hash = "sha256:af248a825037ef591efbf6ed20cc5faa03d3b47b9e5a2230a529eeee1c1fc3ef"}, - {file = "websocket_client-1.9.0.tar.gz", hash = "sha256:9e813624b6eb619999a97dc7958469217c3176312b3a16a4bd1bc7e08a46ec98"}, -] - -[package.extras] -docs = ["Sphinx (>=6.0)", "myst-parser (>=2.0.0)", "sphinx_rtd_theme (>=1.1.0)"] -optional = ["python-socks", "wsaccel"] -test = ["pytest", "websockets"] - -[[package]] -name = "widgetsnbextension" -version = "4.0.14" -description = "Jupyter interactive widgets for Jupyter Notebook" -optional = false -python-versions = ">=3.7" -files = [ - {file = "widgetsnbextension-4.0.14-py3-none-any.whl", hash = "sha256:4875a9eaf72fbf5079dc372a51a9f268fc38d46f767cbf85c43a36da5cb9b575"}, - {file = "widgetsnbextension-4.0.14.tar.gz", hash = "sha256:a3629b04e3edb893212df862038c7232f62973373869db5084aed739b437b5af"}, -] - -[metadata] -lock-version = "2.0" -python-versions = ">=3.11,<3.14" -content-hash = "0b0f10243c7edd4afae8d07f31aaeee639180c5f6a892894660486a702655036" diff --git a/book/pyproject.toml b/book/pyproject.toml deleted file mode 100644 index db8d036..0000000 --- a/book/pyproject.toml +++ /dev/null @@ -1,21 +0,0 @@ -[tool.poetry] -name = "tsbook" -version = "0.1.0" -description = "" -authors = ["felipeangelimvieira "] -license = "Apache License 2.0" -readme = "README.md" - -[tool.poetry.dependencies] -python = ">=3.11,<3.14" -sktime = "^0.39.0" -matplotlib = "^3.10.7" -seaborn = "^0.13.2" -jupyter = "^1.1.1" -statsmodels = "^0.14.5" -scikit-learn = "^1.7.2" - - -[build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" diff --git a/book/references.bib b/book/references.bib index 332c9f9..ce2ab93 100644 --- a/book/references.bib +++ b/book/references.bib @@ -3,4 +3,26 @@ @book{hyndman2018forecasting author = {Hyndman, Rob J and Athanasopoulos, George}, year = {2018}, publisher = {OTexts} +} + +@article{makridakis2022m5, + title = {M5 accuracy competition: Results, findings, and conclusions}, + author = {Makridakis, Spyros and Spiliotis, Evangelos and Assimakopoulos, Vassilios}, + journal = {International journal of forecasting}, + volume = {38}, + number = {4}, + pages = {1346--1364}, + year = {2022}, + publisher = {Elsevier} +} + +@article{montero2021principles, + title = {Principles and algorithms for forecasting groups of time series: Locality and globality}, + author = {Montero-Manso, Pablo and Hyndman, Rob J}, + journal = {International Journal of Forecasting}, + volume = {37}, + number = {4}, + pages = {1632--1653}, + year = {2021}, + publisher = {Elsevier} } \ No newline at end of file diff --git a/book/summary.qmd b/book/summary.qmd deleted file mode 100644 index b450ab7..0000000 --- a/book/summary.qmd +++ /dev/null @@ -1,3 +0,0 @@ -# Summary - -In summary, this book has no content whatsoever. diff --git a/convert_qmd_to_ipynb.sh b/convert_qmd_to_ipynb.sh new file mode 100755 index 0000000..64eef38 --- /dev/null +++ b/convert_qmd_to_ipynb.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash + +# Converts every .qmd file under book/content/pt/ into an equivalent .ipynb +# notebook, preserving the relative folder structure inside the notebooks/ +# directory. Also copies image assets referenced by the notebooks so inline +# media continues to work. Requires Quarto to be installed and available on +# the PATH. + +set -euo pipefail + +INPUT_ROOT="book/content/pt" +OUTPUT_ROOT="notebooks" + +if ! command -v quarto >/dev/null 2>&1; then + echo "Error: quarto command not found. Please install Quarto." >&2 + exit 1 +fi + +if [ ! -d "$INPUT_ROOT" ]; then + echo "Error: input directory '$INPUT_ROOT' does not exist." >&2 + exit 1 +fi + +find "$INPUT_ROOT" -type f -name '*.qmd' -print0 | +while IFS= read -r -d '' qmd_file; do + rel_path="${qmd_file#$INPUT_ROOT/}" + rel_dir="$(dirname "$rel_path")" + base_name="$(basename "$rel_path" .qmd)" + target_dir="$OUTPUT_ROOT/$rel_dir" + target_file="$target_dir/$base_name.ipynb" + + mkdir -p "$target_dir" + quarto convert "$qmd_file" --output "$target_file" + echo "Converted $qmd_file -> $target_file" +done + +# Copy supporting image assets; extend patterns below if needed. +find "$INPUT_ROOT" -type f \ + \( -iname '*.png' -o -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.gif' \ + -o -iname '*.svg' -o -iname '*.webp' -o -iname '*.bmp' \) -print0 | +while IFS= read -r -d '' asset_file; do + rel_path="${asset_file#$INPUT_ROOT/}" + target_path="$OUTPUT_ROOT/$rel_path" + target_dir="$(dirname "$target_path")" + + mkdir -p "$target_dir" + cp -p "$asset_file" "$target_path" + echo "Copied asset $asset_file -> $target_path" +done diff --git a/notebooks/extra/img/private_methods.png b/notebooks/extra/img/private_methods.png new file mode 100644 index 0000000..fa12ee2 Binary files /dev/null and b/notebooks/extra/img/private_methods.png differ diff --git a/notebooks/extra/sktime_custom.ipynb b/notebooks/extra/sktime_custom.ipynb new file mode 100644 index 0000000..23fad13 --- /dev/null +++ b/notebooks/extra/sktime_custom.ipynb @@ -0,0 +1,547 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Criando modelos customizados com sktime\n", + "\n", + "O sktime oferece um ecossistema robusto, mas em cenarios reais frequentemente precisamos ajustar comportamentos, incorporar dados hierarquicos ou adicionar pre-processamentos especificos. Felizmente, o sktime torna relativamente simples a criacao de modelos customizados, desde que sigamos algumas regras.\n", + "\n", + "Acredito que essa e uma das grandes vantagens da biblioteca: o foco em ser extensivel e customizavel.\n", + "\n", + "## O sistema de tags\n", + "\n", + "Entre a chamada dos metodos publicos e privados existe uma camada de validacoes e conversões controlada pelas tags. Elas sinalizam ao `BaseForecaster` e ao `BaseTransformer` o que precisa ser garantido antes de executar a implementacao customizada.\n", + "\n", + "As tags mais importantes para um forecaster podem ser definidas assim:\n", + "\n", + "```python\n", + "_tags = {\n", + "\t\"capability:exogenous\": True,\n", + "\t\"requires-fh-in-fit\": False,\n", + "\t\"X_inner_mtype\": [\n", + "\t\t\"pd.Series\",\n", + "\t\t\"pd.DataFrame\",\n", + "\t\t\"pd-multiindex\",\n", + "\t\t\"pd_multiindex_hier\",\n", + "\t],\n", + "\t\"y_inner_mtype\": [\n", + "\t\t\"pd.Series\",\n", + "\t\t\"pd.DataFrame\",\n", + "\t\t\"pd-multiindex\",\n", + "\t\t\"pd_multiindex_hier\",\n", + "\t]\n", + "}\n", + "```\n", + "\n", + "Cada uma delas indica o que o modelo é capaz de fazer nos seus métodos privados\n", + "`_fit` e `_predict`:\n", + "\n", + "* `capability:exogenous`: Indica se o modelo suporta variáveis exógenas (X) durante o ajuste e a previsão.\n", + "* `requires-fh-in-fit`: Indica se o modelo precisa do horizonte de previsão (fh) durante o ajuste. Alguns modelos precisam devido a sua implementação interna.\n", + "* `y_inner_mtype`: Define os tipos de dados aceitos para a variável dependente (y) durante o ajuste e a previsão.\n", + "* `X_inner_mtype`: Define os tipos de dados aceitos para as variáveis exógenas (X) durante o ajuste e a previsão.\n", + " \n", + "Os machine-types (**mtypes**), ou tipos para máquina, são a peça mais crucial nesse sistema.\n", + "\n", + "### Machine-types disponiveis\n", + "\n", + "Os `mtypes` definem qual a estrutura de dados que o modelo aceita como entrada e produz como saída. Os principais mtypes para séries temporais são:\n", + "\n", + "- `np.ndarray`\n", + "- `pd.Series`\n", + "- `pd.DataFrame`\n", + "- `pd-multiindex` (ideia de painel)\n", + "- `pd_multiindex_hier` (dados hierarquicos)\n", + "\n", + "Se o modelo suporta um `mtype` hierárquico e passamos um dado hierárquico, o \n", + "dado chegará normalmente ao método privado `_fit` ou `_predict`. Caso contrário, o sktime tentará converter o dado para um mtype suportado.\n", + "\n", + "#### Baixando exemplos por mtype\n", + "\n", + "Para entender melhor cada mtype, podemos baixar exemplos práticos usando a função `get_examples` do sktime:" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.datatypes import get_examples\n", + "\n", + "get_examples(mtype=\"np.ndarray\", as_scitype=\"Series\")[0]" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "get_examples(mtype=\"pd.DataFrame\", as_scitype=\"Series\")[0].head()" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "get_examples(mtype=\"pd-multiindex\", as_scitype=\"Panel\")[0].head()" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "get_examples(mtype=\"pd_multiindex_hier\", as_scitype=\"Hierarchical\")[0].head()" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Alguns mtypes tem limitações: uma `pd.Series` simples nao representa problemas hierarquicos, sendo necessario recorrer ao `pd_multiindex_hier`.\n", + "\n", + "#### Exemplo prático\n", + "\n", + "Vamos criar o nosso primeiro esqueleto de forecaster customizado. Para isso, baixamos uma série de exemplo:" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "# | echo: false\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"ignore\")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.forecasting.base import BaseForecaster\n", + "from sktime.utils._testing.series import _make_series\n", + "\n", + "y = _make_series(4)\n", + "y" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Nosso protótipo irá apenas printar os dados recebidos no método `_fit`. O `__init__` recebe um dicionário de tags para definir as capacidades do modelo." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "class Logger(BaseForecaster):\n", + "\n", + " _tags = {\n", + " \"requires-fh-in-fit\": False,\n", + " }\n", + "\n", + " def __init__(self, tags_to_set):\n", + " self.tags_to_set = tags_to_set\n", + " super().__init__()\n", + "\n", + " self.set_tags(**tags_to_set)\n", + " \n", + " def _fit(self, y, X=None, fh=None):\n", + " print(\"Inside fit:\")\n", + " print(y)\n", + " return self" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "logger = Logger(tags_to_set={\"y_inner_mtype\" : [\"pd.Series\"] })\n", + "logger.fit(y)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "logger = Logger(tags_to_set={\"y_inner_mtype\" : [\"np.ndarray\"] })\n", + "logger.fit(y)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "logger = Logger(tags_to_set={\"y_inner_mtype\" : [\"pd.DataFrame\"] })\n", + "logger.fit(y)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "try:\n", + " logger = Logger(tags_to_set={\"y_inner_mtype\" : [\"pd_multiindex_hier\"] })\n", + " logger.fit(y)\n", + "except ValueError as e:\n", + " print(e)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "try:\n", + " logger = Logger(tags_to_set={\"y_inner_mtype\": [\"pd.DataFrame\", \"pd_multiindex_hier\"]})\n", + " logger.fit(y)\n", + "except ValueError as e:\n", + " print(e)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Input hierárquico\n", + "\n", + "Agora veremos como o modelo se comporta com dados hierárquicos. Note que, nos casos onde o modelo não suporta dados hierárquicos, o sktime tentará convertê-los para um mtype suportado." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.utils._testing.hierarchical import _make_hierarchical\n", + "\n", + "y = _make_hierarchical((1,2), max_timepoints=4, min_timepoints=2)\n", + "y" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "logger = Logger(tags_to_set={\"y_inner_mtype\" : [\"pd.Series\"] })\n", + "logger.fit(y)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "logger = Logger(tags_to_set={\"y_inner_mtype\" : [\"np.ndarray\"] })\n", + "logger.fit(y)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "logger = Logger(tags_to_set={\"y_inner_mtype\" : [\"pd.DataFrame\"] })\n", + "logger.fit(y)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "try:\n", + " logger = Logger(tags_to_set={\"y_inner_mtype\": [\"pd_multiindex_hier\"]})\n", + " logger.fit(y)\n", + "except ValueError as e:\n", + " print(e)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Criando um modelo naive\n", + "\n", + "Agora, vamos implementar um modelo simples de previsão, o `CustomNaiveForecaster`, que prevê o valor médio dos últimos n pontos da série temporal.\n", + "\n", + "É um exemplo simples, mas que ilustra bem como criar um forecaster customizado com sktime." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from tsbook.datasets.retail import SyntheticRetail\n", + "\n", + "dataset = SyntheticRetail(\"panel\")\n", + "y_train, y_test = dataset.load(\"y_train\", \"y_test\")\n", + "y_train" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.utils.plotting import plot_series\n", + "\n", + "plot_series(\n", + " y_train.loc[0],\n", + " y_train.loc[24],\n", + " labels=[\n", + " \"SKU 0\",\n", + " \"SKU 24\",\n", + " ],\n", + ")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Abaixo, implementamos o `CustomNaiveForecaster` seguindo as regras do sktime (clique para expandir). Em seguida, explicamos passo a passo." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "# | code-fold: true\n", + "from sktime.forecasting.base import BaseForecaster\n", + "import pandas as pd\n", + "\n", + "\n", + "class CustomNaiveForecaster(BaseForecaster):\n", + " \"\"\"\n", + " A simple naive forecaster\n", + "\n", + " Parameters\n", + " ----------\n", + " n : int\n", + " Number of past values to use.\n", + " \"\"\"\n", + "\n", + " _tags = {\n", + " \"requires-fh-in-fit\": False,\n", + " \"y_inner_mtype\": [\n", + " \"pd.Series\",\n", + " ],\n", + " }\n", + "\n", + " # Add hyperparameters in init!\n", + " def __init__(self, n=1):\n", + " # 1. Set hyper-parameters\n", + " self.n = n\n", + "\n", + " # 2. Initialize parent class\n", + " super().__init__()\n", + "\n", + " # 3. Check hyper-parameters\n", + " assert self.n > 0, \"n must be greater than 0\"\n", + "\n", + " def _fit(self, y, X, fh):\n", + " \"\"\"\n", + " Fit necessary parameters.\n", + " \"\"\"\n", + "\n", + " self.value_ = y.iloc[-self.n :].mean()\n", + " return self\n", + "\n", + " def _predict(self, fh, X):\n", + " \"\"\"\n", + " Use forecasting horizon and optionally X to predict y\n", + " \"\"\"\n", + "\n", + " # During fit, BaseForecaster sets\n", + " # self.cutoff to the latest cutoff time point\n", + " index = fh.to_absolute_index(self.cutoff)\n", + " y_pred = pd.Series(\n", + " index=index,\n", + " data=[self.value_ for _ in range(len(index))],\n", + " )\n", + " y_pred.name = self._y.name\n", + "\n", + " return y_pred\n", + "\n", + " # Veremos mais tarde como usar esse método\n", + " @classmethod\n", + " def get_test_params(cls, parameter_set=\"default\"):\n", + " return [\n", + " {\"n\": 1},\n", + " {\"n\": 2},\n", + " ]" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Definindo o método `__init__`\n", + "\n", + "O método `__init__` possui 3 etapas:\n", + "\n", + "1. A definição dos hiperparametros e seus atributos com mesmo nome.\n", + "2. A chamada do `super().__init__()` para inicializar a classe pai.\n", + "3. A validação dos hiperparâmetros.\n", + " \n", + "\n", + "\n", + "```python\n", + "# Add hyperparameters in init!\n", + "def __init__(self, n=1):\n", + " # 1. Set hyper-parameters\n", + " self.n = n\n", + "\n", + " # 2. Initialize parent class\n", + " super().__init__()\n", + "\n", + " # 3. Check hyper-parameters\n", + " assert self.n > 0, \"n must be greater than 0\"\n", + "```\n", + "\n", + "No caso de algum preprocessamento dos hiperparâmetros no `__init__`, devemos guardar em uma variável com nome diferente do hiperparâmetro. Por exemplo, se tivéssemos interesse em ter um atributo `n` diferente do passado no `__init__`, poderíamos fazer:\n", + "\n", + "```\n", + "self._n = n + 1\n", + "```\n", + "\n", + "O `self.n` funciona como uma digital do modelo, e deve ser exatamente o que foi passado no `__init__`.\n", + "\n", + "### Definindo o método `_fit`\n", + "\n", + "No método `_fit`, devemos implementar a lógica de ajuste do modelo. No nosso caso, calculamos a média dos últimos `n` valores e armazenamos em `self.value_`.\n", + "\n", + "O `_` após o nome do atributo indica que é um atributo aprendido durante o ajuste, e será retornado quando chamarmos `get_fitted_params()`.\n", + "\n", + "Note que podemos supor que `y` é do tipo definido na tag `y_inner_mtype`, ou seja, uma `pd.Series`.\n", + "\n", + "### Definindo o método `_predict`\n", + "\n", + "No método `_predict`, implementamos a lógica de previsão. Usamos o horizonte de previsão `fh` para determinar os índices futuros e retornamos uma série com o valor previsto para cada ponto no horizonte.\n", + "\n", + "O `fh` é um objeto do tipo `ForecastingHorizon`, que possui o método `to_absolute_index(cutoff)` para converter o horizonte relativo em índices absolutos, considerando o último ponto conhecido (`self.cutoff`).\n", + "\n", + "Retornamos um `pd.Series` com os índices e os valores previstos.\n", + "\n", + "### Usando o `CustomNaiveForecaster`\n", + "\n", + "Agora, já podemos usar o nosso modelo customizado para fazer previsões." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "custom_naive_model = CustomNaiveForecaster()\n", + "custom_naive_model.fit(y_train)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Como passamos um dado hierárquico, o sktime converteu automaticamente para `pd.Series`, que é o mtype suportado pelo nosso modelo. Os modelos internos, para cada série, ficam disponíveis em `forecasters_`." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "custom_naive_model.forecasters_" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "y_pred = custom_naive_model.predict(fh=y_test.index.get_level_values(-1).unique())\n", + "\n", + "fig, _ = plot_series(\n", + " y_train.loc[0],\n", + " y_pred.loc[0],\n", + " labels=[\n", + " \"SKU 0\",\n", + " \"Previsão SKU 0\",\n", + " ],\n", + ")\n", + "fig.show()" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Testes unitários\n", + "\n", + "O sktime também fornece uma funcionalidade que traz testes unitários prontos para validar se o modelo customizado está funcionando corretamente.\n", + "\n", + "Ele usa os hiperparâmetros retornados pelo método `get_test_params` para criar instâncias do modelo e executar uma série de testes." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.utils.estimator_checks import check_estimator\n", + "\n", + "\n", + "check_estimator(CustomNaiveForecaster, tests_to_exclude=[\"test_doctest_examples\"])" + ], + "execution_count": null, + "outputs": [] + } + ], + "metadata": { + "kernelspec": { + "name": "python3", + "language": "python", + "display_name": "Python 3 (ipykernel)", + "path": "/Users/felipeangelim/Workspace/python_brasil_2025/.venv/share/jupyter/kernels/python3" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} \ No newline at end of file diff --git a/notebooks/part1/balls1.png b/notebooks/part1/balls1.png new file mode 100644 index 0000000..bb8bcab Binary files /dev/null and b/notebooks/part1/balls1.png differ diff --git a/notebooks/part1/components_and_diff.ipynb b/notebooks/part1/components_and_diff.ipynb new file mode 100644 index 0000000..f0fcd66 --- /dev/null +++ b/notebooks/part1/components_and_diff.ipynb @@ -0,0 +1,486 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Decomposição de séries temporais e modelos clássicos\n", + "\n", + "Nesse capítulo, vamos começar a usar um dataset mais realista, com dados simulando vendas diárias de uma empresa de varejo. \n", + "\n", + "Vamos aprender os seguintes pontos:\n", + "\n", + "* Definição de séries integradas, e uso de diferenciação\n", + "* Modelos estatísticos clássicos: modelos de suavização exponencial e modelos autoregressivos (AR)\n", + "* Como usar esses modelos com a biblioteca `sktime`\n", + "\n", + "\n", + "## Importando dados\n", + "\n", + "Para acessar os dados, vamos usar a classe `SyntheticRetail` da biblioteca `tsbook`, que contém dados simulados de vendas diárias de uma empresa de varejo." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from tsbook.datasets.retail import SyntheticRetail\n", + "from sktime.utils.plotting import plot_series\n", + "\n", + "dataset = SyntheticRetail(\"univariate\")\n", + "y_train, y_test = dataset.load(\"y_train\", \"y_test\")\n", + "\n", + "plot_series(y_train, y_test, labels=[\"Treino\", \"Teste\"])" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Os dados são diários, e vemos que sempre positivos. Também notamos que existe alguma sazonalidade, aparentemente algo mensal e anual, que aumenta de magnitude ao longo do tempo.\n", + "\n", + "Algo que deve chamar a atenção nesse gráfico é que a magnitude da série temporal está aumentando ao longo do tempo. Isso não deve passar desapercebido, pois é um ponto importante para entendermos o que vem a seguir.\n", + "\n", + "\n", + "## Auto-correlação\n", + "\n", + "É interessante entender o quanto de informação o passado de uma série temporal carrega sobre o seu futuro. Uma maneira de capturar essa relação (considerando variações lineares) é através da auto-correlação. A autocorrelação para um determinado lag $k$ é definida como:\n", + "\n", + "$$\n", + "\\text{Corr}(Y_t,Y_{t-k})=\\frac{\\text{Cov}(Y_t,Y_{t-k})}{\\sqrt{\\text{Var}(Y_t)\\text{Var}(Y_{t-k})}} = \\frac{E[(Y_t - \\mu)(Y_{t-k} - \\mu)]}{\\sqrt{\\text{Var}(Y_t)\\text{Var}(Y_{t-k})}}\n", + "$$\n", + "\n", + "Em outras palavras, quando o valor em $k$ observações atrás está acima (ou abaixo) da média, o valor atual também tende a estar acima (ou abaixo) da média?\n", + "\n", + "Com `plot_correlations`, podemos visualizar algumas informações úteis:\n", + "\n", + "1. No plot superior, vemos a série temporal original.\n", + "2. No canto inferior esquerdo, temos o gráfico de autocorrelação (ACF), que mostra a correlação entre a série temporal e suas versões defasadas (lags). Valores próximos de 1 ou -1 indicam uma forte correlação positiva ou negativa, respectivamente.\n", + "3. No canto inferior direito, temos o gráfico de autocorrelação parcial (PACF), que mostra a correlação entre a série temporal e suas versões defasadas, controlando para as correlações intermediárias. É útil para entender o efeito isolado de um lag." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.utils.plotting import plot_correlations\n", + "\n", + "\n", + "fig, ax = plot_correlations(y_train, lags=60)\n", + "fig.show()" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "No gráfico de autocorrelação, vemos algumas características interessantes:\n", + "\n", + "1. Valores são extremamente altos, e decaem lentamente ao longo do tempo.\n", + "2. Existem oscilações claras, indicando padrões sazonais na série temporal.\n", + "\n", + "::: {.callout-tip}\n", + "\n", + "\n", + "É um erro comum usar a correlação de lags de uma série para seleção de variáveis (lags). Sempre que possível, devemos eliminar a tendência antes de analisar a autocorrelação.\n", + "\n", + "::: \n", + "\n", + "Veremos que esses padrões são indicativos de que a série temporal possui componentes importantes, e que valores passados dizem muito sobre valores futuros.\n", + "\n", + "## Componentes de séries temporais\n", + "\n", + "Séries temporais podem ser decompostas em 3 componentes principais:\n", + "\n", + "* Tendência: padrão de longo prazo na série temporal\n", + "* Sazonalidade: padrões que se repetem em intervalos regulares, como diariamente, semanalmente ou anualmente\n", + "* Ruído: variação aleatória que não pode ser explicada pelos outros componentes\n", + "\n", + "Uma série aditiva pode ser representada como:\n", + "\n", + "$$\n", + "Y(t) = T(t) + S(t) + R(t)\n", + "$$\n", + "\n", + "onde $T(t)$ é a tendência, $S(t)$ é a sazonalidade, e $R(t)$ é o ruído. \n", + "\n", + "Em séries aditivas, o impacto da sazonalidade se dá em termos absolutos, dizemos: \"em janeiro, as vendas aumentam em 100 unidades com relação a média do ano\".\n", + "\n", + "Mas também existem séries multiplicativas, onde os componentes interagem de forma diferente:\n", + "\n", + "$$\n", + "Y(t) = T(t) \\cdot S(t) \\cdot R(t)\n", + "$$\n", + "\n", + "\n", + "Nessas séries, o impacto da sazonalidade se dá em termos relativos, dizemos: \"em janeiro, as vendas aumentam em 20% com relação a média do ano\". Esse é o caso mais comum para séries não-negativas, como vendas. Isso vem por definição: se temos vendas muito baixas, por exemplo, 10 unidades, não faz sentido dizer que em janeiro as vendas diminuem em 100 unidades, pois isso levaria a vendas negativas. Já dizer que as vendas diminuem em 20% é perfeitamente razoável.\n", + "\n", + "\n", + "::: {.callout-tip}\n", + "\n", + "Em alguns casos, as séries multiplicativas são definidas como:\n", + "\n", + "$$\n", + "Y(t) = T(t) + T(t) \\cdot S(t) + T(t) \\cdot R(t)\n", + "$$\n", + "\n", + ":::\n", + "\n", + "\n", + "Quando a série é multiplicativa, podemos fazer recurso ao logaritmo para transformá-la em aditiva:\n", + "\n", + "$$\n", + "log(Y(t)) = log(T(t)) + log(S(t)) + log(R(t))\n", + "$$\n", + "\n", + "Para fazer isso no sktime, usamos o transformador `LogTransformer`. Transformadores são usados para pré-processar ou pós-processar os dados antes de aplicar um modelo de previsão, e sua interface é similar a dos modelos de previsão:\n", + "\n", + "* `__init__`: define os hiperparâmetros do transformador\n", + "* `fit`: aprende os parâmetros do transformador a partir dos dados\n", + "* `transform`: aplica a transformação nos dados\n", + "* `inverse_transform` (opcional): aplica a transformação inversa nos dados\n" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.transformations.series.boxcox import LogTransformer\n", + "log_transformer = LogTransformer()\n", + "log_transformer.fit(y_train)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "y_train_log = log_transformer.transform(y_train)\n", + "plot_series(y_train_log, labels=[\"Logaritmo\"])" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Ainda que não esteja perfeito, essa transformação estabiliza as variações da série temporal, o que é importante para alguns modelos de previsão.\n", + "\n", + "\n", + "### Decompondo a série temporal\n", + "\n", + "\n", + "Sktime fornece algumas opções para decompor séries temporais. Aqui, vamos usar o `Detrender` para remover a tendência, e o `Deseasonalizer` para remover a sazonalidade.\n", + "\n", + "#### Tendência" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.transformations.series.detrend import Detrender, Deseasonalizer\n", + "\n", + "\n", + "detrender = LogTransformer() * Detrender(model=\"additive\")\n", + "detrender.fit(y_train)\n", + "y_train_detrended = detrender.transform(y_train)\n", + "plot_series(y_train_detrended, labels=[\"Detrended\"])" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Vemos uma mudança importante no gráfico de autocorrelação:" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "fig, _ = plot_correlations(y_train_detrended, lags=60)\n", + "fig.show()" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "O que indica que, ao eliminar a tendencia, a informação que o passado carrega sobre o futuro diminuiu bastante. Na verdade, a existência da tendência - um efeito de longo prazo - faz com que valores passados sejam altamente correlacionados com valores futuros, e pode dar a falsa impressão de que a série é \"fácil\" de modelar.\n", + "\n", + "\n", + "#### Sazonalidade\n", + "\n", + "Agora, usamos o `Deseasonalizer` para remover a sazonalidade:" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "deseasonalizer = LogTransformer() * Deseasonalizer(model=\"additive\", sp=365)\n", + "deseasonalizer.fit(y_train)\n", + "y_train_deseasonalized = deseasonalizer.transform(y_train)\n", + "plot_series(y_train_log, y_train_deseasonalized, labels=[\"Log with seasonality\", \"Deseasonalized\"])" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Podemos usar o `Detrender` e o `Deseasonalizer` juntos para remover ambos os componentes:" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "remove_components = LogTransformer() * Detrender(model=\"additive\") * Deseasonalizer(model=\"additive\", sp=365) \n", + "remove_components.fit(y_train)\n", + "y_train_removed = remove_components.transform(y_train)\n", + "plot_series(y_train_log, y_train_removed, labels=[\"Log with seasonality\", \"Deseasonalized and detrended\"])" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "fig, _ = plot_correlations(y_train_removed, lags=60)\n", + "fig.show()" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "::: {.callout-tip}\n", + "Tente adicionar mais um deseasonalizer para remover a sazonalidade semanal.\n", + ":::\n", + "\n", + "## Séries estacionárias\n", + "\n", + "O conceito de estacionariedade é fundamental em séries temporais. Uma série temporal é dita estacionária se suas propriedades estatísticas, como média, variância e autocovariância, são constantes ao longo do tempo, não importando a janela e quando ela é observada.\n", + "\n", + "\n", + "::: .{.callout-tip}\n", + "\n", + "Mais precisamente, se $Y(t)$, onde $t$ é o indice temporal, então dizemos que ela é estacionária se:\n", + "\n", + "$$\n", + "P(Y(t_{start}:t_{end})) = P(Y(t_{start}+k:t_{end}+k)), \\quad \\forall k, t_{start}, t_{end} \\in \\mathbb{Z}\n", + "$$\n", + "\n", + ":::\n", + "\n", + "Claramente, a série temporal que estamos analisando não é estacionária. Basta percebermos que para valores maiores de $t$, a média e a variância são maiores.\n", + "\n", + "O aumento da média da série ao longo do tempo é chamado de **tendência**. A tendência é um padrão de longo prazo na série temporal, e um grande desafio para previsões de longo prazo.\n", + "\n", + "Existem definições mais \"suaves\" de estacionariedade, como a estacionariedade fraca, que requer apenas que propriedades como média e autovariância sejam constantes ao longo do tempo. \n", + "\n", + "No fundo, o que nos interessa mais é ter uma série temporal que seja \"fácil\"de modelar. Para alguns algoritmos, como Naive, é importante que ela seja o mais próxima possível de estacionária. Veja abaixo o problema geraodo quando aplicamos o modelo Naive diretamente na série temporal original." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.forecasting.naive import NaiveForecaster\n", + "\n", + "naive = NaiveForecaster(strategy=\"mean\", window_length=24)\n", + "naive.fit(y_train)\n", + "y_pred = naive.predict(fh=y_test.index)\n", + "\n", + "plot_series(y_train, y_test, y_pred, labels=[\"Treino\", \"Teste\", \"Previsão Naive\"])" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Diferenciação\n", + "\n", + "Uma técnica simples e eficaz para lidar com séries não estacionárias é a diferenciação. Calculamos:\n", + "\n", + "$$\n", + "Y'(t) = Y(t) - Y(t-1)\n", + "$$\n", + "\n", + "e fazemos previsões em $Y'(t)$ ao invés de $Y(t)$. Para obter a previsão de $Y(t)$, precisamos fazer o processo inverso: somar a previsão de $Y'(t)$ com o valor anterior de $Y(t-1)$.\n", + "\n", + "$$\n", + "\\hat{Y(t)} = \\hat{Y'}(t) + \\hat{Y}(t-1), \\quad \\hat{Y}(0) \\text{ conhecido}\n", + "$$\n", + "\n", + "Com sktime, isso é extremamente fácil. Aqui, vamos usar um **transformador** chamado `Differencer`." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.transformations.series.difference import Differencer\n", + "diff = Differencer()\n", + "diff.fit(y_train)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "y_train_diff = diff.transform(y_train)\n", + "\n", + "plot_series(y_train, y_train_diff, labels=[\"Original\", \"Diferenciado\"])" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Criando um pipeline com diferenciação e Naive\n", + "\n", + "Agora, podemos criar um modelo de forecasting mais complexo, composto por dois passos:\n", + "\n", + "* Diferenciação dos dados\n", + "* Modelo Naive aplicado nos dados diferenciados\n", + "\n", + "Para isso, usamos a classe `TransformedTargetForecaster`, que cria um pipeline de transformadores e um modelo de previsão." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.forecasting.compose import TransformedTargetForecaster\n", + "\n", + "model = TransformedTargetForecaster(steps=[\n", + " (\"differencer\", Differencer()),\n", + " (\"naive\", NaiveForecaster(strategy=\"mean\", window_length=24))\n", + "])\n", + "model.fit(y_train)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Ou apenas:" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "model = Differencer() * NaiveForecaster(strategy=\"mean\", window_length=24)\n", + "model.fit(y_train)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "E agora podemos prever:" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "y_pred = model.predict(fh=y_test.index)\n", + "plot_series(y_train, y_test, y_pred, labels=[\"Treino\", \"Teste\", \"Previsão Naive com diferenciação\"])" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Existem ainda alguns problemas com a diferenciação. Note que a variância da série temporal diferenciada não é constante ao longo do tempo. Aqui, podemos combinar nossa transformação logarítmica com a diferenciação. \n", + "\n", + "Primeiro, vamos criar um transformador que combina as duas transformações:" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.transformations.compose import TransformerPipeline\n", + "\n", + "log_diff = TransformerPipeline(steps=[\n", + " (\"log\", LogTransformer()),\n", + " (\"diff\", Differencer())\n", + "])\n", + "log_diff.fit(y_train)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "y_train_log_diff = log_diff.transform(y_train)\n", + "plot_series(y_train_log_diff, labels=[\"Log + Diferenciado\"])" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Para fazer forecast, criamos um pipeline com o transformador combinado e o modelo Naive:" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "model = log_diff * NaiveForecaster(strategy=\"mean\", window_length=24)\n", + "model.fit(y_train)\n", + "y_pred = model.predict(fh=y_test.index)\n", + "plot_series(y_train, y_test, y_pred, labels=[\"Treino\", \"Teste\", \"Previsão Naive com log + diferenciação\"])" + ], + "execution_count": null, + "outputs": [] + } + ], + "metadata": { + "kernelspec": { + "name": "python3", + "language": "python", + "display_name": "Python 3 (ipykernel)", + "path": "/Users/felipeangelim/Workspace/python_brasil_2025/.venv/share/jupyter/kernels/python3" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} \ No newline at end of file diff --git a/notebooks/part1/ets_and_ar.ipynb b/notebooks/part1/ets_and_ar.ipynb new file mode 100644 index 0000000..4713a82 --- /dev/null +++ b/notebooks/part1/ets_and_ar.ipynb @@ -0,0 +1,284 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Modelos estatísticos clássicos e diferenciação\n", + "\n", + "## Exponential Smoothing\n", + "\n", + "O Modelo Naive, que calculada a média dos últimos valores, é um modelo muito simples, mas que pode ser melhorado. Analizando o que ele faz:\n", + "\n", + "$$\n", + "\\hat{Y}(t) = \\frac{Y(t-1) + Y(t-2) + \\dots + Y(t-n)}{n}\n", + "$$\n", + "\n", + "Ele basicamente atribui o mesmo peso para todas as observações passadas. Mas, intuitivamente, faz mais sentido dar mais peso para as observações mais recentes, e menos peso para as observações mais antigas.\n", + "\n", + "O exponential smoothing faz exatamente isso. Ele atribui pesos decrescentes para observações mais antigas. O peso da observação decresce exponencialmente segundo um fator $0 < \\alpha < 1$ [@hyndman2018forecasting]:\n", + "\n", + "$$\n", + "\\hat{Y}(t) = \\alpha Y(t-1) + \\alpha(1 - \\alpha)Y(t-1) + \\alpha(1 - \\alpha)^2 Y(t-2) + \\dots\n", + "$$\n" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "fig, ax = plt.subplots(1, 1, figsize=(6, 4))\n", + "alpha = 0.3\n", + "weights = [alpha * (1 - alpha) ** i for i in range(20)]\n", + "ax.bar(range(len(weights)), weights)\n", + "ax.set_title(\"Pesos do Exponential Smoothing\")\n", + "ax.set_xlabel(\"Observações passadas\")\n", + "ax.set_ylabel(\"Peso\")\n", + "plt.show()" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Usando Exponential Smoothing com sktime" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from tsbook.datasets.retail import SyntheticRetail\n", + "from sktime.utils.plotting import plot_series\n", + "\n", + "dataset = SyntheticRetail(\"univariate\")\n", + "y_train, y_test = dataset.load(\"y_train\", \"y_test\")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.utils.plotting import plot_series\n", + "from sktime.forecasting.exp_smoothing import ExponentialSmoothing\n", + "\n", + "model = ExponentialSmoothing()\n", + "model.fit(y_train)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "y_pred = model.predict(fh=y_test.index)\n", + "plot_series(y_train, y_test, y_pred, labels=[\"Treino\", \"Teste\", \"Previsão Exponential Smoothing\"])" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Existem versões alternativas que consideram sazonalidade e tendências. Veja a [documentação](https://www.sktime.org/en/stable/api_reference/auto_generated/sktime.forecasting.exp_smoothing.ExponentialSmoothing.html) para mais detalhes.\n" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "model = ExponentialSmoothing(trend=\"add\", seasonal=\"add\", sp=7)\n", + "model.fit(y_train)\n", + "y_pred = model.predict(fh=y_test.index)\n", + "plot_series(\n", + " y_train,\n", + " y_test,\n", + " y_pred,\n", + " labels=[\"Treino\", \"Teste\", \"Previsão Exponential Smoothing\"],\n", + ")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Modelos autoregressivos (AR)\n", + "\n", + "Modelos autoregressivos (AR) são modelos que prevem o valor atual de uma série temporal como uma combinação dos valores passados. O modelo AR(p) usa os últimos p valores para fazer a previsão:\n", + "\n", + "$$\n", + "\\hat{Y}(t) = \\phi_1 Y(t-1) + \\phi_2 Y(t-2) + \\dots + \\phi_p Y(t-p)\n", + "$$\n", + "\n", + "onde $\\phi_1, \\phi_2, \\dots, \\phi_p$ são os parâmetros do modelo que precisam ser estimados a partir dos dados." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.forecasting.auto_reg import AutoREG\n", + "from sktime.utils.plotting import plot_series\n", + "\n", + "model = AutoREG(lags=31)\n", + "model.fit(y_train)\n", + "\n", + "y_pred = model.predict(fh=y_test.index)\n", + "plot_series(\n", + " y_train,\n", + " y_test,\n", + " y_pred,\n", + " labels=[\"Treino\", \"Teste\", \"Previsão AR\"],\n", + ")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Um modelo auto-regressivo mais complexo é o ARIMA, que combina autoregressão (AR), média móvel (MA) e diferenciação integrada (I) para lidar com séries temporais não estacionárias. Não vamos estudar o ARIMA aqui pois envolve conceitos mais avançados, mas temos ele disponível no sktime, `sktime.forecasting.arima.ARIMA`.\n", + "\n", + "## STL: dividir e conquistar\n", + "\n", + "Sabemos que séries temporais podem ser decompostas em componentes de tendência, sazonalidade e resíduos. O modelo STL (Seasonal and Trend decomposition using Loess) é uma técnica que permite fazer essa decomposição de forma robusta.\n", + "\n", + "Temos no sktime o `STLTransformer`, que permite fazer a decomposição STL:" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.transformations.series.detrend import STLTransformer\n", + "\n", + "stl = STLTransformer(sp=365)\n", + "stl.fit(y_train)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "E agora podemos inspecionar os componentes:" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "fig, ax = plt.subplots(3, 1, figsize=(10, 8), sharex=True)\n", + "stl.trend_.plot.line(ax=ax[0])\n", + "ax[0].set_title(\"Tendência\")\n", + "stl.seasonal_.plot.line(ax=ax[1])\n", + "ax[1].set_title(\"Sazonalidade\")\n", + "stl.resid_.plot.line(ax=ax[2])\n", + "ax[2].set_title(\"Resíduos\")\n", + "fig.show()" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Uma possibilidade, uma vez que temos os diferentes componentes, é modelar cada componente separadamente e depois combinar as previsões. O sktime tem o `STLForecaster`, que faz exatamente isso:" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.forecasting.trend import STLForecaster\n", + "from sktime.forecasting.naive import NaiveForecaster\n", + "\n", + "model = STLForecaster(\n", + " forecaster_trend=AutoREG(lags=31),\n", + " forecaster_seasonal=NaiveForecaster(sp=7),\n", + " forecaster_resid=AutoREG(lags=31),\n", + " sp=7,\n", + ")\n", + "\n", + "model.fit(y_train)\n", + "y_pred = model.predict(fh=y_test.index)\n", + "\n", + "plot_series(\n", + " y_train,\n", + " y_test,\n", + " y_pred,\n", + " labels=[\"Treino\", \"Teste\", \"Previsão STL + AR\"],\n", + ")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Para fins de demonstração, podemos complicar um pouco mais o modelo, modelando os resíduos com outro `STLForecaster`:" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "model = STLForecaster(\n", + " forecaster_trend=AutoREG(lags=31),\n", + " forecaster_seasonal=NaiveForecaster(sp=7),\n", + " forecaster_resid=STLForecaster(\n", + " forecaster_trend=AutoREG(lags=31),\n", + " forecaster_seasonal=NaiveForecaster(sp=365),\n", + " forecaster_resid=AutoREG(lags=31),\n", + " sp=365,\n", + " ),\n", + " sp=7,\n", + ")\n", + "\n", + "model.fit(y_train)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "y_pred = model.predict(fh=y_test.index)\n", + "\n", + "plot_series(\n", + " y_train,\n", + " y_test,\n", + " y_pred,\n", + " labels=[\"Treino\", \"Teste\", \"Previsão STL + AR\"],\n", + ")" + ], + "execution_count": null, + "outputs": [] + } + ], + "metadata": { + "kernelspec": { + "name": "python3", + "language": "python", + "display_name": "Python 3 (ipykernel)", + "path": "/Users/felipeangelim/Workspace/python_brasil_2025/.venv/share/jupyter/kernels/python3" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} \ No newline at end of file diff --git a/notebooks/part1/index.ipynb b/notebooks/part1/index.ipynb new file mode 100644 index 0000000..fad77f3 --- /dev/null +++ b/notebooks/part1/index.ipynb @@ -0,0 +1,72 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Definição do problema\n", + "\n", + "Em modelos de regressão e classificação básicos, supomos que os dados são i.i.d: variaveis aleatórias independentes e identicamente distribuidas. Ou seja, a ordem que observamos os dados não tem nenhum impacto em nossa habilidade de prever quais serão as próximas observações.\n", + "\n", + "Vamos considerar um experimento simples: temos uma caixa com bolas pretas e vermelhas dentro. \n", + "Digamos que as bolas pretas valem 1 e as bolas vermelhas valem 0. Queremos descobrir qual a quantidade de pontos vamos ter na próxima rodada.\n", + "\n", + "![Bolas pretas e vermelhas](./balls1.png){width=300px}\n", + "\n", + "Se as bolas estão distribuidas aleatóriamente dentro da caixa, podemos esperar ver algo como a seguinte sequência de pontos:\n", + "\n", + "$$\n", + "\\{0, 1, 1, 0, 1, 0, 0, \\dots \\}\n", + "$$\n", + "\n", + "Nesse caso, a ordem dos pontos não importa. Não importa o que vimos até então; a próxima bola que vamos tirar da caixa é independente das bolas que já tiramos.\n", + "\n", + "Vamos supor um caso ligeiramente diferente. Agora, as bolas estão organizadas em blocos: primeiro todas as bolas pretas, depois todas as bolas vermelhas, depois todas as bolas pretas de novo, e assim por diante. Nesse caso, podemos esperar ver algo como:\n", + "\n", + "$$\n", + "\\{1, 1, 1, 0, 0, 0, 1, 1, 1, \\dots \\}\n", + "$$\n", + "\n", + "Nesse caso, a ordem dos pontos importa. Se vimos muitas bolas pretas recentemente, é mais provável que vejamos uma bola preta na próxima rodada. A próxima bola que vamos tirar da caixa depende das bolas que já tiramos. Esse é um caso básico de aplicação de **modelos de previsão de séries temporais**.\n", + "\n", + "Considerando que cada observação é indexada por uma variável temporal, podemos definir uma série temporal como uma sequência de observações ordenadas no tempo. Exemplos comuns de séries temporais incluem:\n", + "\n", + "* Varejo: vendas diárias, semanais ou mensais de um produto\n", + "* Finanças: preços diários de ações, taxas de câmbio\n", + "* Saúde: número diário de novos casos de uma doença\n", + "* Clima: temperatura diária, precipitação mensal\n", + "\n", + "Esse livro é uma introdução prática a modelos de séries temporais, com objetivo de dar contexto das ferramentas mais relevantes em 3h de leitura.\n", + "\n", + "O conteúdo está organizado nas seguintes seções:\n", + "\n", + "1. **Introdução a séries temporais**: \n", + " 1. Modelo Naive, Modelo Naive sazonal\n", + " 2. Séries integradas, diferenciação e estacionariedade\n", + " 3. Modelo de Suavização Exponencial (Exponential Smoothing)\n", + " 4. Modelos autoregressivos (AR)\n", + " 5. Métricas de avaliação de modelos de séries temporais\n", + " 6. Engenharia de features para séries temporais\n", + "2. **Modelos avançados e caso de uso**\n", + " 1. Forecast com modelos de Machine Learning\n", + " 2. Forecast com modelos fundacionais\n", + " 3. Previsão de vendas totais agregadas\n", + " 4. Previsão de vendas por região e modelos globais\n", + " 5. Previsão hierárquica e reconciliação\n", + "3. **Customização de modelos com sktime**\n", + " 1. Como customizar e criar modelos em sktime\n", + " 2. Criando um wrapper de biblioteca externa" + ] + } + ], + "metadata": { + "kernelspec": { + "name": "python3", + "language": "python", + "display_name": "Python 3 (ipykernel)", + "path": "/Users/felipeangelim/Workspace/python_brasil_2025/.venv/share/jupyter/kernels/python3" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} \ No newline at end of file diff --git a/notebooks/part1/metricas.ipynb b/notebooks/part1/metricas.ipynb new file mode 100644 index 0000000..118379f --- /dev/null +++ b/notebooks/part1/metricas.ipynb @@ -0,0 +1,302 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Métricas e cross-validation\n", + "\n", + "Até então, vimos como criar modelos de séries temporais e avaliamos qualitativamente seu desempenho, em apenas uma simples divisão treino-teste. Agora, vamos explorar como avaliar modelos de séries temporais de forma mais robusta, utilizando métricas específicas e técnicas de validação cruzada adaptadas para dados temporais.\n", + "\n", + "\n", + "\n", + "## Métricas\n", + "\n", + "Primeiro vamos baixar os dados e criar um modelo simples.\n" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "import matplotlib.pyplot as plt\n", + "from tsbook.datasets.retail import SyntheticRetail\n", + "from sktime.utils.plotting import plot_series\n", + "from sktime.forecasting.naive import NaiveForecaster\n", + "\n", + "dataset = SyntheticRetail(\"univariate\")\n", + "y_train, y_test = dataset.load(\"y_train\", \"y_test\")\n", + "\n", + "# Predict\n", + "model = NaiveForecaster(strategy=\"last\")\n", + "model.fit(y_train)\n", + "y_pred = model.predict(fh=y_test.index)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Para avaliar o desempenho de modelos de séries temporais, existem diversas métricas que podem ser utilizadas. \n", + "\n", + "Duas métricas básicas são:\n", + "\n", + "* **Mean Absolute Error (MAE)**: Média dos erros absolutos entre as previsões e os valores reais.\n", + "$$\n", + "MAE = \\frac{1}{h} \\sum_{t=T+1}^{T+h} |y_t - \\hat{y}_t|\n", + "$$" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.performance_metrics.forecasting import MeanAbsoluteError\n", + "\n", + "metric = MeanAbsoluteError()\n", + "metric(y_true=y_test, y_pred=y_pred)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "* **Mean Squared Error (MSE)**: Média dos erros quadráticos entre as previsões e os valores reais.\n", + "\n", + "$$\n", + "MSE = \\frac{1}{h} \\sum_{t=T+1}^{T+h} (y_t - \\hat{y}_t)^2\n", + "$$" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.performance_metrics.forecasting import MeanSquaredError\n", + "\n", + "metric = MeanSquaredError()\n", + "metric(y_true=y_test, y_pred=y_pred)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "No entanto, essas métricas não levam em consideração a escala dos dados, o que pode ser um problema ao comparar desempenho entre diferentes séries temporais, e também na comunicação dos resultados para stakeholders.\n", + "\n", + "Em muitos meios, é comum usar métricas que eliminam o fator escala, como:\n", + "\n", + "* **Mean Absolute Percentage Error (MAPE)**: Média dos erros percentuais absolutos entre as previsões e os valores reais.\n", + "\n", + "$$\n", + "MAPE = \\frac{1}{h} \\sum_{t=T+1}^{T+h} \\left| \\frac{y_t - \\hat{y}_t}{y_t} \\right|\n", + "$$\n" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.performance_metrics.forecasting import MeanAbsolutePercentageError\n", + "\n", + "metric = MeanAbsolutePercentageError()\n", + "metric(y_true=y_test, y_pred=y_pred)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "* **Symmetric Mean Absolute Percentage Error (sMAPE)**: Média dos erros percentuais absolutos simétricos entre as previsões e os valores reais." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "metric = MeanAbsolutePercentageError(symmetric=True)\n", + "metric(y_true=y_test, y_pred=y_pred)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "$$\n", + "sMAPE = \\frac{1}{h} \\sum_{t=T+1}^{T+h} \\frac{|y_t - \\hat{y}_t|}{(|y_t| + |\\hat{y}_t|)/2}\n", + "$$\n", + "\n", + "Essas métricas, no entanto, apresentam seus próprios problemas. Note que os valores\n", + "podem ser exageradamente alto quando o denominador é próximo de zero. Ou seja, o MAPE e sMAPE podem ser problemáticos quando a série temporal contém valores próximos de zero.\n", + "\n", + "Outro tema é que essas métricas não consideram a dificuldade da série. Por exemplo, 5% de erro pode ser muito em uma série, e pouco em outra. Para isso, foram propostas métricas \"escalas\":\n", + "\n", + "* **Mean Absolute Scaled Error (MASE)**: dividimos o erro absoluto pelo erro absoluto médio de um naive nos dados de treino. Se:\n", + "\n", + "$$\n", + "e_{naive} = \\frac{1}{T-1} \\sum_{t=2}^{T} |y_t - y_{t-1}|\n", + "$$\n", + "\n", + "são os erros nos dados de treino, então:\n", + "\n", + "$$\n", + "MASE = \\frac{1}{h} \\sum_{t=T+1}^{T+h} \\frac{|y_t - \\hat{y}_t|}{e_{naive}}\n", + "$$\n" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.performance_metrics.forecasting import MeanAbsoluteScaledError\n", + "\n", + "metric = MeanAbsoluteScaledError()\n", + "metric(y_true=y_test, y_pred=y_pred, y_train=y_train)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "* **Mean Squared Scaled Error (MSSE)**: dividimos o erro quadrático pelo quadrado de $e_{naive}$.\n", + "\n", + "$$\n", + "MSSE = \\frac{1}{h} \\sum_{t=T+1}^{T+h} \\frac{(y_t - \\hat{y}_t)^2}{e_{naive}^2}\n", + "$$" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.performance_metrics.forecasting import MeanSquaredScaledError\n", + "\n", + "metric = MeanSquaredScaledError()\n", + "metric(y_true=y_test, y_pred=y_pred, y_train=y_train)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Cross-validation para séries temporais\n", + "\n", + "Em um cross-validation, nossa intenção é estimar corretamente o erro de generalização do modelo, ou seja, o erro que o modelo terá em dados futuros.\n", + "\n", + "$$\n", + "\\mathbb{E}[L(Y, \\hat{Y})]\n", + "$$\n", + "\n", + "Onde a média é sobre a distribuição que mais representa o que será visto em produção.\n", + "\n", + "Tipicamente, fazemos back-testing com dois tipos de janelas:\n", + "\n", + "* **Sliding window**: a janela de treino tem tamanho fixo, e \"desliza\" ao longo do tempo. A cada passo, o modelo é re-treinado com os dados mais recentes.\n" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.utils.plotting import plot_windows\n", + "\n", + "from sktime.split import SlidingWindowSplitter\n", + "\n", + "sliding_window_cv = SlidingWindowSplitter(\n", + " window_length=365 * 3, step_length=100, fh=list(range(1, 90 + 1))\n", + ")\n", + "plot_windows(cv=sliding_window_cv, y=y_train)\n", + "plt.show()" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "* **Expanding window**: a janela de treino começa com um tamanho mínimo, e vai aumentando ao longo do tempo, incluindo todos os dados anteriores. A cada passo, o modelo é re-treinado com todos os dados disponíveis até aquele ponto.\n" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.split import ExpandingWindowSplitter\n", + "\n", + "\n", + "\n", + "expanding_window_cv = ExpandingWindowSplitter(\n", + " initial_window=365 * 3, step_length=100, fh=list(range(1, 90 + 1))\n", + ")\n", + "plot_windows(cv=expanding_window_cv, y=y_train)\n", + "plt.show()" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "::: .{callout-tip}\n", + "\n", + "Qual escolher? Bom, escolha o que mais representa o que você espera ver em produção.\n", + "\n", + ":::\n", + "\n", + "\n", + "## Executando o cross-validation\n", + "\n", + "Para executar cross-validation, usamos a função `evaluate`, que recebe o modelo, o esquema de cross-validation, os dados, e a métrica a ser usada." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.forecasting.model_evaluation import evaluate\n", + "from sktime.performance_metrics.forecasting import MeanAbsoluteScaledError\n", + "from sktime.forecasting.naive import NaiveForecaster\n", + "\n", + "model = NaiveForecaster(strategy=\"last\")\n", + "\n", + "evaluate(\n", + " forecaster=model,\n", + " cv=expanding_window_cv,\n", + " y=y_train,\n", + " X=None, # Veremos na próxima seçao!\n", + " scoring=MeanAbsoluteScaledError(),\n", + " error_score=\"raise\",\n", + " return_data=True,\n", + ")" + ], + "execution_count": null, + "outputs": [] + } + ], + "metadata": { + "kernelspec": { + "name": "python3", + "language": "python", + "display_name": "Python 3 (ipykernel)", + "path": "/Users/felipeangelim/Workspace/python_brasil_2025/.venv/share/jupyter/kernels/python3" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} \ No newline at end of file diff --git a/notebooks/part1/naive.ipynb b/notebooks/part1/naive.ipynb new file mode 100644 index 0000000..9e87f50 --- /dev/null +++ b/notebooks/part1/naive.ipynb @@ -0,0 +1,226 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Primeiro passos com sktime e modelo Naive\n", + "\n", + "Considerando o nosso exemplo da caixa com bolas pretas e vermelhas, um modelo simples para prever a próxima bola que vamos tirar da caixa é o **Modelo Naive**. \n", + "\n", + "Existem algumas versões de modelo naive:\n", + "\n", + "* **Naive (last)**: a previsão para o próximo ponto é igual ao último ponto observado. \n", + "* **Naive (mean)**: a previsão para o próximo ponto é igual à média dos pontos observados numa janela passada.\n", + "* **Naive Sazonal**: a previsão para o próximo ponto é igual ao ponto observado na mesma posição em um ciclo anterior. Por exemplo, se estamos prevendo vendas diárias e hoje é segunda-feira, a previsão para hoje é igual às vendas da última segunda-feira.\n", + " \n", + "\n", + "Esse modelo é simples, mas é extremamente eficaz e dificil de ser vencido em muitos casos.\n", + "No cenário onde os dados não vêm organizados em nenhuma ordem específica, a melhor aposta que podemos fazer é o valor médio das bolas no histórico recente.\n", + "\n", + "Vamos ver como fazer um forecast simples com sktime, usando o modelo Naive.\n", + "\n", + "## Exemplo prático com sktime\n", + "\n", + "Aqui, baixamos o dataset simples que vem na biblioteca desse repositório." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from tsbook.datasets.simple import SimpleDataset\n", + "\n", + "\n", + "dataset = SimpleDataset(True)\n", + "y = dataset.load(\"y\")\n", + "\n", + "y.head()" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Esse é um dataset simples com uma série temporal mensal. Vamos dividir os dados em treino e teste, usando os últimos 36 meses como teste. Para isso, devemos respeitar a ordem temporal dos dados.\n", + "\n", + "A função `temporal_train_test_split` faz isso para nós." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.forecasting.model_selection import temporal_train_test_split\n", + "y_train, y_test = temporal_train_test_split(y, test_size=36)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Também temos uma função de plotagem simples para visualizar séries temporais." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.utils.plotting import plot_series\n", + "\n", + "plot_series(y, labels=[\"Observações\"])" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Criando o modelo\n", + "\n", + "No sktime, os modelos são usados em 3 passos:\n", + "\n", + "* Inicialização (`__init__`): aqui, definimos os hiperparâmetros do modelo. Pense nessa parte como a configuração do modelo.\n", + "* Treinamento (`fit`): aqui, o modelo aprende com os dados de treino.\n", + "* Previsão (`predict`): com esse método, o modelo faz previsões para os dados futuros.\n", + "\n", + "Quando inicializamos o modelo em um notebook, o sktime mostra uma ilustração do modelo, o que é útil para entender o que está acontecendo \"por baixo dos panos\".\n", + "Em casos mais complexos com composição de modelos, isso pode ser útil para ilustrar o que estamos fazendo para outros cientistas." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.forecasting.naive import NaiveForecaster\n", + "\n", + "model = NaiveForecaster(strategy=\"last\")\n", + "model" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Ao treinar o modelo, passamos dados de treinamento." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "model.fit(y_train)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Para a previsão, temos que passar um argumento obrigatório: `fh`, abreviatura de \"forecasting horizon\" (horizonte de previsão). Tipicamente passamos um `fh` relativo, ou seja, no formato de uma lista " + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "model.predict(fh=[1,2,3,4])" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Onde cada número representa o número de períodos à frente que queremos prever. Também podemos passar o `fh` como um índice de tempo absoluto, que é o que faremos aqui. Vamos passar o índice de tempo do conjunto de teste." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "y_pred = model.predict(fh=y_test.index)\n", + "\n", + "plot_series(y_train, y_test, y_pred, labels=[\"Treino\", \"Teste\", \"Previsão\"])" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Ajustando hiperparâmetros\n", + "\n", + "Para alterar hiperparametros de um modelo já existente, podemos usar o método `set_params`, que modifica *in-place* os hiperparâmetros do modelo." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "model.set_params(\n", + " strategy=\"mean\",\n", + " window_length=12,\n", + ")\n", + "\n", + "model.fit(y_train)\n", + "y_pred = model.predict(fh=y_test.index)\n", + "\n", + "\n", + "plot_series(y_train, y_test, y_pred, labels=[\"Treino\", \"Teste\", \"Previsão\"])" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Podemos também testar o Naive sazonal, que repete a última observação de `6` períodos atrás." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "model.set_params(\n", + " sp=6,\n", + " strategy=\"last\"\n", + "\n", + ")\n", + "model.fit(y_train)\n", + "y_pred = model.predict(fh=y_test.index)\n", + "plot_series(y_train, y_test, y_pred, labels=[\"Treino\", \"Teste\", \"Previsão\"])" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Claro, como esse exemplo é muito simples, o modelo naive sazonal captura perfeitamente a sazonalidade dos dados. No entanto, vamos ver no próximo capítulo um caso de uso mais realista, com dados de varejo, e estudaremos modelos mais avançados." + ] + } + ], + "metadata": { + "kernelspec": { + "name": "python3", + "language": "python", + "display_name": "Python 3 (ipykernel)", + "path": "/Users/felipeangelim/Workspace/python_brasil_2025/.venv/share/jupyter/kernels/python3" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} \ No newline at end of file diff --git a/notebooks/part2/deep_learning.ipynb b/notebooks/part2/deep_learning.ipynb new file mode 100644 index 0000000..b0fb454 --- /dev/null +++ b/notebooks/part2/deep_learning.ipynb @@ -0,0 +1,239 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Modelos de deep learning e zero-shot forecasting\n", + "\n", + "Além de modelos de ML simples, também podemos usar modelos de deep learning para forecasting. Podemos com certeza usar uma rede neural simples como um regressor, assim como fizemos com os modelos de ML tradicionais. No entanto, existem alguns modelos com arquiteturas específicas para forecasting de séries temporais. Por exemplo, o N-BEATS é um modelo de deep learning que pode ser usado para forecasting.\n", + "\n", + "::: {.callout-tip}\n", + "\n", + "Importante notar que esse livro/tutorial tem o objetivo de ser curto e prático, então não entraremos em muitos detalhes sobre deep learning ou sobre todos métodos\n", + "existentes. \n", + "\n", + ":::\n" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "# | echo: false\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"ignore\")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "# | code-fold: true\n", + "import pandas as pd\n", + "import matplotlib.pyplot as plt\n", + "\n", + "from sktime.utils.plotting import plot_series" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "# | code-fold: true\n", + "\n", + "from tsbook.datasets.retail import SyntheticRetail\n", + "\n", + "dataset = SyntheticRetail(\"panel\")\n", + "y_train, X_train, y_test, X_test = dataset.load(\n", + " \"y_train\", \"X_train\", \"y_test\", \"X_test\"\n", + ")\n", + "\n", + "fh = y_test.index.get_level_values(-1).unique()" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## N-Beats\n", + " \n", + " N-BEATS é um modelo de séries temporais totalmente baseado em camadas densas (MLP)—nada de RNN/LSTM nem convolução. Ele pega uma janela do passado e entrega diretamente a previsão do futuro, aprendendo padrões como tendência e sazonalidade de forma pura, só com perceptrons.\n", + "\n", + "\n", + "O N-BEATS usa bases para construir sinais interpretáveis:\n", + "\n", + "* Base de tendência: combina funções polinomiais (captura subidas/descidas suaves).\n", + "* Base sazonal: combina senos/cossenos (captura repetições periódicas).\n", + "* Base genérica: aprende formas livres (sem pressupor forma analítica).\n", + "\n", + "Assim, internamente, ele determina os coeficientes das funções base para modelar a série temporal, baseado no histórico observado, fazendo uma espécie de meta-aprendizado interno.\n", + "\n", + "Os blocos são empilhados e executados de forma sucessiva. Pense numa fila de especialistas olhando a mesma janela do passado:\n", + "\n", + "* Cada um explica a sua parte do que viu (remove do passado o que já foi entendido = backcast), e propõe um pedaço da previsão (seu forecast).\n", + "* O que não foi explicado segue para o próximo especialista. No final, a previsão é a soma do que cada um sugeriu.\n", + "\n", + "![](img/nbeats_simplified.png)\n", + "\n", + "\n", + "### Pytorch Forecasting\n", + "\n", + "O Sktime nao é uma biblioteca especializada em deep learning, mas sim uma API\n", + "uniforme que provê acesso aos mais diversos algoritmos.\n", + "\n", + "Logo, também provemos acesso a bibliotecas especializadas em deep learning, como o\n", + "Pytorch Forecasting, que implementa o N-BEATS.\n", + "\n", + "Aqui, temos que definir os hiperparâmetros do modelo, como o número de blocos, o tamanho do contexto (janela de entrada), e o número de coeficientes para as funcões de base." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.forecasting.pytorchforecasting import PytorchForecastingNBeats\n", + "from pytorch_forecasting.data.encoders import EncoderNormalizer\n", + "\n", + "CONTEXT_LENGTH = 120\n", + "nbeats = PytorchForecastingNBeats(\n", + " train_to_dataloader_params={\"batch_size\": 256},\n", + " trainer_params={\"max_epochs\": 1},\n", + " model_params={\n", + " \"stack_types\": [\"trend\", \"seasonality\"], # One of the following values: “generic”, “seasonality” or “trend”.\n", + " \"num_blocks\" : [2,2], # The number of blocks per stack. \n", + " \"context_length\": CONTEXT_LENGTH, # lookback period\n", + " \"expansion_coefficient_lengths\" : [2, 5],\n", + " \"learning_rate\": 1e-3,\n", + " },\n", + " dataset_params={\n", + "\n", + " \"max_encoder_length\": CONTEXT_LENGTH,\n", + " \"target_normalizer\": EncoderNormalizer()\n", + " },\n", + ")\n", + "\n", + "nbeats.fit(y_train.astype(float), fh=fh)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "y_pred_nbeats = nbeats.predict(fh=fh, X=X_test)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.performance_metrics.forecasting import MeanSquaredScaledError\n", + "\n", + "metric = MeanSquaredScaledError(multilevel=\"uniform_average_time\")\n", + "metric(y_true=y_test, y_pred=y_pred_nbeats, y_train=y_train)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Agora, podemos visualizar o forecast para uma das séries. Vemos que, mesmo\n", + "com poucas épocas de treinamento ou tuning, o N-BEATS já consegue capturar a tendência." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "fig, ax = plt.subplots(figsize=(10, 4))\n", + "y_train.loc[10].plot(ax=ax, label=\"Train\")\n", + "y_test.loc[10].plot(ax=ax, label=\"Test\")\n", + "y_pred_nbeats.loc[10].plot(ax=ax, label=\"N-BEATS\")\n", + "fig.show()" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Zero-shot forecasting com N-BEATS\n", + "\n", + "Zero-shot forecasting se refere ao fato de fazer previsão para uma série jamais\n", + "vista pelo modeo, sem utilizar a série para treinar ou ajustar parâmetros dele.\n", + "\n", + "Aqui, para simular esse cenário, vamos criar uma nova série temporal combinando duas séries do conjunto de treino." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "new_y_train = (y_train.loc[0]**2 + y_train.loc[20]).astype(float)\n", + "new_y_test = (y_test.loc[0]**2 + y_test.loc[20]).astype(float)\n", + "\n", + "# Plotting the new series\n", + "fig, ax = plt.subplots(figsize=(10, 4))\n", + "new_y_train[\"sales\"].plot.line(ax=ax, label=\"New Train\")\n", + "new_y_test[\"sales\"].plot.line(ax=ax, label=\"New Test\")\n", + "fig.show()" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Na interface atual do sktime, usamos o argumento `y` do método `predict` para passar a nova série temporal para o modelo:" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "y_pred_zeroshot = nbeats.predict(fh=fh, y=new_y_train)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "fig, ax = plt.subplots(figsize=(10, 4))\n", + "new_y_train[\"sales\"].plot.line(ax=ax, label=\"New Train\")\n", + "new_y_test[\"sales\"].plot.line(ax=ax, label=\"New Test\")\n", + "y_pred_zeroshot[\"sales\"].plot.line(ax=ax, label=\"N-BEATS Zero-shot\")\n", + "plt.legend()\n", + "plt.show()" + ], + "execution_count": null, + "outputs": [] + } + ], + "metadata": { + "kernelspec": { + "name": "python3", + "language": "python", + "display_name": "Python 3 (ipykernel)", + "path": "/Users/felipeangelim/Workspace/python_brasil_2025/.venv/share/jupyter/kernels/python3" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} \ No newline at end of file diff --git a/notebooks/part2/exog_variables.ipynb b/notebooks/part2/exog_variables.ipynb new file mode 100644 index 0000000..b9a41b8 --- /dev/null +++ b/notebooks/part2/exog_variables.ipynb @@ -0,0 +1,359 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Variáveis Exógenas\n", + "\n", + "Como séries temporais possuem uma ordem temporal, a sua própria história tem poder preditivo muito forte. No entanto, em alguns casos, apenas a história não é suficiente para fazer previsões precisas.\n", + "\n", + "Por exemplo, em vendas de varejo, fatores como promoções, feriados e eventos sazonais podem impactar significativamente as vendas.\n", + "\n", + "Agora, vamos ver como usar variáveis exógenas em modelos de séries temporais com sktime, as famosas \"features\".\n", + "\n", + "A interface é sempre a mesma, vamos ver que a diferença é o uso do parâmetro `X` nos métodos `fit` e `predict`.\n", + "\n", + "Usaremos os mesmos dados de varejo sintético dos exemplos anteriores. Agora, teremos também `X_train` e `X_test`, que são as variáveis exógenas.\n" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from tsbook.datasets.retail import SyntheticRetail\n", + "from sktime.utils.plotting import plot_series\n", + "from sktime.forecasting.naive import NaiveForecaster\n", + "\n", + "dataset = SyntheticRetail(\"univariate\", macro_trend=True)\n", + "y_train, X_train, y_test, X_test = dataset.load(\n", + " \"y_train\", \"X_train\", \"y_test\", \"X_test\"\n", + ")\n", + "\n", + "X_train.head()" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "X_train.plot.line()" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "fig, ax = plt.subplots( figsize=(10, 6))\n", + "\n", + "y_train.plot(ax=ax, label=\"y\")\n", + "X_train[\"macro_trend\"].plot(ax=ax.twinx(), label=\"macro_trend\")\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Nem todos modelos suportam variáveis exógenas. Para ver uma lista de possibilidades, podemos usar a função `all_estimators` do sktime." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.registry import all_estimators\n", + "\n", + "all_estimators(\n", + " \"forecaster\", filter_tags={\"capability:exogenous\": True}, as_dataframe=True\n", + ")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Tipos de Variáveis Exógenas\n", + "\n", + "Antes de prosseguirmos, vamos separar as variáveis exógenas em dois tipos:\n", + "\n", + "* **Variáveis exógenas com valores futuros conhecidos**: São variáveis cujos valores futuros já são conhecidos no momento da previsão. Variáveis indicadoras (dummies) para feriados ou eventos especiais, bem como recursos de sazonalidade, são exemplos comuns desse tipo de variável.\n", + "\n", + "* **Variáveis exógenas com valores futuros desconhecidos**: São variáveis cujos valores futuros não são conhecidos para o horizonte de previsão. Por exemplo, se quisermos incluir indicadores econômicos que ainda não foram divulgados, devemos tratá-los como variáveis exógenas de valor futuro desconhecido.\n", + " \n", + "Nesse último cenário, para realizar a previsão, existem três opções:\n", + "\t1.\tPrever os valores futuros das variáveis exógenas usando um modelo separado (ou o mesmo modelo, se ele permitir) e usar essas previsões como entrada para o modelo principal de previsão.\n", + "\t2.\tUsar um valor de preenchimento (por exemplo, o último valor conhecido) para as variáveis exógenas de valor futuro desconhecido durante a previsão.\n", + "\t3.\tUsar valores defasados (lags) das variáveis exógenas como características (features), o que pode ser útil se o modelo conseguir aprender com os valores passados dessas variáveis.\n", + "\n", + "## Usando variáveis exógenas com sktime\n", + "\n", + "Vamos usar `AutoREG` como modelo base para nosso exemplo de vairáveis exógenas. Primeiramente, vamos supor que conhecemos a variável `macro_trend` no futuro" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.forecasting.auto_reg import AutoREG\n", + "\n", + "model = AutoREG(lags=30)\n", + "model.fit(y_train, X=X_train)\n", + "y_pred = model.predict(fh=y_test.index, X=X_test)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "plot_series(y_train, y_test, y_pred, labels=[\"Treino\", \"Teste\", \"Previsão com Exógenas\"])" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Fácil, no caso que conhecemos a variável exógena no futuro, basta passar `X` em `fit` e `predict`.\n", + "\n", + "Antes de avançar, é importante revisar que os transformadores com `fit` e `transform` também podem ser aplicados a variávei exógenas! Inclusive, podemos fazer pipelines compostos de várias etapas de preprocessamento de exógenas. Isso será importante para os próximos exemplos.\n", + "\n", + "## Variável observada, mas desconhecida no futuro\n", + "\n", + "Vamos eliminar a variável `macro_trend` do conjunto de teste, para simular o cenário onde não conhecemos o valor futuro dessa variável." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "import numpy as np\n", + "\n", + "X_test_missing = X_test.copy()\n", + "X_test_missing[\"macro_trend\"] = np.nan" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Solução 1: Prever a variável exógena\n", + "\n", + "Agora, vamos supor que não sabemos o valor futuro de `macro_trend`. Nesse caso, podemos criar um modelo separado para prever `macro_trend` e usar essa previsão como entrada para o modelo principal.\n", + "\n", + "Sktime possui uma funcionalidade pronta para isso: um forecaster chamado `ForecastX`. Ele é composto de dois modelos, um para a variável exógena, e outro para o alvo principal.\n", + "\n", + "Aqui, o forecaster necessita que o horizonte de previsão (`fh`) seja passado já na etapa de `fit`." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.forecasting.compose import ForecastX\n", + "\n", + "model = ForecastX(\n", + " forecaster_y=AutoREG(lags=30),\n", + " forecaster_X=AutoREG(lags=30),\n", + ")\n", + "\n", + "fh = [i for i in range(1, len(y_test) + 1)]\n", + "model.fit(y_train, X=X_train, fh=fh)\n", + "\n", + "y_pred_case1 = model.predict(X=X_test_missing)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "plot_series(y_train, y_test, y_pred_case1, labels=[\"Treino\", \"Teste\", \"Previsão com Exógenas Previstas\"])" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Solução 2: Usar valor de preenchimento (imputação)\n", + "\n", + "Para essa solução, vamos usar transformadores para preencher os valores faltantes na variável exógena. Aqui, usaremos o `Imputer` do sktime, que suporta variáveis exógenas." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.transformations.series.impute import Imputer\n", + "imputer = Imputer(method=\"mean\")\n", + "imputer.fit(X_train)\n", + "\n", + "# Agora imputamos\n", + "X_test_imputed = imputer.transform(X_test_missing)\n", + "X_test_imputed.tail()" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Para usar preprocessamento de exógenas + forecasting, podemos usar a composição `ForecastingPipeline`." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.forecasting.compose import ForecastingPipeline\n", + "from sktime.transformations.series.difference import Differencer\n", + "\n", + "\n", + "model = ForecastingPipeline(\n", + " steps=[(\"imputer\", Imputer(method=\"mean\")), (\"forecaster\", AutoREG(lags=30))]\n", + ")\n", + "model.fit(y_train, X=X_train)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "y_pred_case2 = model.predict(fh=y_test.index, X=X_test_missing)\n", + "plot_series(y_train, y_test, y_pred_case2, labels=[\"Treino\", \"Teste\", \"Previsão com Exógenas Imputadas\"])" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Nossa previsão não ficou boa. Claro! A variável exógena possui uma tendência - que naturalmente faz com que a imputação do ultimo valor ou a média não funcione bem. A solução ótima varia de caso para caso.\n", + "\n", + "\n", + "::: {.callout-tip}\n", + "\n", + "**Dica**: Podemos também usar o operador `**` como atalho para criar pipelines de variáveis exógenas.\n", + "\n", + "```python\n", + "model = Imputer(method=\"mean\") ** AutoREG(lags=30)\n", + "````\n", + "\n", + "Note a diferença do que aprendemos para criar pipelines com transformações na variável target, que usam `*` como operador. Claramente, podemos fazer composições mais complexas, como:\n", + "\n", + "```python\n", + "model = Imputer(method=\"mean\") ** (Differencer() * AutoREG())\n", + "```\n", + "\n", + ":::\n", + "\n", + "### Solução 3: Usar valores defasados (lags) da variável exógena\n", + "\n", + "Outra opção é criar versões defasadas das variáveis exógenas e usá-las como features.\n", + "\n", + "Para isso, podemos usar o transformador Lag do sktime.\n", + "Ao utilizar defasagens (lags), surgem dois desafios principais:\n", + "\t1.\tO aparecimento de valores NaN, que muitos modelos de previsão não conseguem tratar.\n", + "\t2.\tO número de variáveis exógenas pode aumentar significativamente, o que pode levar a overfitting ou, no caso do nosso conjunto de dados, a um número de features maior que o número de amostras — o que pode gerar erros no processo de ajuste (fitting).\n", + "\n", + "Para lidar com isso, no exemplo abaixo utilizamos um TransformerPipeline que realiza as seguintes etapas:\n", + "\t•\tSeleção de variáveis: executa uma seleção das variáveis exógenas, mantendo apenas as mais relevantes.\n", + "\t•\tDefasagem: aplica o transformador Lag para criar versões defasadas das variáveis exógenas.\n", + "\t•\tImputação: usa o transformador Imputer para preencher os valores NaN criados pelo processo de defasagem. Neste caso, é usado o método backfill (preenchimento a partir de valores posteriores)." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.transformations.compose import TransformerPipeline\n", + "from sktime.transformations.series.feature_selection import FeatureSelection\n", + "from sktime.transformations.series.impute import Imputer\n", + "from sktime.transformations.series.lag import Lag\n", + "from sktime.transformations.series.subset import IndexSubset\n", + "\n", + "\n", + "transformer_pipeline = TransformerPipeline(\n", + " steps=[\n", + " (\"lag\", Lag(lags=list(range(1, 180 + 1)))), # Cria lags 3 e 4\n", + " (\"subset\", IndexSubset()), # Seleciona apenas macro_trend\n", + " (\"impute\", Imputer(method=\"backfill\", value=0)), # Imputa valores NaN\n", + " (\"feature_selection\", FeatureSelection()), # Seleciona features\n", + " ]\n", + ")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "transformer_pipeline.fit(X=X_train, y=y_train)\n", + "X_test_transformed = transformer_pipeline.transform(X=X_test_missing)\n", + "\n", + "X_test_transformed.head()" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "model = ForecastingPipeline(\n", + " steps=[\n", + " (\"preprocessing\", transformer_pipeline),\n", + " (\"forecaster\", AutoREG(lags=30)),\n", + " ]\n", + ").fit(X=X_train, y=y_train)\n", + "\n", + "\n", + "model" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "y_pred_case3 = model.predict(fh=y_test.index, X=X_test_missing)\n", + "plot_series(y_train, y_test, y_pred_case3, labels=[\"Treino\", \"Teste\", \"Previsão com Exógenas Lag\"])" + ], + "execution_count": null, + "outputs": [] + } + ], + "metadata": { + "kernelspec": { + "name": "python3", + "language": "python", + "display_name": "Python 3 (ipykernel)", + "path": "/Users/felipeangelim/Workspace/python_brasil_2025/.venv/share/jupyter/kernels/python3" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} \ No newline at end of file diff --git a/notebooks/part2/hierarchical_forecasting.ipynb b/notebooks/part2/hierarchical_forecasting.ipynb new file mode 100644 index 0000000..3c87fb4 --- /dev/null +++ b/notebooks/part2/hierarchical_forecasting.ipynb @@ -0,0 +1,470 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Forecasting Hierárquico\n", + "\n", + "Muitas vezes, não apenas temos múltiplas séries temporais, mas essas séries também estão organizadas em uma hierarquia. Por exemplo, vendas de produtos podem ser organizadas por SKU, categoria, departamento e total da loja.\n", + "\n", + "Vamos usar o mesmo dataset sintético, mas agora com uma hierarquia de produtos.\n", + "\n", + "```{mermaid}\n", + "\n", + "graph TD\n", + " root[\"__total\"]\n", + "\n", + " %% group -1\n", + " root --> g_minus1[\"-1\"]\n", + " g_minus1 --> sku20[\"20\"]\n", + " g_minus1 --> sku21[\"21\"]\n", + " g_minus1 --> sku22[\"22\"]\n", + " g_minus1 --> sku23[\"23\"]\n", + " g_minus1 --> sku24[\"24\"]\n", + "\n", + " %% group 0\n", + " root --> g0[\"0\"]\n", + " g0 --> sku0[\"0\"]\n", + " g0 --> sku1[\"1\"]\n", + " g0 --> sku2[\"2\"]\n", + " g0 --> sku3[\"3\"]\n", + " g0 --> sku4[\"4\"]\n", + "\n", + " %% group 1\n", + " root --> g1[\"...\"]\n", + "\n", + " \n", + " %% group 3\n", + " root --> g3[\"3\"]\n", + " g3 --> sku15[\"15\"]\n", + " g3 --> sku16[\"16\"]\n", + " g3 --> sku17[\"17\"]\n", + " g3 --> sku18[\"18\"]\n", + " g3 --> sku19[\"19\"]\n", + "```\n", + "\n", + "\n", + "Ao mesmo tempo que dados hierarárquicos são interessantes pois nos trazem mais informação, eles também trazem desafios adicionais. Imagine que queremos prever as vendas futuras de cada produto. Se fizermos previsões independetes para cada produto, não há garantia que a soma das previsões dos produtos será igual à previsão do total da loja. Isso é chamado de incoerência nas previsões hierárquicas. O processo de ajustar as previsões para garantir coerência é chamado de **reconciliação**.\n", + "\n", + "## Carregando dados\n", + "\n", + "Vamos usar os dados sintéticos, agora com sua versao hierárquica." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "# | echo: false\n", + "\n", + "import warnings\n", + "import pandas as pd\n", + "import matplotlib.pyplot as plt\n", + "\n", + "warnings.filterwarnings(\"ignore\")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from tsbook.datasets.retail import SyntheticRetail\n", + "\n", + "dataset = SyntheticRetail(\"hierarchical\")\n", + "y_train, X_train, y_test, X_test = dataset.load(\"y_train\", \"X_train\", \"y_test\", \"X_test\")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Uso de pandas e dados hierárquicos\n", + "\n", + "Agora, os dataframes possuem mais de 2 ou mais índices, representando a hierarquia." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "y_train" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Para obter o número de pontos de série únicos (séries temporais individuais), podemos fazer o seguinte:" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "y_train.index.droplevel(-1).nunique()" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note que existem algumas séries com um identificador `__total`. Esse identificador representa o total para aquele nível da hierarquia. Por exemplo, se o id completo é `(-1, \"__total\")`, isso representa o total do grupo -1." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "y_train.loc[(-1, \"__total\")].head()" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "O total de todas as séries é representado por `(\"__total\", \"__total\")`." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "y_train.loc[(\"__total\", \"__total\")]" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Para contabilizar o número de séries temporais individuais, podemos fazer o seguinte:" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "y_train.index.droplevel(-1).nunique()" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Previsão sem reconciliação\n", + "\n", + "Vamos fazer uma previsão e entender o problema da incoerência." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "fh = y_test.index.get_level_values(-1).unique()" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from tsbook.forecasting.reduction import ReductionForecaster\n", + "from lightgbm import LGBMRegressor\n", + "\n", + "forecaster = ReductionForecaster(\n", + " LGBMRegressor(n_estimators=50, verbose=-1, objective=\"tweedie\"),\n", + " window_length=30,\n", + " normalization_strategy=\"divide_mean\",\n", + ")\n", + "forecaster.fit(y_train, X=X_train)\n", + "y_pred = forecaster.predict(fh, X=X_test)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Para somar as previsões de baixo para cima, podemos usar o transformador `Aggregator`. Vamos ver que,\n", + "quando somarmos as previsões das séries filhas, o resultado não é igual à previsão da série total." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.transformations.hierarchical.aggregate import Aggregator\n", + "\n", + "Aggregator().fit_transform(y_pred) - y_pred" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Existe uma diferença... ou seja, os valores não batem.\n", + "Imagine o impacto de levar previsões incoerentes para a tomada de decisão em uma empresa?\n", + "A raiz do problema é que temos mais modelos que graus de liberdade. Para ilustrar, suponha que temos 3 séries: $A$, $B$ e $C$, onde:\n", + "\n", + "$$\n", + "C(t) = A(t) + B(t)\n", + "$$\n", + "\n", + "Aqui, temos 3 séries, mas apenas 2 graus de liberdade, pois $C$ é completamente determinado por $A$ e $B$. Se fizermos previsões independentes para $A$, $B$ e $C$, não há garantia de que a relação acima será mantida nas previsões.\n", + "\n", + "## Reconciliação de previsões hierárquicas\n", + "\n", + "![](img/hierarchical_reconciled_vs_not.png)\n", + "\n", + "Existem diferentes métodos para reconciliar previsões em séries temporais hierárquicas. Não existe uma solução única, e o melhor método depende dos dados e do contexto.\n", + "\n", + "## Bottom-up\n", + "\n", + "A maneira mais simples de reconcialiar previsões hierárquicas é a abordagem **bottom-up**. Nessa abordagem, fazemos previsões apenas para as séries mais baixas na hierarquia (as séries filhas) e depois somamos essas previsões para obter as previsões das séries superiores (as séries pais).\n", + "\n", + "\"Hierarchical\n", + "\n", + "Lados positivos:\n", + "\n", + "* Simplicidade: fácil de entender e implementar.\n", + "* Coerência garantida: a soma das previsões das séries filhas sempre será igual à previsão da série pai.\n", + "* Sérias filhas podem capturar detalhes específicos que podem ser perdidos em níveis superiores.\n", + "\n", + "No entanto, essa abordagem também tem desvantagens: é sucetível ao ruído nas séries filhas, e se as séries filhas tiverem pouca informação, as previsões podem ser ruins. Por exemplo, muitos zeros nas séries de níveis baixos pode levar a previsões ruins a niveis agregados." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.transformations.hierarchical.reconcile import BottomUpReconciler\n", + "\n", + "bottom_up = BottomUpReconciler() * forecaster\n", + "bottom_up.fit(y_train)\n", + "\n", + "y_pred_bottomup = bottom_up.predict(fh=fh)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Agora vemos que as previsões são coerentes:" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "Aggregator().fit_transform(y_pred_bottomup) - y_pred_bottomup" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Top-down (forecast proportions)\n", + "\n", + "Outra abordagem é a **top-down**. Nessa abordagem, fazemos previsões apenas para as séries superiores na hierarquia (as séries pais) e depois distribuímos essas previsões para as séries filhas com base em proporções previstas.\n", + "\n", + "Suponha que temos a seguinte hierarquia $C(t) = A(t) + B(t)$. Considere $\\hat{C}(t)$, $\\hat{A}(t)$ e $\\hat{B}(t)$ como as previsões para $C$, $A$ e $B$, respectivamente. Na abordagem top-down, faríamos o seguinte:\n", + "\n", + "1. Prever $\\hat{C}(t)$, $\\hat{A}(t)$ e $\\hat{B}(t)$ independentemente.\n", + "2. Calcular as proporções previstas para os níveis mais baixos:\n", + "$$\n", + "p_A(t) = \\frac{\\hat{A}(t)}{\\hat{A}(t) + \\hat{B}(t)}\n", + "$$\n", + "\n", + "$$\n", + "p_B(t) = \\frac{\\hat{B}(t)}{\\hat{A}(t) + \\hat{B}(t)}\n", + "$$\n", + "\n", + "3. Distribuir a previsão de $C$ para $A$ e $B$ usando essas proporções:\n", + "$$\n", + "\\tilde{A}(t) = p_A(t) \\cdot \\hat{C}(t)\n", + "$$\n", + "\n", + "$$\n", + "\\tilde{B}(t) = p_B(t) \\cdot \\hat{C}(t)\n", + "$$\n", + "\n", + "Essa abordagem é capaz de usufruir da qualidade do forecast total, e ainda consegue distribuir para as séries filhas baseadas no histórico.\n", + "\n", + "\"Topdown\n", + "\n", + "\n", + "O que chamam de \"Proporções históricas\" é equivalente a esse método, mas com um modelo Naive para prever as proporções.\n", + "\n", + "Esse método pode ser bom quando o forecast total é de boa qualidade. No entanto,\n", + "dependemos profundamente da qualidade do forecast total e das proporções." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.transformations.hierarchical.reconcile import TopdownReconciler\n", + "\n", + "top_down_fcst = TopdownReconciler() * forecaster\n", + "top_down_fcst.fit(y_train)\n", + "\n", + "y_pred_topdown = top_down_fcst.predict(fh=fh)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Reconciliação ótima\n", + "\n", + "Existe uma abordagem mais sofisticada, com uma intuição geométrica interessante.\n", + "A ideia é ajustar as previsões iniciais para que elas satisfaçam as restrições de soma da hierarquia. Por exemplo, para a hierarquia $C(t) = A(t) + B(t)$, queremos garantir que:\n", + "\n", + "$$\n", + "\\hat{C}(t) = \\hat{A}(t) + \\hat{B}(t)\n", + "$$\n", + "\n", + "Se consideramos nosso espaço 3D de observações $(\\hat{A}, \\hat{B}, \\hat{C})$, a \n", + "condição acima é satisfeita para um plano 2D nesse universo.\n", + "\n", + "\n", + "![](img/coherent_plane.png)\n", + "\n", + "\n", + "Podemos então projetar nossas previsões iniciais nesse plano para obter previsões coerentes. Essa projeção pode ser feita de várias maneiras, levando a diferentes métodos de reconciliação ótima. Os métodos levam o nome \"OLS\" pois a projeção é feita minimizando o erro quadrático (Ordinary Least Squares).\n", + "\n", + "* **OLS** : projetar ortogonalmente todas as previsões base na espaço de reconciliação, tratando todas as séries igualmente.\n", + "* **Weighted OLS**: projetar obliquamente, ou seja, considerando pesos diferentes para cada série, permitindo dar mais importância a certas séries na reconciliação. A projeção não faz mais uma perpendicular, mas sim uma oblíqua.\n", + "* **Minimum trace (MinT)**: use a matriz de covariância do erro para encontrar as previsões reconciliadas ótimas. Chamado de \"ótimo\".\n", + "\n", + "\n", + "Para a reconciliação ótima com OLS, podemos usar o `OptimalReconciler` do sktime:" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.transformations.hierarchical.reconcile import OptimalReconciler\n", + "\n", + "optimal = OptimalReconciler(\"ols\") * forecaster\n", + "optimal.fit(y_train)\n", + "y_pred_optimal = optimal.predict(fh=fh)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "# | code-fold: true\n", + "from sktime.utils.plotting import plot_series\n", + "import matplotlib.pyplot as plt\n", + "import pandas as pd\n", + "\n", + "\n", + "idx = y_train.index.droplevel(-1).unique()[10]\n", + "\n", + "plot_series(\n", + " y_train.loc[idx,],\n", + " y_test.loc[idx,],\n", + " y_pred.loc[idx,],\n", + " y_pred_optimal.loc[idx,],\n", + " labels=[\"Train\", \"Test\", \"Predicted (sem reconciliação)\", \"Predicted (ótimo)\"],\n", + ")\n", + "plt.xlim(pd.to_datetime(\"2024-05-01\"), None)\n", + "plt.show()" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Para reconciliações ótimas (que usam a covariância do erro), podemos usar o `ReconcilerForecaster` do sktime, que internamente já faz o cálculo da covariância do erro:" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.forecasting.reconcile import ReconcilerForecaster\n", + "\n", + "\n", + "mint_forecaster = ReconcilerForecaster(\n", + " forecaster=forecaster,\n", + " method=\"mint_shrink\")\n", + "\n", + "mint_forecaster.fit(y_train)\n", + "y_pred_mint = mint_forecaster.predict(fh=fh)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Comparando resultados" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.performance_metrics.forecasting import MeanSquaredScaledError\n", + "\n", + "metric = MeanSquaredScaledError(multilevel=\"uniform_average_time\")\n", + "\n", + "pd.DataFrame(\n", + " {\n", + " \"Baseline\": metric(y_test, y_pred, y_train=y_train),\n", + " \"BottomUpReconciler\": metric(y_test, y_pred_bottomup, y_train=y_train),\n", + " \"TopDownReconciler\": metric(y_test, y_pred_topdown, y_train=y_train),\n", + " \"OptimalReconciler (ols)\": metric(y_test, y_pred_optimal, y_train=y_train),\n", + " \"Mint Reconciler\": metric(y_test, y_pred_mint, y_train=y_train),\n", + " },\n", + " index=[\"Mean Absolute Scaled Error\"],\n", + ")" + ], + "execution_count": null, + "outputs": [] + } + ], + "metadata": { + "kernelspec": { + "name": "python3", + "language": "python", + "display_name": "Python 3 (ipykernel)", + "path": "/Users/felipeangelim/Workspace/python_brasil_2025/.venv/share/jupyter/kernels/python3" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} \ No newline at end of file diff --git a/notebooks/part2/img/coherent_plane.png b/notebooks/part2/img/coherent_plane.png new file mode 100644 index 0000000..836d394 Binary files /dev/null and b/notebooks/part2/img/coherent_plane.png differ diff --git a/notebooks/part2/img/global_reduction.png b/notebooks/part2/img/global_reduction.png new file mode 100644 index 0000000..7fbe4c6 Binary files /dev/null and b/notebooks/part2/img/global_reduction.png differ diff --git a/notebooks/part2/img/hierarchical_bottomup.png b/notebooks/part2/img/hierarchical_bottomup.png new file mode 100644 index 0000000..472dc40 Binary files /dev/null and b/notebooks/part2/img/hierarchical_bottomup.png differ diff --git a/notebooks/part2/img/hierarchical_reconciled_vs_not.png b/notebooks/part2/img/hierarchical_reconciled_vs_not.png new file mode 100644 index 0000000..4723676 Binary files /dev/null and b/notebooks/part2/img/hierarchical_reconciled_vs_not.png differ diff --git a/notebooks/part2/img/hierarchical_td_fcst.png b/notebooks/part2/img/hierarchical_td_fcst.png new file mode 100644 index 0000000..60f44fb Binary files /dev/null and b/notebooks/part2/img/hierarchical_td_fcst.png differ diff --git a/notebooks/part2/img/hierarchical_topdown.png b/notebooks/part2/img/hierarchical_topdown.png new file mode 100644 index 0000000..bb0348d Binary files /dev/null and b/notebooks/part2/img/hierarchical_topdown.png differ diff --git a/notebooks/part2/img/nbeats_simplified.png b/notebooks/part2/img/nbeats_simplified.png new file mode 100644 index 0000000..aeac0a4 Binary files /dev/null and b/notebooks/part2/img/nbeats_simplified.png differ diff --git a/notebooks/part2/img/reduction.png b/notebooks/part2/img/reduction.png new file mode 100644 index 0000000..d672f20 Binary files /dev/null and b/notebooks/part2/img/reduction.png differ diff --git a/notebooks/part2/ml_models.ipynb b/notebooks/part2/ml_models.ipynb new file mode 100644 index 0000000..611f822 --- /dev/null +++ b/notebooks/part2/ml_models.ipynb @@ -0,0 +1,374 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Modelos de Machine Learning\n", + "\n", + "Nesse capítulo vamos ver as maneiras de usar modelos de Machine Learning para\n", + "forecasting. Aqui é onde mais acontecem erros de novos praticantes, pois\n", + "muitas vezes tentam aplicar modelos de ML diretamente na série temporal.\n", + "\n", + "\n", + "Para usar um modelo de ML, precisamos transformar a série temporal em um problema de regressão tradicional. Isso é feito criando janelas deslizantes (sliding windows) da série temporal, onde cada janela é usada como uma amostra de treinamento para o modelo de ML.\n", + "\n", + "Ou seja, se temos uma série temporal $(y_t)$, podemos criar janelas de tamanho $n$ e usar os valores $y_{t-n}, y_{t-n+1}, \\ldots, y_{t-1}$ como características (features) para prever o valor $y_t$.\n", + "\n", + "![img/reduction.png](img/reduction.png)\n", + "\n", + "\n", + "Para prever mais de um passo à frente, existem duas abordagens:\n", + "\n", + "1. **Previsão recursive**: se queremos prever $h$ passos à frente, podemos usar o modelo para prever $y_{t+1}$, depois usar essa previsão para prever $y_{t+2}$, e assim por diante, até $y_{t+h}$. Isso pode levar a erros acumulados, pois cada previsão depende das previsões anteriores.\n", + "2. **Previsão direta**: em vez de prever um passo de cada vez, podemos treinar o modelo para prever todos os $h$ passos à frente de uma vez. Isso pode ser feito usando um modelo para cada $h$ ou usando um modelo que prevê um vetor de $h$ valores.\n", + "\n", + "A verdade é que as duas abordagens podem ser vistas como uma: a previsão recursiva pode ser vista como uma previsão direta para $h=1$." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "# | echo: false\n", + "\n", + "import warnings\n", + "warnings.filterwarnings(\"ignore\")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "# | code-fold: true\n", + "from tsbook.datasets.retail import SyntheticRetail\n", + "from sktime.utils.plotting import plot_series\n", + "from sktime.forecasting.naive import NaiveForecaster\n", + "\n", + "dataset = SyntheticRetail(\"univariate\")\n", + "y_train, X_train, y_test, X_test = dataset.load(\n", + " \"y_train\", \"X_train\", \"y_test\", \"X_test\"\n", + ")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## O problema da tendência\n", + "\n", + "A tendência em séries temporais é como um constante problema de data drift:" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "# | code-fold: true\n", + "_X = [y_train.iloc[i : i + 7] for i in range(0, 700)]\n", + "\n", + "_X_test = [y_train.iloc[i : i + 7] for i in range(700, 800)]\n", + "\n", + "\n", + "def set_index(x):\n", + " x.index = range(len(x))\n", + " return x\n", + "\n", + "\n", + "_X = [set_index(x) for x in _X]\n", + "_X_test = [set_index(x) for x in _X_test]\n", + "\n", + "import matplotlib.pyplot as plt\n", + "\n", + "fig, ax = plt.subplots()\n", + "for x in _X:\n", + " ax.plot(x, color=\"gray\", alpha=0.3)\n", + "for x in _X_test:\n", + " ax.plot(x, color=\"red\", alpha=0.3)\n", + "\n", + "# Add legend, with 1 red line for test and 1 gray for train\n", + "from matplotlib.lines import Line2D\n", + "\n", + "legend_handles = [\n", + " Line2D([0], [0], color=\"gray\", alpha=0.3, lw=2, label=\"Treino\"),\n", + " Line2D([0], [0], color=\"red\", alpha=0.3, lw=2, label=\"Teste\"),\n", + "]\n", + "ax.legend(handles=legend_handles, loc=\"best\")\n", + "ax.set_title(\"Série original - Magnitudes diferentes para cada janela\")\n", + "fig.show()" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Quando criamos nossas janelas e olhamos treine e teste, esse problema fica claro. A informação de uma série em treino não é util para prever a série de teste, pois elas estão em magnitudes diferentes.\n", + "\n", + "Uma possível solução para isso é normalizar cada janela, dividindo pelo valor médio da janela. Assim, todas as janelas ficam na mesma escala:" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "# | code-fold: true\n", + "\n", + "_X = [x / x.mean() for x in _X]\n", + "_X_test = [x / x.mean() for x in _X_test]\n", + "\n", + "fig, ax = plt.subplots()\n", + "for x in _X:\n", + " ax.plot(x, color=\"gray\", alpha=0.3)\n", + "for x in _X_test:\n", + " ax.plot(x, color=\"red\", alpha=0.3)\n", + "\n", + "ax.legend(handles=legend_handles, loc=\"best\")\n", + "ax.set_title(\"Série normalizada\")\n", + "fig.show()" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "e podemos prever sem problemas.\n", + "\n", + "Outra possibilidade é a diferenciação, como já vimos em capítulos anteriores. A diferenciação remove a tendência da série, tornando-a estacionária.\n", + "\n", + "## Usando modelos de ML com sktime\n", + "\n", + "Primeiro, vamos import `ReductionForecaster`, que é a classe que implementa a abordagem de janelas deslizantes para usar modelos de ML em séries temporais. Vamos testar um primeiro caso sem nenhum tipo de preprocessamento, apenas criando as janelas:" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from tsbook.forecasting.reduction import ReductionForecaster\n", + "from sklearn.ensemble import RandomForestRegressor\n", + "\n", + "model = ReductionForecaster(\n", + " RandomForestRegressor(n_estimators=100, random_state=42),\n", + " window_length=30,\n", + ")\n", + "\n", + "model.fit(y_train, X=X_train)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "y_pred = model.predict(fh=y_test.index, X=X_test)\n", + "plot_series(y_train, y_test, y_pred, labels=[\"Treino\", \"Teste\", \"Previsão com ML\"])\n", + "plt.show()" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Claramente, tivemos o problema que mencionamos anteriormente.\n", + "\n", + "\n", + "### Solução 1: Diferenciação\n", + "\n", + "Uma solução é usar a diferenciação para remover a tendência da série. " + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.transformations.series.difference import Differencer\n", + "\n", + "regressor = RandomForestRegressor(n_estimators=100, random_state=42)\n", + "\n", + "model = Differencer() * ReductionForecaster(\n", + " regressor,\n", + " window_length=30,\n", + " steps_ahead=1,\n", + ")\n", + "\n", + "model.fit(y_train, X=X_train)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "y_pred_diff = model.predict(fh=y_test.index, X=X_test)\n", + "\n", + "plot_series(\n", + " y_train, y_test, y_pred_diff, labels=[\"Treino\", \"Teste\", \"Previsão com ML + Diferença\"]\n", + ")\n", + "plt.show()" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Aqui, já vemos uma melhora significativa. Mas tem algo que podemos melhorar para realizar a diferenciação? Sim. Lembre que essa série tem um padrão multiplicativo. Então, antes de aplicar a diferenciação, podemos aplicar uma transformação logarítmica para estabilizar a variância:" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.transformations.series.boxcox import LogTransformer\n", + "\n", + "model_log = LogTransformer() * model\n", + "\n", + "model_log.fit(y_train, X=X_train)\n", + "y_pred_log_diff = model_log.predict(fh=y_test.index, X=X_test)\n", + "plot_series(\n", + " y_train,\n", + " y_test,\n", + " y_pred_log_diff,\n", + " labels=[\"Treino\", \"Teste\", \"Previsão com ML + Log + Diferença\"],\n", + ")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Solução 2: Normalização por janela\n", + "\n", + "A diferenciação aumenta o ruído da série, o que pode dificultar o trabalho do modelo de ML. Outra opção é normalizar em cada janela. A classe `ReductionForecaster` tem um parâmetro chamado `normalization_strategy`, que pode ser usado para determinar a estratégia de normalização. Vamos usar a estratégia `divide_mean`, que divide cada janela pelo seu valor médio." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "model = ReductionForecaster(\n", + " regressor,\n", + " window_length=30,\n", + " steps_ahead=1,\n", + " normalization_strategy=\"divide_mean\",\n", + ")\n", + "\n", + "model.fit(y_train, X=X_train)\n", + "y_pred_norm = model.predict(fh=y_test.index, X=X_test)\n", + "\n", + "plot_series(\n", + " y_train, y_test, y_pred_norm, labels=[\"Treino\", \"Teste\", \"Previsão com ML + Normalização\"]\n", + ")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Modelo direto e recursivo\n", + "\n", + "Podemos fazer um conjunto de modelos, um para cada passo à frente. Abaixo, definimos `steps_ahead=12`, o que significa que o modelo vai prever 12 passos à frente diretamente." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "model = ReductionForecaster(\n", + " regressor,\n", + " window_length=30,\n", + " steps_ahead=12,\n", + " normalization_strategy=\"divide_mean\",\n", + ")\n", + "\n", + "model.fit(y_train, X=X_train)\n", + "y_pred_norm_direct = model.predict(fh=y_test.index, X=X_test)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "plot_series(\n", + " y_train,\n", + " y_test,\n", + " y_pred_norm_direct,\n", + " labels=[\"Treino\", \"Teste\", \"Previsão com ML + Normalização\"],\n", + ")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Podemos comparar o MAPE de todos os modelos:" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.performance_metrics.forecasting import MeanAbsolutePercentageError\n", + "\n", + "mape = MeanAbsolutePercentageError()\n", + "\n", + "results = {}\n", + "for _y_pred, label in zip(\n", + " [\n", + " y_pred,\n", + " y_pred_diff,\n", + " y_pred_log_diff,\n", + " y_pred_norm,\n", + " y_pred_norm_direct,\n", + " ],\n", + " [\n", + " \"ML\",\n", + " \"ML + Diferença\",\n", + " \"ML + Log + Diferença\",\n", + " \"ML + Normalização\",\n", + " \"ML + Normalização + Direto\",\n", + " ],\n", + "):\n", + " results[label] = mape(y_test, _y_pred)\n", + "\n", + "import pandas as pd\n", + "\n", + "results = pd.DataFrame.from_dict(results, orient=\"index\", columns=[\"MAPE\"])\n", + "results.sort_values(\"MAPE\")" + ], + "execution_count": null, + "outputs": [] + } + ], + "metadata": { + "kernelspec": { + "name": "python3", + "language": "python", + "display_name": "Python 3 (ipykernel)", + "path": "/Users/felipeangelim/Workspace/python_brasil_2025/.venv/share/jupyter/kernels/python3" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} \ No newline at end of file diff --git a/notebooks/part2/panel_data.ipynb b/notebooks/part2/panel_data.ipynb new file mode 100644 index 0000000..fa3bb05 --- /dev/null +++ b/notebooks/part2/panel_data.ipynb @@ -0,0 +1,724 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Dados em painel (multi-series)\n", + "\n", + "Em muitas aplicações, não temos acesso a uma única série temporal, mas sim a um conjunto de séries temporais relacionadas. Isso é comum em cenários como vendas de produtos em diferentes lojas, consumo de energia em diferentes regiões, etc. Esses dados são chamados de dados em painel.\n", + "\n", + "Uma ideia poderosa é aproveitar a similaridade entre as séries para melhorar as previsões. Chamamos de **modelos globais** os modelos capazes de aprender padrões comuns entre as séries, ao contrário dos **modelos locais** que aprendem apenas com uma única série.\n", + "\n", + "A maioria dos modelos clássicos de séries temporais são locais. Modelos globais são, em geral, baseados em modelos tabulares de ML ou deep learning. Segundo competições de séries temporais, como a M5, em forecasts de painel os modelos globais são os que apresentam melhor desempenho [@makridakis2022m5].\n", + "\n", + "\n", + "## Acessando os dados\n", + "\n", + "Aqui, vamos usar o dataset sintético que vimos antes, mas agora teremos acesso às várias séries temporais que compõe o total.\n", + "\n", + "Esse dataset é feito para simular um caso de varejo, onde temos vendas diárias de vários produtos:" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "# | echo: false\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"ignore\")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "# | code-fold: true\n", + "import pandas as pd\n", + "import matplotlib.pyplot as plt\n", + "\n", + "from sktime.utils.plotting import plot_series" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from tsbook.datasets.retail import SyntheticRetail\n", + "dataset = SyntheticRetail(\"panel\")\n", + "y_train, X_train, y_test, X_test = dataset.load(\n", + " \"y_train\", \"X_train\", \"y_test\", \"X_test\"\n", + ")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note que, para dados em painel, os dataframes possuem mais um nível de índice, que identifica a série temporal a que cada observação pertence:" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "display(X_train)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Podemos visualizar algumas séries. Vemos que há mais zeros nesse dataset, em comparação\n", + "ao que usamos antes." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.utils.plotting import plot_series\n", + "\n", + "fig, ax = plt.subplots(figsize=(10, 4))\n", + "y_train.unstack(level=0).droplevel(0, axis=1).iloc[:, [0,10]].plot(ax=ax, alpha=0.7)\n", + "plt.show()" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Pandas e multi-índices\n", + "\n", + "Para trabalhar com essas estruturas de dados, é importante revisar algumas operações do pandas." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "y_train.index.get_level_values(-1)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As seguintes operações são bem úteis para trabalhar com multi-índices:" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "y_train.index" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Acessar valores únicos no primeiro nivel (nível 0, mais à esquerda):" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "y_train.index.get_level_values(0).unique()" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Selecionar uma série específica (nível 0 igual a 0):" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "y_train.loc[0]" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Aqui, podemos usar `pd.IndexSlice` para selecionar várias séries ao mesmo tempo.\n", + "Note que pd.IndexSlice é passado diretamente para `.loc`:" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "y_train.loc[pd.IndexSlice[[0,2], :]]" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Agora, para selecionar o horizonte de forecasting, temos que chamar `unique`:" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "fh = y_test.index.get_level_values(1).unique()\n", + "\n", + "fh" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Upcasting automático\n", + "\n", + "Nem todos modelos suportam nativamente dados em painel. Por exemplo, exponential smoothing.\n", + "Aqui, temos uma boa notícia: sem linhas extras necessárias. O sktime faz *upcasting* automático para dados em painel ao usar estimadores do `sktime`." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.forecasting.naive import NaiveForecaster\n", + "\n", + "\n", + "naive_forecaster = NaiveForecaster(strategy=\"last\", window_length=1)\n", + "naive_forecaster.fit(y_train)\n", + "y_pred_naive = naive_forecaster.predict(fh=fh)\n", + "\n", + "y_pred_naive" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Internamente, o `sktime` cria um clone do estimador para cada série nos dados em painel.\n", + "Em seguida, cada clone é treinado com a série correspondente. Isso é feito de\n", + "forma transparente para usuário, mas sem exigir esforço.\n", + "\n", + "O atributo `forecasters_` armazena um DataFrame com os estimatores de cada série." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "naive_forecaster.forecasters_.head()" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "É dificil explicar o quanto isso é extremamente útil para código limpo e prototipagem rápida.\n", + "Foi um dos motivos que me levaram a usar o `sktime`.\n", + "\n", + "\n", + "## Métricas\n", + "\n", + "Agora que temos várias séries, precisamos explicar como calcular métricas de avaliação.\n", + "O sktime oferece duas opções para isso, como argumentos na criação da métrica:\n", + "\n", + "* `multilevel=\"uniform_average_time\"` para calcular a média das séries temporais no painel.\n", + "* `multilevel=\"raw_values\"` para obter o erro por série.\n" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.performance_metrics.forecasting import MeanSquaredScaledError\n", + "\n", + "metric = MeanSquaredScaledError(multilevel=\"uniform_average_time\")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "metric(y_true=y_test, y_pred=y_pred_naive, y_train=y_train)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Na prática, as métricas que a sua aplicação exige podem ser diferentes. Por exemplo,\n", + "as séries temporais podem ter diferentes importâncias, e você pode querer ponderar\n", + "as métricas de acordo. \n", + "\n", + "Para isso, é possível criar uma métrica customizada no sktime, mas não entraremos\n", + "nesse mérito aqui.\n", + "\n", + "## Modelos globais de Machine Learning\n", + "\n", + "Quando vimos como usar modelos de Machine Learning para forecasting, já mencionamos\n", + "como é necessário traduzir o problema de séries temporais para um problema de regressão tradicional.\n", + "\n", + "No caso de dados em painel, também podemos usar essa abordagem, mas agora aproveitando\n", + "todas as séries temporais para treinar um único modelo global.\n", + " \n", + "![](img/global_reduction.png)\n", + "\n", + "Abaixo, vamos comparar um LightGBM global com um local. Veremos o seguinte: o modelo local é **melhor** que o modelo global, **se não processarmos os dados** corretamente para o modelo global aproveitar as **similaridades** entre as séries!" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from tsbook.forecasting.reduction import ReductionForecaster\n", + "from lightgbm import LGBMRegressor\n", + "\n", + "global_forecaster1 = ReductionForecaster(\n", + " LGBMRegressor(n_estimators=100, verbose=-1),\n", + " window_length=30,\n", + ")\n", + "\n", + "global_forecaster1.fit(y_train, X_train)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "y_pred_global1 = global_forecaster1.predict(fh=fh, X=X_test)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "fig, ax = plt.subplots(figsize=(10, 4))\n", + "y_train.loc[10, \"sales\"].plot(ax=ax, label=\"Treino\")\n", + "y_test.loc[10, \"sales\"].plot(ax=ax, label=\"Teste\")\n", + "y_pred_global1.loc[10, \"sales\"].plot(ax=ax, label=\"Global 1\")\n", + "plt.legend()\n", + "plt.show()" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Para forçar que um modelo global funcione como um modelo local, podemos usar `ForecastByLevel`, que cria um modelo separado para cada série temporal, mesmo quando o estimador suporta dados em painel.\n" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.forecasting.compose import ForecastByLevel\n", + "\n", + "local_forecaster1 = ForecastByLevel(global_forecaster1, groupby=\"local\")\n", + "\n", + "local_forecaster1.fit(y_train, X=X_train)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "y_pred_local1 = local_forecaster1.predict(fh=fh, X=X_test)\n", + "\n", + "err_global1 = metric(y_true=y_test, y_pred=y_pred_global1, y_train=y_train)\n", + "err_local1 = metric(y_true=y_test, y_pred=y_pred_local1, y_train=y_train)\n", + "\n", + "errors = pd.DataFrame(\n", + " {\n", + " \"Global (1)\": [err_global1],\n", + " \"Local (1)\": [err_local1],\n", + " },\n", + " index=[\"MSE\"],\n", + ")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Preprocessamento e engenharia de features\n", + "\n", + "Sabemos como preprocessar séries temporais univariadas para melhorar o desempenho dos modelos de ML. Aplicamos da mesma maneira que fizemos anteriormente o `Differencer`, com objetivo de remover tendências." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.transformations.series.difference import Differencer\n", + "\n", + "\n", + "global_forecaster2 = Differencer() * global_forecaster1\n", + "global_forecaster2.fit(y_train, X_train)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "y_pred_global2 = global_forecaster2.predict(fh=fh, X=X_test)\n", + "metric_global2 = metric(y_true=y_test, y_pred=y_pred_global2, y_train=y_train)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "E agora sua versão local:" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "local_forecaster2 = ForecastByLevel(global_forecaster2, groupby=\"local\")\n", + "local_forecaster2.fit(y_train, X=X_train)\n", + "\n", + "y_pred_local2 = local_forecaster2.predict(fh=fh, X=X_test)\n", + "metric_local2 = metric(y_true=y_test, y_pred=y_pred_local2, y_train=y_train)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Agora, podemos comparar:" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "errors[\"Global (2)\"] = metric_global2\n", + "errors[\"Local (2)\"] = metric_local2\n", + "\n", + "errors" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note como já superamos o modelo global incial, e o modelo local. Isso é para\n", + "destacar que é **essencial** realizar um bom preprocessamento e engenharia de features para que modelos de Machine Learning tenham bom desempenho em dados em painel.\n", + "\n", + "### Normalização por janela\n", + "\n", + "Agora, vamos usar a normalização por janela, que é especialmente útil em dados em painel, onde as séries podem ter diferentes escalas." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "global_forecaster3 = global_forecaster1.clone().set_params(\n", + " normalization_strategy=\"divide_mean\"\n", + ")\n", + "\n", + "global_forecaster3.fit(y_train, X_train)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "# Predict\n", + "y_pred_global3 = global_forecaster3.predict(fh=fh, X=X_test)\n", + "\n", + "# Métrica\n", + "metric_global3 = metric(y_true=y_test, y_pred=y_pred_global3, y_train=y_train)\n", + "\n", + "errors[\"Global 3 (window norm)\"] = metric_global3\n", + "\n", + "display(errors)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Vemos que resultados são ainda melhores!\n", + "\n", + "\n", + "### Pipelines de features exógenas\n", + "\n", + "Podemos ajudar o modelo a capturar sazonalidades adicionando features de Fourier como features exógenas.\n", + "\n", + "Usamos `**` para criar um pipeline aplicado sobre as features exógenas:" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.transformations.series.fourier import FourierFeatures\n", + "\n", + "fourier_features = FourierFeatures(\n", + " sp_list=[365.25, 365.25 / 12], fourier_terms_list=[1, 1], freq=\"D\"\n", + ")\n", + "\n", + "global_forecaster4 = fourier_features**global_forecaster3\n", + "global_forecaster4.fit(y_train, X_train)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "y_pred_global4 = global_forecaster4.predict(fh=fh, X=X_test)\n", + "metric_global4 = metric(y_true=y_test, y_pred=y_pred_global4, y_train=y_train)\n", + "\n", + "errors[\"Global 4 (fourier)\"] = metric_global4\n", + "errors" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Agrupamento + Modelos globais\n", + "\n", + "Uma técnica muito adotada é fazer modelos globais por clusters de séries temporais similares. \n", + "\n", + "Uma maneira de categorizar é usando ADI (Average Demand Interval) e CV² (squared Coefficient of Variation). O componente ADI é calculado como o percentual de períodos com demanda (y>0), e $CV^2$ é o quadrado do coeficiente de variação das demandas positivas.\n", + "\n", + "| **Categoria** | **ADI** | **CV²** | **Padrão típico** | **Exemplos** |\n", + "|:--------------|:--------|:--------|:------------------|:-------------|\n", + "| **Suave (Smooth)** | ≤ 1,32 | ≤ 0,49 | Demanda contínua e estável | Itens de consumo diário, alimentos |\n", + "| **Errática (Erratic)** | ≤ 1,32 | > 0,49 | Demanda contínua, porém muito variável | Moda, eletrônicos |\n", + "| **Intermitente (Intermittent)** | > 1,32 | ≤ 0,49 | Muitos períodos sem venda, mas valores estáveis quando ocorre | Peças de reposição, ferramentas |\n", + "| **Irregular (Lumpy)** | > 1,32 | > 0,49 | Muitos períodos com zero e valores muito variáveis quando há demanda | Equipamentos caros, sobressalentes grandes |\n" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "# | echo: false\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "\n", + "rng = np.random.default_rng(42)\n", + "\n", + "\n", + "def compute_adi(y):\n", + " nnz = np.count_nonzero(y > 0)\n", + " return len(y) / nnz if nnz > 0 else np.inf\n", + "\n", + "\n", + "def compute_cv2(y):\n", + " pos = y[y > 0]\n", + " if len(pos) <= 1:\n", + " return np.inf\n", + " m = pos.mean()\n", + " s = pos.std(ddof=1)\n", + " return (s / m) ** 2 if m > 0 else np.inf\n", + "\n", + "\n", + "def plot_category(ax, series_list, title):\n", + " x = np.arange(len(series_list[0]))\n", + " for y in series_list:\n", + " ax.plot(x, y, linewidth=2)\n", + " adis = [compute_adi(y) for y in series_list]\n", + " cv2s = [compute_cv2(y) for y in series_list]\n", + " ax.text(\n", + " 0.02,\n", + " 0.98,\n", + " f\"avg ADI={np.mean(adis):.2f}\\navg CV²={np.mean(cv2s):.2f}\",\n", + " transform=ax.transAxes,\n", + " va=\"top\",\n", + " ha=\"left\",\n", + " bbox=dict(boxstyle=\"round,pad=0.3\", fc=\"white\", ec=\"0.5\", alpha=0.9),\n", + " )\n", + " ax.set_title(title, fontsize=12)\n", + " ax.set_xlabel(\"time\")\n", + " ax.set_ylabel(\"demand\")\n", + " ax.grid(True, alpha=0.3)\n", + "\n", + "\n", + "N = 60\n", + "\n", + "\n", + "def gen_smooth(n=3):\n", + " out = []\n", + " for _ in range(n):\n", + " base = 20 + 2 * np.sin(np.linspace(0, 3 * np.pi, N))\n", + " noise = rng.normal(0, 2, N)\n", + " out.append(np.clip(base + noise, 0, None))\n", + " return out\n", + "\n", + "\n", + "def gen_erratic(n=3):\n", + " out = []\n", + " for _ in range(n):\n", + " base = 20 + 2 * np.sin(np.linspace(0, 2 * np.pi, N))\n", + " noise = rng.normal(0, 9, N)\n", + " out.append(np.clip(base + noise, 0, None))\n", + " return out\n", + "\n", + "\n", + "def gen_intermittent(n=3):\n", + " out = []\n", + " for _ in range(n):\n", + " mask = rng.binomial(1, 0.35, N)\n", + " values = 18 + rng.normal(0, 2, N)\n", + " out.append(np.clip(mask * values, 0, None))\n", + " return out\n", + "\n", + "\n", + "def gen_lumpy(n=3):\n", + " out = []\n", + " for _ in range(n):\n", + " mask = rng.binomial(1, 0.25, N)\n", + " values = rng.gamma(shape=2.0, scale=10.0, size=N)\n", + " out.append(np.clip(mask * values, 0, None))\n", + " return out\n", + "\n", + "\n", + "cats = [\n", + " (\"Smooth (low ADI, low CV²)\", gen_smooth()),\n", + " (\"Erratic (low ADI, high CV²)\", gen_erratic()),\n", + " (\"Intermittent (high ADI, low CV²)\", gen_intermittent()),\n", + " (\"Lumpy (high ADI, high CV²)\", gen_lumpy()),\n", + "]\n", + "\n", + "fig, axes = plt.subplots(2, 2, figsize=(10, 6))\n", + "for ax, (title, series) in zip(axes.ravel(), cats):\n", + " plot_category(ax, series, title)\n", + "\n", + "plt.tight_layout()\n", + "plt.show()" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from sktime.forecasting.compose import GroupbyCategoryForecaster\n", + "from sktime.transformations.series.adi_cv import ADICVTransformer\n", + "\n", + "# TODO: customize yours!\n", + "group_forecaster = GroupbyCategoryForecaster(\n", + " forecasters =\n", + " {\"smooth\": global_forecaster3.clone(),\n", + " \"erratic\": global_forecaster3.clone(),\n", + " \"intermittent\": global_forecaster3.clone(),\n", + " \"lumpy\": global_forecaster3.clone(),\n", + " },\n", + " transformer=ADICVTransformer(features=[\"class\"],))\n", + "\n", + "\n", + "group_forecaster.fit(y_train, X_train)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "y_pred_group = group_forecaster.predict(fh=fh, X=X_test)\n", + "metric_group = metric(y_true=y_test, y_pred=y_pred_group, y_train=y_train)\n", + "\n", + "metric_group" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Although it did not perform better than the best global model in this synthetic example, in real-world scenarios this approach can be very effective, particularly when there are large samples for each category, allowing the model to learn specific patterns for each group of series.\n", + "\n", + "In addition, this can be useful when there are computation constraints, as training multiple smaller models for each cluster can be more efficient than training a single large global model.\n", + "\n", + "\n", + "## Resumo\n", + "\n", + "Aqui, vimos como trabalhar com dados em painel (multi-series) usando o sktime. Vimos, especialmente, como criar modelos globais de Machine Learning que aproveitam as similaridades entre as séries para melhorar o desempenho das previsões. Também destacamos a importância do preprocessamento e da engenharia de features para obter bons resultados com esses modelos.\n", + "\n", + "Para uma análise mais aprofundada, recomendo o artigo de [@montero2021principles], que discute princípios para forecasting em dados em painel usando modelos globais de Machine Learning.\n" + ] + } + ], + "metadata": { + "kernelspec": { + "name": "python3", + "language": "python", + "display_name": "Python 3 (ipykernel)", + "path": "/Users/felipeangelim/Workspace/python_brasil_2025/.venv/share/jupyter/kernels/python3" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} \ No newline at end of file diff --git a/notebooks/tmp.ipynb b/notebooks/tmp.ipynb new file mode 100644 index 0000000..e69de29 diff --git a/poetry.lock b/poetry.lock index 2ed62da..59c2138 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,183 @@ # This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. +[[package]] +name = "absl-py" +version = "2.3.1" +description = "Abseil Python Common Libraries, see https://github.com/abseil/abseil-py." +optional = false +python-versions = ">=3.8" +files = [ + {file = "absl_py-2.3.1-py3-none-any.whl", hash = "sha256:eeecf07f0c2a93ace0772c92e596ace6d3d3996c042b2128459aaae2a76de11d"}, + {file = "absl_py-2.3.1.tar.gz", hash = "sha256:a97820526f7fbfd2ec1bce83f3f25e3a14840dac0d8e02a0b71cd75db3f77fc9"}, +] + +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +description = "Happy Eyeballs for asyncio" +optional = false +python-versions = ">=3.9" +files = [ + {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, + {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, +] + +[[package]] +name = "aiohttp" +version = "3.13.1" +description = "Async http client/server framework (asyncio)" +optional = false +python-versions = ">=3.9" +files = [ + {file = "aiohttp-3.13.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2349a6b642020bf20116a8a5c83bae8ba071acf1461c7cbe45fc7fafd552e7e2"}, + {file = "aiohttp-3.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2a8434ca31c093a90edb94d7d70e98706ce4d912d7f7a39f56e1af26287f4bb7"}, + {file = "aiohttp-3.13.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0bd610a7e87431741021a9a6ab775e769ea8c01bf01766d481282bfb17df597f"}, + {file = "aiohttp-3.13.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:777ec887264b629395b528af59b8523bf3164d4c6738cd8989485ff3eda002e2"}, + {file = "aiohttp-3.13.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ac1892f56e2c445aca5ba28f3bf8e16b26dfc05f3c969867b7ef553b74cb4ebe"}, + {file = "aiohttp-3.13.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:499a047d1c5e490c31d16c033e2e47d1358f0e15175c7a1329afc6dfeb04bc09"}, + {file = "aiohttp-3.13.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:610be925f89501938c770f1e28ca9dd62e9b308592c81bd5d223ce92434c0089"}, + {file = "aiohttp-3.13.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90eb902c06c6ac85d6b80fa9f2bd681f25b1ebf73433d428b3d182a507242711"}, + {file = "aiohttp-3.13.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ab8ac3224b2beb46266c094b3869d68d5f96f35dba98e03dea0acbd055eefa03"}, + {file = "aiohttp-3.13.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:79ac65b6e2731558aad1e4c1a655d2aa2a77845b62acecf5898b0d4fe8c76618"}, + {file = "aiohttp-3.13.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4dadbd858ed8c04d1aa7a2a91ad65f8e1fbd253ae762ef5be8111e763d576c3c"}, + {file = "aiohttp-3.13.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e0b2ccd331bc77149e88e919aa95c228a011e03e1168fd938e6aeb1a317d7a8a"}, + {file = "aiohttp-3.13.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:fba3c85fb24fe204e73f3c92f09f4f5cfa55fa7e54b34d59d91b7c5a258d0f6a"}, + {file = "aiohttp-3.13.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8d5011e4e741d2635cda18f2997a56e8e1d1b94591dc8732f2ef1d3e1bfc5f45"}, + {file = "aiohttp-3.13.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c5fe2728a89c82574bd3132d59237c3b5fb83e2e00a320e928d05d74d1ae895f"}, + {file = "aiohttp-3.13.1-cp310-cp310-win32.whl", hash = "sha256:add14a5e68cbcfc526c89c1ed8ea963f5ff8b9b4b854985b07820c6fbfdb3c3c"}, + {file = "aiohttp-3.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:a4cc9d9cfdf75a69ae921c407e02d0c1799ab333b0bc6f7928c175f47c080d6a"}, + {file = "aiohttp-3.13.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9eefa0a891e85dca56e2d00760945a6325bd76341ec386d3ad4ff72eb97b7e64"}, + {file = "aiohttp-3.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6c20eb646371a5a57a97de67e52aac6c47badb1564e719b3601bbb557a2e8fd0"}, + {file = "aiohttp-3.13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bfc28038cd86fb1deed5cc75c8fda45c6b0f5c51dfd76f8c63d3d22dc1ab3d1b"}, + {file = "aiohttp-3.13.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b22eeffca2e522451990c31a36fe0e71079e6112159f39a4391f1c1e259a795"}, + {file = "aiohttp-3.13.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:65782b2977c05ebd78787e3c834abe499313bf69d6b8be4ff9c340901ee7541f"}, + {file = "aiohttp-3.13.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dacba54f9be3702eb866b0b9966754b475e1e39996e29e442c3cd7f1117b43a9"}, + {file = "aiohttp-3.13.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:aa878da718e8235302c365e376b768035add36b55177706d784a122cb822a6a4"}, + {file = "aiohttp-3.13.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e4b4e607fbd4964d65945a7b9d1e7f98b0d5545736ea613f77d5a2a37ff1e46"}, + {file = "aiohttp-3.13.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0c3db2d0e5477ad561bf7ba978c3ae5f8f78afda70daa05020179f759578754f"}, + {file = "aiohttp-3.13.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9739d34506fdf59bf2c092560d502aa728b8cdb33f34ba15fb5e2852c35dd829"}, + {file = "aiohttp-3.13.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:b902e30a268a85d50197b4997edc6e78842c14c0703450f632c2d82f17577845"}, + {file = "aiohttp-3.13.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1bbfc04c8de7def6504cce0a97f9885a5c805fd2395a0634bc10f9d6ecb42524"}, + {file = "aiohttp-3.13.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:6941853405a38a5eeb7d9776db77698df373ff7fa8c765cb81ea14a344fccbeb"}, + {file = "aiohttp-3.13.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:7764adcd2dc8bd21c8228a53dda2005428498dc4d165f41b6086f0ac1c65b1c9"}, + {file = "aiohttp-3.13.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c09e08d38586fa59e5a2f9626505a0326fadb8e9c45550f029feeb92097a0afc"}, + {file = "aiohttp-3.13.1-cp311-cp311-win32.whl", hash = "sha256:ce1371675e74f6cf271d0b5530defb44cce713fd0ab733713562b3a2b870815c"}, + {file = "aiohttp-3.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:77a2f5cc28cf4704cc157be135c6a6cfb38c9dea478004f1c0fd7449cf445c28"}, + {file = "aiohttp-3.13.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0760bd9a28efe188d77b7c3fe666e6ef74320d0f5b105f2e931c7a7e884c8230"}, + {file = "aiohttp-3.13.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7129a424b441c3fe018a414401bf1b9e1d49492445f5676a3aecf4f74f67fcdb"}, + {file = "aiohttp-3.13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e1cb04ae64a594f6ddf5cbb024aba6b4773895ab6ecbc579d60414f8115e9e26"}, + {file = "aiohttp-3.13.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:782d656a641e755decd6bd98d61d2a8ea062fd45fd3ff8d4173605dd0d2b56a1"}, + {file = "aiohttp-3.13.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f92ad8169767429a6d2237331726c03ccc5f245222f9373aa045510976af2b35"}, + {file = "aiohttp-3.13.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0e778f634ca50ec005eefa2253856921c429581422d887be050f2c1c92e5ce12"}, + {file = "aiohttp-3.13.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9bc36b41cf4aab5d3b34d22934a696ab83516603d1bc1f3e4ff9930fe7d245e5"}, + {file = "aiohttp-3.13.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3fd4570ea696aee27204dd524f287127ed0966d14d309dc8cc440f474e3e7dbd"}, + {file = "aiohttp-3.13.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7bda795f08b8a620836ebfb0926f7973972a4bf8c74fdf9145e489f88c416811"}, + {file = "aiohttp-3.13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:055a51d90e351aae53dcf324d0eafb2abe5b576d3ea1ec03827d920cf81a1c15"}, + {file = "aiohttp-3.13.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:d4131df864cbcc09bb16d3612a682af0db52f10736e71312574d90f16406a867"}, + {file = "aiohttp-3.13.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:163d3226e043f79bf47c87f8dfc89c496cc7bc9128cb7055ce026e435d551720"}, + {file = "aiohttp-3.13.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:a2370986a3b75c1a5f3d6f6d763fc6be4b430226577b0ed16a7c13a75bf43d8f"}, + {file = "aiohttp-3.13.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:d7c14de0c7c9f1e6e785ce6cbe0ed817282c2af0012e674f45b4e58c6d4ea030"}, + {file = "aiohttp-3.13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb611489cf0db10b99beeb7280bd39e0ef72bc3eb6d8c0f0a16d8a56075d1eb7"}, + {file = "aiohttp-3.13.1-cp312-cp312-win32.whl", hash = "sha256:f90fe0ee75590f7428f7c8b5479389d985d83c949ea10f662ab928a5ed5cf5e6"}, + {file = "aiohttp-3.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:3461919a9dca272c183055f2aab8e6af0adc810a1b386cce28da11eb00c859d9"}, + {file = "aiohttp-3.13.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:55785a7f8f13df0c9ca30b5243d9909bd59f48b274262a8fe78cee0828306e5d"}, + {file = "aiohttp-3.13.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4bef5b83296cebb8167707b4f8d06c1805db0af632f7a72d7c5288a84667e7c3"}, + {file = "aiohttp-3.13.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:27af0619c33f9ca52f06069ec05de1a357033449ab101836f431768ecfa63ff5"}, + {file = "aiohttp-3.13.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a47fe43229a8efd3764ef7728a5c1158f31cdf2a12151fe99fde81c9ac87019c"}, + {file = "aiohttp-3.13.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6e68e126de5b46e8b2bee73cab086b5d791e7dc192056916077aa1e2e2b04437"}, + {file = "aiohttp-3.13.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e65ef49dd22514329c55970d39079618a8abf856bae7147913bb774a3ab3c02f"}, + {file = "aiohttp-3.13.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0e425a7e0511648b3376839dcc9190098671a47f21a36e815b97762eb7d556b0"}, + {file = "aiohttp-3.13.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:010dc9b7110f055006acd3648d5d5955bb6473b37c3663ec42a1b4cba7413e6b"}, + {file = "aiohttp-3.13.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1b5c722d0ca5f57d61066b5dfa96cdb87111e2519156b35c1f8dd17c703bee7a"}, + {file = "aiohttp-3.13.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:93029f0e9b77b714904a281b5aa578cdc8aa8ba018d78c04e51e1c3d8471b8ec"}, + {file = "aiohttp-3.13.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:d1824c7d08d8ddfc8cb10c847f696942e5aadbd16fd974dfde8bd2c3c08a9fa1"}, + {file = "aiohttp-3.13.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:8f47d0ff5b3eb9c1278a2f56ea48fda667da8ebf28bd2cb378b7c453936ce003"}, + {file = "aiohttp-3.13.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:8a396b1da9b51ded79806ac3b57a598f84e0769eaa1ba300655d8b5e17b70c7b"}, + {file = "aiohttp-3.13.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d9c52a65f54796e066b5d674e33b53178014752d28bca555c479c2c25ffcec5b"}, + {file = "aiohttp-3.13.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a89da72d18d6c95a653470b78d8ee5aa3c4b37212004c103403d0776cbea6ff0"}, + {file = "aiohttp-3.13.1-cp313-cp313-win32.whl", hash = "sha256:02e0258b7585ddf5d01c79c716ddd674386bfbf3041fbbfe7bdf9c7c32eb4a9b"}, + {file = "aiohttp-3.13.1-cp313-cp313-win_amd64.whl", hash = "sha256:ef56ffe60e8d97baac123272bde1ab889ee07d3419606fae823c80c2b86c403e"}, + {file = "aiohttp-3.13.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:77f83b3dc5870a2ea79a0fcfdcc3fc398187ec1675ff61ec2ceccad27ecbd303"}, + {file = "aiohttp-3.13.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:9cafd2609ebb755e47323306c7666283fbba6cf82b5f19982ea627db907df23a"}, + {file = "aiohttp-3.13.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9c489309a2ca548d5f11131cfb4092f61d67954f930bba7e413bcdbbb82d7fae"}, + {file = "aiohttp-3.13.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79ac15fe5fdbf3c186aa74b656cd436d9a1e492ba036db8901c75717055a5b1c"}, + {file = "aiohttp-3.13.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:095414be94fce3bc080684b4cd50fb70d439bc4662b2a1984f45f3bf9ede08aa"}, + {file = "aiohttp-3.13.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c68172e1a2dca65fa1272c85ca72e802d78b67812b22827df01017a15c5089fa"}, + {file = "aiohttp-3.13.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3751f9212bcd119944d4ea9de6a3f0fee288c177b8ca55442a2cdff0c8201eb3"}, + {file = "aiohttp-3.13.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8619dca57d98a8353abdc7a1eeb415548952b39d6676def70d9ce76d41a046a9"}, + {file = "aiohttp-3.13.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:97795a0cb0a5f8a843759620e9cbd8889f8079551f5dcf1ccd99ed2f056d9632"}, + {file = "aiohttp-3.13.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1060e058da8f9f28a7026cdfca9fc886e45e551a658f6a5c631188f72a3736d2"}, + {file = "aiohttp-3.13.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:f48a2c26333659101ef214907d29a76fe22ad7e912aa1e40aeffdff5e8180977"}, + {file = "aiohttp-3.13.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f1dfad638b9c91ff225162b2824db0e99ae2d1abe0dc7272b5919701f0a1e685"}, + {file = "aiohttp-3.13.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:8fa09ab6dd567cb105db4e8ac4d60f377a7a94f67cf669cac79982f626360f32"}, + {file = "aiohttp-3.13.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4159fae827f9b5f655538a4f99b7cbc3a2187e5ca2eee82f876ef1da802ccfa9"}, + {file = "aiohttp-3.13.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ad671118c19e9cfafe81a7a05c294449fe0ebb0d0c6d5bb445cd2190023f5cef"}, + {file = "aiohttp-3.13.1-cp314-cp314-win32.whl", hash = "sha256:c5c970c148c48cf6acb65224ca3c87a47f74436362dde75c27bc44155ccf7dfc"}, + {file = "aiohttp-3.13.1-cp314-cp314-win_amd64.whl", hash = "sha256:748a00167b7a88385756fa615417d24081cba7e58c8727d2e28817068b97c18c"}, + {file = "aiohttp-3.13.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:390b73e99d7a1f0f658b3f626ba345b76382f3edc65f49d6385e326e777ed00e"}, + {file = "aiohttp-3.13.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:27e83abb330e687e019173d8fc1fd6a1cf471769624cf89b1bb49131198a810a"}, + {file = "aiohttp-3.13.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2b20eed07131adbf3e873e009c2869b16a579b236e9d4b2f211bf174d8bef44a"}, + {file = "aiohttp-3.13.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:58fee9ef8477fd69e823b92cfd1f590ee388521b5ff8f97f3497e62ee0656212"}, + {file = "aiohttp-3.13.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1f62608fcb7b3d034d5e9496bea52d94064b7b62b06edba82cd38191336bbeda"}, + {file = "aiohttp-3.13.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fdc4d81c3dfc999437f23e36d197e8b557a3f779625cd13efe563a9cfc2ce712"}, + {file = "aiohttp-3.13.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:601d7ec812f746fd80ff8af38eeb3f196e1bab4a4d39816ccbc94c222d23f1d0"}, + {file = "aiohttp-3.13.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47c3f21c469b840d9609089435c0d9918ae89f41289bf7cc4afe5ff7af5458db"}, + {file = "aiohttp-3.13.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d6c6cdc0750db88520332d4aaa352221732b0cafe89fd0e42feec7cb1b5dc236"}, + {file = "aiohttp-3.13.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:58a12299eeb1fca2414ee2bc345ac69b0f765c20b82c3ab2a75d91310d95a9f6"}, + {file = "aiohttp-3.13.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:0989cbfc195a4de1bb48f08454ef1cb47424b937e53ed069d08404b9d3c7aea1"}, + {file = "aiohttp-3.13.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:feb5ee664300e2435e0d1bc3443a98925013dfaf2cae9699c1f3606b88544898"}, + {file = "aiohttp-3.13.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:58a6f8702da0c3606fb5cf2e669cce0ca681d072fe830968673bb4c69eb89e88"}, + {file = "aiohttp-3.13.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:a417ceb433b9d280e2368ffea22d4bc6e3e0d894c4bc7768915124d57d0964b6"}, + {file = "aiohttp-3.13.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8ac8854f7b0466c5d6a9ea49249b3f6176013859ac8f4bb2522ad8ed6b94ded2"}, + {file = "aiohttp-3.13.1-cp314-cp314t-win32.whl", hash = "sha256:be697a5aeff42179ed13b332a411e674994bcd406c81642d014ace90bf4bb968"}, + {file = "aiohttp-3.13.1-cp314-cp314t-win_amd64.whl", hash = "sha256:f1d6aa90546a4e8f20c3500cb68ab14679cd91f927fa52970035fd3207dfb3da"}, + {file = "aiohttp-3.13.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a5dc5c3b086adc232fd07e691dcc452e8e407bf7c810e6f7e18fd3941a24c5c0"}, + {file = "aiohttp-3.13.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fb7c5f0b35f5a3a06bd5e1a7b46204c2dca734cd839da830db81f56ce60981fe"}, + {file = "aiohttp-3.13.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cb1e557bd1a90f28dc88a6e31332753795cd471f8d18da749c35930e53d11880"}, + {file = "aiohttp-3.13.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e95ea8fb27fbf667d322626a12db708be308b66cd9afd4a997230ded66ffcab4"}, + {file = "aiohttp-3.13.1-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f37da298a486e53f9b5e8ef522719b3787c4fe852639a1edcfcc9f981f2c20ba"}, + {file = "aiohttp-3.13.1-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:37cc1b9773d2a01c3f221c3ebecf0c82b1c93f55f3fde52929e40cf2ed777e6c"}, + {file = "aiohttp-3.13.1-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:412bfc63a6de4907aae6041da256d183f875bf4dc01e05412b1d19cfc25ee08c"}, + {file = "aiohttp-3.13.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d8ccd2946aadf7793643b57d98d5a82598295a37f98d218984039d5179823cd5"}, + {file = "aiohttp-3.13.1-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:51b3c44434a50bca1763792c6b98b9ba1d614339284780b43107ef37ec3aa1dc"}, + {file = "aiohttp-3.13.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9bff813424c70ad38667edfad4fefe8ca1b09a53621ce7d0fd017e418438f58a"}, + {file = "aiohttp-3.13.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:ed782a438ff4b66ce29503a1555be51a36e4b5048c3b524929378aa7450c26a9"}, + {file = "aiohttp-3.13.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:a1d6fd6e9e3578a7aeb0fa11e9a544dceccb840330277bf281325aa0fe37787e"}, + {file = "aiohttp-3.13.1-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:7c5e2660c6d6ab0d85c45bc8bd9f685983ebc63a5c7c0fd3ddeb647712722eca"}, + {file = "aiohttp-3.13.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:168279a11571a39d689fc7b9725ddcde0dc68f2336b06b69fcea0203f9fb25d8"}, + {file = "aiohttp-3.13.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ff0357fa3dd28cf49ad8c515452a1d1d7ad611b513e0a4f6fa6ad6780abaddfd"}, + {file = "aiohttp-3.13.1-cp39-cp39-win32.whl", hash = "sha256:a617769e8294ca58601a579697eae0b0e1b1ef770c5920d55692827d6b330ff9"}, + {file = "aiohttp-3.13.1-cp39-cp39-win_amd64.whl", hash = "sha256:f2543eebf890739fd93d06e2c16d97bdf1301d2cda5ffceb7a68441c7b590a92"}, + {file = "aiohttp-3.13.1.tar.gz", hash = "sha256:4b7ee9c355015813a6aa085170b96ec22315dabc3d866fd77d147927000e9464"}, +] + +[package.dependencies] +aiohappyeyeballs = ">=2.5.0" +aiosignal = ">=1.4.0" +attrs = ">=17.3.0" +frozenlist = ">=1.1.1" +multidict = ">=4.5,<7.0" +propcache = ">=0.2.0" +yarl = ">=1.17.0,<2.0" + +[package.extras] +speedups = ["Brotli", "aiodns (>=3.3.0)", "backports.zstd", "brotlicffi"] + +[[package]] +name = "aiosignal" +version = "1.4.0" +description = "aiosignal: a list of registered asynchronous callbacks" +optional = false +python-versions = ">=3.9" +files = [ + {file = "aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e"}, + {file = "aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7"}, +] + +[package.dependencies] +frozenlist = ">=1.1.0" +typing-extensions = {version = ">=4.2", markers = "python_version < \"3.13\""} + [[package]] name = "anyio" version = "4.11.0" @@ -421,6 +599,29 @@ files = [ {file = "charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a"}, ] +[[package]] +name = "chex" +version = "0.1.91" +description = "Chex: Testing made fun, in JAX!" +optional = false +python-versions = ">=3.11" +files = [ + {file = "chex-0.1.91-py3-none-any.whl", hash = "sha256:6fc4cbfc22301c08d4a7ef706045668410100962eba8ba6af03fa07f4e5dcf9b"}, + {file = "chex-0.1.91.tar.gz", hash = "sha256:65367a521415ada905b8c0222b0a41a68337fcadf79a1fb6fc992dbd95dd9f76"}, +] + +[package.dependencies] +absl-py = ">=2.3.1" +jax = ">=0.7.0" +jaxlib = ">=0.7.0" +numpy = ">=1.24.1" +toolz = ">=1.0.0" +typing_extensions = ">=4.15.0" + +[package.extras] +docs = ["sphinx (>=6.0.0)", "sphinx-book-theme (>=1.0.1)", "sphinxcontrib-katex"] +test = ["cloudpickle (==3.1.0)", "dm-tree (>=0.1.9)"] + [[package]] name = "colorama" version = "0.4.6" @@ -641,6 +842,17 @@ files = [ [package.extras] devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benchmark", "pytest-cache", "validictory"] +[[package]] +name = "filelock" +version = "3.20.0" +description = "A platform independent file lock." +optional = false +python-versions = ">=3.10" +files = [ + {file = "filelock-3.20.0-py3-none-any.whl", hash = "sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2"}, + {file = "filelock-3.20.0.tar.gz", hash = "sha256:711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4"}, +] + [[package]] name = "fonttools" version = "4.60.1" @@ -732,6 +944,203 @@ files = [ {file = "fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f"}, ] +[[package]] +name = "frozenlist" +version = "1.8.0" +description = "A list-like structure which implements collections.abc.MutableSequence" +optional = false +python-versions = ">=3.9" +files = [ + {file = "frozenlist-1.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b37f6d31b3dcea7deb5e9696e529a6aa4a898adc33db82da12e4c60a7c4d2011"}, + {file = "frozenlist-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef2b7b394f208233e471abc541cc6991f907ffd47dc72584acee3147899d6565"}, + {file = "frozenlist-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a88f062f072d1589b7b46e951698950e7da00442fc1cacbe17e19e025dc327ad"}, + {file = "frozenlist-1.8.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f57fb59d9f385710aa7060e89410aeb5058b99e62f4d16b08b91986b9a2140c2"}, + {file = "frozenlist-1.8.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:799345ab092bee59f01a915620b5d014698547afd011e691a208637312db9186"}, + {file = "frozenlist-1.8.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c23c3ff005322a6e16f71bf8692fcf4d5a304aaafe1e262c98c6d4adc7be863e"}, + {file = "frozenlist-1.8.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8a76ea0f0b9dfa06f254ee06053d93a600865b3274358ca48a352ce4f0798450"}, + {file = "frozenlist-1.8.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c7366fe1418a6133d5aa824ee53d406550110984de7637d65a178010f759c6ef"}, + {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:13d23a45c4cebade99340c4165bd90eeb4a56c6d8a9d8aa49568cac19a6d0dc4"}, + {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:e4a3408834f65da56c83528fb52ce7911484f0d1eaf7b761fc66001db1646eff"}, + {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:42145cd2748ca39f32801dad54aeea10039da6f86e303659db90db1c4b614c8c"}, + {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e2de870d16a7a53901e41b64ffdf26f2fbb8917b3e6ebf398098d72c5b20bd7f"}, + {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:20e63c9493d33ee48536600d1a5c95eefc870cd71e7ab037763d1fbb89cc51e7"}, + {file = "frozenlist-1.8.0-cp310-cp310-win32.whl", hash = "sha256:adbeebaebae3526afc3c96fad434367cafbfd1b25d72369a9e5858453b1bb71a"}, + {file = "frozenlist-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:667c3777ca571e5dbeb76f331562ff98b957431df140b54c85fd4d52eea8d8f6"}, + {file = "frozenlist-1.8.0-cp310-cp310-win_arm64.whl", hash = "sha256:80f85f0a7cc86e7a54c46d99c9e1318ff01f4687c172ede30fd52d19d1da1c8e"}, + {file = "frozenlist-1.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:09474e9831bc2b2199fad6da3c14c7b0fbdd377cce9d3d77131be28906cb7d84"}, + {file = "frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:17c883ab0ab67200b5f964d2b9ed6b00971917d5d8a92df149dc2c9779208ee9"}, + {file = "frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa47e444b8ba08fffd1c18e8cdb9a75db1b6a27f17507522834ad13ed5922b93"}, + {file = "frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2552f44204b744fba866e573be4c1f9048d6a324dfe14475103fd51613eb1d1f"}, + {file = "frozenlist-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e7c38f250991e48a9a73e6423db1bb9dd14e722a10f6b8bb8e16a0f55f695"}, + {file = "frozenlist-1.8.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8585e3bb2cdea02fc88ffa245069c36555557ad3609e83be0ec71f54fd4abb52"}, + {file = "frozenlist-1.8.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:edee74874ce20a373d62dc28b0b18b93f645633c2943fd90ee9d898550770581"}, + {file = "frozenlist-1.8.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c9a63152fe95756b85f31186bddf42e4c02c6321207fd6601a1c89ebac4fe567"}, + {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b6db2185db9be0a04fecf2f241c70b63b1a242e2805be291855078f2b404dd6b"}, + {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f4be2e3d8bc8aabd566f8d5b8ba7ecc09249d74ba3c9ed52e54dc23a293f0b92"}, + {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c8d1634419f39ea6f5c427ea2f90ca85126b54b50837f31497f3bf38266e853d"}, + {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1a7fa382a4a223773ed64242dbe1c9c326ec09457e6b8428efb4118c685c3dfd"}, + {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:11847b53d722050808926e785df837353bd4d75f1d494377e59b23594d834967"}, + {file = "frozenlist-1.8.0-cp311-cp311-win32.whl", hash = "sha256:27c6e8077956cf73eadd514be8fb04d77fc946a7fe9f7fe167648b0b9085cc25"}, + {file = "frozenlist-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac913f8403b36a2c8610bbfd25b8013488533e71e62b4b4adce9c86c8cea905b"}, + {file = "frozenlist-1.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:d4d3214a0f8394edfa3e303136d0575eece0745ff2b47bd2cb2e66dd92d4351a"}, + {file = "frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1"}, + {file = "frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b"}, + {file = "frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4"}, + {file = "frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383"}, + {file = "frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4"}, + {file = "frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8"}, + {file = "frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b"}, + {file = "frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52"}, + {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29"}, + {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3"}, + {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143"}, + {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608"}, + {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa"}, + {file = "frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf"}, + {file = "frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746"}, + {file = "frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd"}, + {file = "frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a"}, + {file = "frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7"}, + {file = "frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40"}, + {file = "frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027"}, + {file = "frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822"}, + {file = "frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121"}, + {file = "frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5"}, + {file = "frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e"}, + {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11"}, + {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1"}, + {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1"}, + {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8"}, + {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed"}, + {file = "frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496"}, + {file = "frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231"}, + {file = "frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62"}, + {file = "frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94"}, + {file = "frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c"}, + {file = "frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52"}, + {file = "frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51"}, + {file = "frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65"}, + {file = "frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82"}, + {file = "frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714"}, + {file = "frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d"}, + {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506"}, + {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51"}, + {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e"}, + {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0"}, + {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41"}, + {file = "frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b"}, + {file = "frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888"}, + {file = "frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042"}, + {file = "frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0"}, + {file = "frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f"}, + {file = "frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c"}, + {file = "frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2"}, + {file = "frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8"}, + {file = "frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686"}, + {file = "frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e"}, + {file = "frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a"}, + {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128"}, + {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f"}, + {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7"}, + {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30"}, + {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7"}, + {file = "frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806"}, + {file = "frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0"}, + {file = "frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b"}, + {file = "frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d"}, + {file = "frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed"}, + {file = "frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930"}, + {file = "frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c"}, + {file = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24"}, + {file = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37"}, + {file = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a"}, + {file = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2"}, + {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef"}, + {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe"}, + {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8"}, + {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a"}, + {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e"}, + {file = "frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df"}, + {file = "frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd"}, + {file = "frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79"}, + {file = "frozenlist-1.8.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d8b7138e5cd0647e4523d6685b0eac5d4be9a184ae9634492f25c6eb38c12a47"}, + {file = "frozenlist-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a6483e309ca809f1efd154b4d37dc6d9f61037d6c6a81c2dc7a15cb22c8c5dca"}, + {file = "frozenlist-1.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1b9290cf81e95e93fdf90548ce9d3c1211cf574b8e3f4b3b7cb0537cf2227068"}, + {file = "frozenlist-1.8.0-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:59a6a5876ca59d1b63af8cd5e7ffffb024c3dc1e9cf9301b21a2e76286505c95"}, + {file = "frozenlist-1.8.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6dc4126390929823e2d2d9dc79ab4046ed74680360fc5f38b585c12c66cdf459"}, + {file = "frozenlist-1.8.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:332db6b2563333c5671fecacd085141b5800cb866be16d5e3eb15a2086476675"}, + {file = "frozenlist-1.8.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9ff15928d62a0b80bb875655c39bf517938c7d589554cbd2669be42d97c2cb61"}, + {file = "frozenlist-1.8.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7bf6cdf8e07c8151fba6fe85735441240ec7f619f935a5205953d58009aef8c6"}, + {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:48e6d3f4ec5c7273dfe83ff27c91083c6c9065af655dc2684d2c200c94308bb5"}, + {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:1a7607e17ad33361677adcd1443edf6f5da0ce5e5377b798fba20fae194825f3"}, + {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:5a3a935c3a4e89c733303a2d5a7c257ea44af3a56c8202df486b7f5de40f37e1"}, + {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:940d4a017dbfed9daf46a3b086e1d2167e7012ee297fef9e1c545c4d022f5178"}, + {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b9be22a69a014bc47e78072d0ecae716f5eb56c15238acca0f43d6eb8e4a5bda"}, + {file = "frozenlist-1.8.0-cp39-cp39-win32.whl", hash = "sha256:1aa77cb5697069af47472e39612976ed05343ff2e84a3dcf15437b232cbfd087"}, + {file = "frozenlist-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:7398c222d1d405e796970320036b1b563892b65809d9e5261487bb2c7f7b5c6a"}, + {file = "frozenlist-1.8.0-cp39-cp39-win_arm64.whl", hash = "sha256:b4f3b365f31c6cd4af24545ca0a244a53688cad8834e32f56831c4923b50a103"}, + {file = "frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d"}, + {file = "frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad"}, +] + +[[package]] +name = "fsspec" +version = "2025.9.0" +description = "File-system specification" +optional = false +python-versions = ">=3.9" +files = [ + {file = "fsspec-2025.9.0-py3-none-any.whl", hash = "sha256:530dc2a2af60a414a832059574df4a6e10cce927f6f4a78209390fe38955cfb7"}, + {file = "fsspec-2025.9.0.tar.gz", hash = "sha256:19fd429483d25d28b65ec68f9f4adc16c17ea2c7c7bf54ec61360d478fb19c19"}, +] + +[package.dependencies] +aiohttp = {version = "<4.0.0a0 || >4.0.0a0,<4.0.0a1 || >4.0.0a1", optional = true, markers = "extra == \"http\""} + +[package.extras] +abfs = ["adlfs"] +adl = ["adlfs"] +arrow = ["pyarrow (>=1)"] +dask = ["dask", "distributed"] +dev = ["pre-commit", "ruff (>=0.5)"] +doc = ["numpydoc", "sphinx", "sphinx-design", "sphinx-rtd-theme", "yarl"] +dropbox = ["dropbox", "dropboxdrivefs", "requests"] +full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "dask", "distributed", "dropbox", "dropboxdrivefs", "fusepy", "gcsfs", "libarchive-c", "ocifs", "panel", "paramiko", "pyarrow (>=1)", "pygit2", "requests", "s3fs", "smbprotocol", "tqdm"] +fuse = ["fusepy"] +gcs = ["gcsfs"] +git = ["pygit2"] +github = ["requests"] +gs = ["gcsfs"] +gui = ["panel"] +hdfs = ["pyarrow (>=1)"] +http = ["aiohttp (!=4.0.0a0,!=4.0.0a1)"] +libarchive = ["libarchive-c"] +oci = ["ocifs"] +s3 = ["s3fs"] +sftp = ["paramiko"] +smb = ["smbprotocol"] +ssh = ["paramiko"] +test = ["aiohttp (!=4.0.0a0,!=4.0.0a1)", "numpy", "pytest", "pytest-asyncio (!=0.22.0)", "pytest-benchmark", "pytest-cov", "pytest-mock", "pytest-recording", "pytest-rerunfailures", "requests"] +test-downstream = ["aiobotocore (>=2.5.4,<3.0.0)", "dask[dataframe,test]", "moto[server] (>4,<5)", "pytest-timeout", "xarray"] +test-full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "cloudpickle", "dask", "distributed", "dropbox", "dropboxdrivefs", "fastparquet", "fusepy", "gcsfs", "jinja2", "kerchunk", "libarchive-c", "lz4", "notebook", "numpy", "ocifs", "pandas", "panel", "paramiko", "pyarrow", "pyarrow (>=1)", "pyftpdlib", "pygit2", "pytest", "pytest-asyncio (!=0.22.0)", "pytest-benchmark", "pytest-cov", "pytest-mock", "pytest-recording", "pytest-rerunfailures", "python-snappy", "requests", "smbprotocol", "tqdm", "urllib3", "zarr", "zstandard"] +tqdm = ["tqdm"] + +[[package]] +name = "graphviz" +version = "0.21" +description = "Simple Python interface for Graphviz" +optional = false +python-versions = ">=3.9" +files = [ + {file = "graphviz-0.21-py3-none-any.whl", hash = "sha256:54f33de9f4f911d7e84e4191749cac8cc5653f815b06738c54db9a15ab8b1e42"}, + {file = "graphviz-0.21.tar.gz", hash = "sha256:20743e7183be82aaaa8ad6c93f8893c923bd6658a04c32ee115edb3c8a835f78"}, +] + +[package.extras] +dev = ["Flake8-pyproject", "build", "flake8", "pep8-naming", "tox (>=3)", "twine", "wheel"] +docs = ["sphinx (>=5,<7)", "sphinx-autodoc-typehints", "sphinx-rtd-theme (>=0.2.5)"] +test = ["coverage", "pytest (>=7,<8.1)", "pytest-cov", "pytest-mock (>=3)"] + [[package]] name = "h11" version = "0.16.0" @@ -927,6 +1336,73 @@ files = [ [package.dependencies] arrow = ">=0.15.0" +[[package]] +name = "jax" +version = "0.8.0" +description = "Differentiate, compile, and transform Numpy code." +optional = false +python-versions = ">=3.11" +files = [ + {file = "jax-0.8.0-py3-none-any.whl", hash = "sha256:d190158bc019756c6a0f6b3d5fc8783471fb407e6deaff559eaac60dd5ee850a"}, + {file = "jax-0.8.0.tar.gz", hash = "sha256:0ea5a7be7068c25934450dfd87d7d80a18a5d30e0a53454e7aade525b23accd5"}, +] + +[package.dependencies] +jaxlib = "0.8.0" +ml_dtypes = ">=0.5.0" +numpy = ">=2.0" +opt_einsum = "*" +scipy = ">=1.13" + +[package.extras] +ci = ["jaxlib (==0.7.2)"] +cuda = ["jax-cuda12-plugin[with-cuda] (==0.8.0)", "jaxlib (==0.8.0)"] +cuda12 = ["jax-cuda12-plugin[with-cuda] (==0.8.0)", "jaxlib (==0.8.0)"] +cuda12-local = ["jax-cuda12-plugin (==0.8.0)", "jaxlib (==0.8.0)"] +cuda13 = ["jax-cuda13-plugin[with-cuda] (==0.8.0)", "jaxlib (==0.8.0)"] +cuda13-local = ["jax-cuda13-plugin (==0.8.0)", "jaxlib (==0.8.0)"] +k8s = ["kubernetes"] +minimum-jaxlib = ["jaxlib (==0.8.0)"] +rocm = ["jax-rocm60-plugin (==0.8.0)", "jaxlib (==0.8.0)"] +tpu = ["jaxlib (==0.8.0)", "libtpu (==0.0.24.*)", "requests"] +xprof = ["xprof"] + +[[package]] +name = "jaxlib" +version = "0.8.0" +description = "XLA library for JAX" +optional = false +python-versions = ">=3.11" +files = [ + {file = "jaxlib-0.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bb602a8c24c614cb8ca6eeed3e70a733d9399c6a2f88900a0252623cd67276b5"}, + {file = "jaxlib-0.8.0-cp311-cp311-manylinux_2_27_aarch64.whl", hash = "sha256:41aebddef67a555a6de17427a4e66ce60a528a815847e2dd96dabce579f7acf8"}, + {file = "jaxlib-0.8.0-cp311-cp311-manylinux_2_27_x86_64.whl", hash = "sha256:ff53e8baf978f6b7c4076215af78f0ba969cac434ed2f72565d87e38c23f00e7"}, + {file = "jaxlib-0.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:9cd4c7a8acc5b3dee4ad28a5d101264d89754e29553b0cdb92c79f5b460a511b"}, + {file = "jaxlib-0.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f60aac0f64e9e70a5cef341fe292684518695514c71ad00036774bbed5f7312e"}, + {file = "jaxlib-0.8.0-cp312-cp312-manylinux_2_27_aarch64.whl", hash = "sha256:d83ff8cf1b070299639cda4f8427707f69051dc8421e59fbb73305523937570d"}, + {file = "jaxlib-0.8.0-cp312-cp312-manylinux_2_27_x86_64.whl", hash = "sha256:2c8675bf86e391afe4f8d863080be1a024d734dfd3dd137f7aa8e7f22091adcd"}, + {file = "jaxlib-0.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:659d894d93876e3675c2132d13c3d241f204b21172a58f928b96f654f603f6dc"}, + {file = "jaxlib-0.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5fcf33a5639f8f164a473a9c78a1fa0b2e15ac3fcbecd6d96aa0f88bf25ea6bb"}, + {file = "jaxlib-0.8.0-cp313-cp313-manylinux_2_27_aarch64.whl", hash = "sha256:b3eac503b90ffecc68f11fa122133eef2c62c536db28e801e436d7e7a9b67bf8"}, + {file = "jaxlib-0.8.0-cp313-cp313-manylinux_2_27_x86_64.whl", hash = "sha256:66c6f576f54a63ed052f5c469bef4db723f5f050b839ec0c429573011341bd58"}, + {file = "jaxlib-0.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:72759ebbfb40a717349f174712207d306aa28630359f05cd69b091bd4efa0603"}, + {file = "jaxlib-0.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:df2781e0fc93fb6f42111b385b90126b9571eafe0e860f033615ff7156b76817"}, + {file = "jaxlib-0.8.0-cp313-cp313t-manylinux_2_27_aarch64.whl", hash = "sha256:7eb3be931de77bfcde27df659ada432719aa1e19a2fa5b835638e7404c74cb63"}, + {file = "jaxlib-0.8.0-cp313-cp313t-manylinux_2_27_x86_64.whl", hash = "sha256:accebe89a36e28306a4db3f68f527a0f87b8a0fd253b3c1556fbd24f16bec22c"}, + {file = "jaxlib-0.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ba7e8a2231e4138ccbd8e096debdbbcd82edc5fc1b13c66f32a51bc240651349"}, + {file = "jaxlib-0.8.0-cp314-cp314-manylinux_2_27_aarch64.whl", hash = "sha256:a9bfca27ae597804db08694a2bf7e1cf7fc3fac4ac2e65ace83be8effaa927ea"}, + {file = "jaxlib-0.8.0-cp314-cp314-manylinux_2_27_x86_64.whl", hash = "sha256:bd3219a4d2bfe4b72605900fde395b62126a053c0b99643eb931b7c20e577bf2"}, + {file = "jaxlib-0.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3320a72d532713c2a31eb20d02c342540a0dec28603a3ac2be0fc0631f086cf2"}, + {file = "jaxlib-0.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:248f1ac3acee1fe2cc81e8a668311f3ccb8f28090404391c276869cae8a95daf"}, + {file = "jaxlib-0.8.0-cp314-cp314t-manylinux_2_27_aarch64.whl", hash = "sha256:a5f0656bbbb3f135a360ce0fde55bf34faf73fbc62ab887941e85f0014b3f476"}, + {file = "jaxlib-0.8.0-cp314-cp314t-manylinux_2_27_x86_64.whl", hash = "sha256:61cb2fde154e5a399db2880d560e3443cfa97bda9f074b545c886232ac8fe024"}, +] + +[package.dependencies] +ml_dtypes = ">=0.5.0" +numpy = ">=2.0" +scipy = ">=1.13" + [[package]] name = "jedi" version = "0.19.2" @@ -1430,6 +1906,94 @@ interegular = ["interegular (>=0.3.1,<0.4.0)"] nearley = ["js2py"] regex = ["regex"] +[[package]] +name = "lightgbm" +version = "4.6.0" +description = "LightGBM Python-package" +optional = false +python-versions = ">=3.7" +files = [ + {file = "lightgbm-4.6.0-py3-none-macosx_10_15_x86_64.whl", hash = "sha256:b7a393de8a334d5c8e490df91270f0763f83f959574d504c7ccb9eee4aef70ed"}, + {file = "lightgbm-4.6.0-py3-none-macosx_12_0_arm64.whl", hash = "sha256:2dafd98d4e02b844ceb0b61450a660681076b1ea6c7adb8c566dfd66832aafad"}, + {file = "lightgbm-4.6.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:4d68712bbd2b57a0b14390cbf9376c1d5ed773fa2e71e099cac588703b590336"}, + {file = "lightgbm-4.6.0-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:cb19b5afea55b5b61cbb2131095f50538bd608a00655f23ad5d25ae3e3bf1c8d"}, + {file = "lightgbm-4.6.0-py3-none-win_amd64.whl", hash = "sha256:37089ee95664b6550a7189d887dbf098e3eadab03537e411f52c63c121e3ba4b"}, + {file = "lightgbm-4.6.0.tar.gz", hash = "sha256:cb1c59720eb569389c0ba74d14f52351b573af489f230032a1c9f314f8bab7fe"}, +] + +[package.dependencies] +numpy = ">=1.17.0" +scipy = "*" + +[package.extras] +arrow = ["cffi (>=1.15.1)", "pyarrow (>=6.0.1)"] +dask = ["dask[array,dataframe,distributed] (>=2.0.0)", "pandas (>=0.24.0)"] +pandas = ["pandas (>=0.24.0)"] +scikit-learn = ["scikit-learn (>=0.24.2)"] + +[[package]] +name = "lightning" +version = "2.5.5" +description = "The Deep Learning framework to train, deploy, and ship AI products Lightning fast." +optional = false +python-versions = ">=3.9" +files = [ + {file = "lightning-2.5.5-py3-none-any.whl", hash = "sha256:69eb248beadd7b600bf48eff00a0ec8af171ec7a678d23787c4aedf12e225e8f"}, + {file = "lightning-2.5.5.tar.gz", hash = "sha256:4d3d66c5b1481364a7e6a1ce8ddde1777a04fa740a3145ec218a9941aed7dd30"}, +] + +[package.dependencies] +fsspec = {version = ">=2022.5.0,<2027.0", extras = ["http"]} +lightning-utilities = ">=0.10.0,<2.0" +packaging = ">=20.0,<27.0" +pytorch-lightning = "*" +PyYAML = ">5.4,<8.0" +torch = ">=2.1.0,<4.0" +torchmetrics = ">0.7.0,<3.0" +tqdm = ">=4.57.0,<6.0" +typing-extensions = ">4.5.0,<6.0" + +[package.extras] +all = ["bitsandbytes (>=0.45.2,<1.0)", "deepspeed (>=0.14.1,<=0.15.0)", "hydra-core (>=1.2.0,<2.0)", "ipython[all] (>=8.0.0,<10.0)", "jsonargparse[jsonnet,signatures] (>=4.39.0,<5.0)", "matplotlib (>3.1,<4.0)", "omegaconf (>=2.2.3,<3.0)", "requests (<3.0)", "rich (>=12.3.0,<15.0)", "tensorboardX (>=2.2,<3.0)", "torchmetrics (>=0.10.0,<2.0)", "torchvision (>=0.16.0,<1.0)"] +data = ["litdata (>=0.2.0rc,<1.0)"] +dev = ["bitsandbytes (>=0.45.2,<1.0)", "click (==8.1.8)", "click (==8.2.1)", "cloudpickle (>=1.3,<4.0)", "coverage (==7.10.6)", "deepspeed (>=0.14.1,<=0.15.0)", "fastapi", "hydra-core (>=1.2.0,<2.0)", "ipython[all] (>=8.0.0,<10.0)", "jsonargparse[jsonnet,signatures] (>=4.39.0,<5.0)", "matplotlib (>3.1,<4.0)", "numpy (>1.20.0,<2.0)", "numpy (>=1.21.0,<2.0)", "omegaconf (>=2.2.3,<3.0)", "onnx (>1.12.0,<2.0)", "onnxruntime (>=1.12.0,<2.0)", "onnxscript (>=0.1.0,<1.0)", "pandas (>2.0,<3.0)", "psutil (<8.0)", "pytest (==8.4.1)", "pytest-cov (==6.2.1)", "pytest-random-order (==1.2.0)", "pytest-rerunfailures (==16.0)", "pytest-timeout (==2.4.0)", "requests (<3.0)", "rich (>=12.3.0,<15.0)", "scikit-learn (>0.22.1,<2.0)", "tensorboard (>=2.11,<3.0)", "tensorboardX (>=2.2,<3.0)", "tensorboardX (>=2.6,<3.0)", "torchmetrics (>=0.10.0,<2.0)", "torchvision (>=0.16.0,<1.0)", "uvicorn"] +examples = ["ipython[all] (>=8.0.0,<10.0)", "requests (<3.0)", "torchmetrics (>=0.10.0,<2.0)", "torchvision (>=0.16.0,<1.0)"] +extra = ["bitsandbytes (>=0.45.2,<1.0)", "hydra-core (>=1.2.0,<2.0)", "jsonargparse[jsonnet,signatures] (>=4.39.0,<5.0)", "matplotlib (>3.1,<4.0)", "omegaconf (>=2.2.3,<3.0)", "rich (>=12.3.0,<15.0)", "tensorboardX (>=2.2,<3.0)"] +fabric-all = ["bitsandbytes (>=0.45.2,<1.0)", "deepspeed (>=0.14.1,<=0.15.0)", "torchmetrics (>=0.10.0,<2.0)", "torchvision (>=0.16.0,<1.0)"] +fabric-dev = ["bitsandbytes (>=0.45.2,<1.0)", "click (==8.1.8)", "click (==8.2.1)", "coverage (==7.10.6)", "deepspeed (>=0.14.1,<=0.15.0)", "numpy (>=1.21.0,<2.0)", "pytest (==8.4.1)", "pytest-cov (==6.2.1)", "pytest-random-order (==1.2.0)", "pytest-rerunfailures (==16.0)", "pytest-timeout (==2.4.0)", "tensorboardX (>=2.6,<3.0)", "torchmetrics (>=0.10.0,<2.0)", "torchvision (>=0.16.0,<1.0)"] +fabric-examples = ["torchmetrics (>=0.10.0,<2.0)", "torchvision (>=0.16.0,<1.0)"] +fabric-strategies = ["bitsandbytes (>=0.45.2,<1.0)", "deepspeed (>=0.14.1,<=0.15.0)"] +fabric-test = ["click (==8.1.8)", "click (==8.2.1)", "coverage (==7.10.6)", "numpy (>=1.21.0,<2.0)", "pytest (==8.4.1)", "pytest-cov (==6.2.1)", "pytest-random-order (==1.2.0)", "pytest-rerunfailures (==16.0)", "pytest-timeout (==2.4.0)", "tensorboardX (>=2.6,<3.0)"] +pytorch-all = ["bitsandbytes (>=0.45.2,<1.0)", "deepspeed (>=0.14.1,<=0.15.0)", "hydra-core (>=1.2.0,<2.0)", "ipython[all] (>=8.0.0,<10.0)", "jsonargparse[jsonnet,signatures] (>=4.39.0,<5.0)", "matplotlib (>3.1,<4.0)", "omegaconf (>=2.2.3,<3.0)", "requests (<3.0)", "rich (>=12.3.0,<15.0)", "tensorboardX (>=2.2,<3.0)", "torchmetrics (>=0.10.0,<2.0)", "torchvision (>=0.16.0,<1.0)"] +pytorch-dev = ["bitsandbytes (>=0.45.2,<1.0)", "cloudpickle (>=1.3,<4.0)", "coverage (==7.10.6)", "deepspeed (>=0.14.1,<=0.15.0)", "fastapi", "hydra-core (>=1.2.0,<2.0)", "ipython[all] (>=8.0.0,<10.0)", "jsonargparse[jsonnet,signatures] (>=4.39.0,<5.0)", "matplotlib (>3.1,<4.0)", "numpy (>1.20.0,<2.0)", "omegaconf (>=2.2.3,<3.0)", "onnx (>1.12.0,<2.0)", "onnxruntime (>=1.12.0,<2.0)", "onnxscript (>=0.1.0,<1.0)", "pandas (>2.0,<3.0)", "psutil (<8.0)", "pytest (==8.4.1)", "pytest-cov (==6.2.1)", "pytest-random-order (==1.2.0)", "pytest-rerunfailures (==16.0)", "pytest-timeout (==2.4.0)", "requests (<3.0)", "rich (>=12.3.0,<15.0)", "scikit-learn (>0.22.1,<2.0)", "tensorboard (>=2.11,<3.0)", "tensorboardX (>=2.2,<3.0)", "torchmetrics (>=0.10.0,<2.0)", "torchvision (>=0.16.0,<1.0)", "uvicorn"] +pytorch-examples = ["ipython[all] (>=8.0.0,<10.0)", "requests (<3.0)", "torchmetrics (>=0.10.0,<2.0)", "torchvision (>=0.16.0,<1.0)"] +pytorch-extra = ["bitsandbytes (>=0.45.2,<1.0)", "hydra-core (>=1.2.0,<2.0)", "jsonargparse[jsonnet,signatures] (>=4.39.0,<5.0)", "matplotlib (>3.1,<4.0)", "omegaconf (>=2.2.3,<3.0)", "rich (>=12.3.0,<15.0)", "tensorboardX (>=2.2,<3.0)"] +pytorch-strategies = ["deepspeed (>=0.14.1,<=0.15.0)"] +pytorch-test = ["cloudpickle (>=1.3,<4.0)", "coverage (==7.10.6)", "fastapi", "numpy (>1.20.0,<2.0)", "onnx (>1.12.0,<2.0)", "onnxruntime (>=1.12.0,<2.0)", "onnxscript (>=0.1.0,<1.0)", "pandas (>2.0,<3.0)", "psutil (<8.0)", "pytest (==8.4.1)", "pytest-cov (==6.2.1)", "pytest-random-order (==1.2.0)", "pytest-rerunfailures (==16.0)", "pytest-timeout (==2.4.0)", "scikit-learn (>0.22.1,<2.0)", "tensorboard (>=2.11,<3.0)", "uvicorn"] +strategies = ["bitsandbytes (>=0.45.2,<1.0)", "deepspeed (>=0.14.1,<=0.15.0)"] +test = ["click (==8.1.8)", "click (==8.2.1)", "cloudpickle (>=1.3,<4.0)", "coverage (==7.10.6)", "fastapi", "numpy (>1.20.0,<2.0)", "numpy (>=1.21.0,<2.0)", "onnx (>1.12.0,<2.0)", "onnxruntime (>=1.12.0,<2.0)", "onnxscript (>=0.1.0,<1.0)", "pandas (>2.0,<3.0)", "psutil (<8.0)", "pytest (==8.4.1)", "pytest-cov (==6.2.1)", "pytest-random-order (==1.2.0)", "pytest-rerunfailures (==16.0)", "pytest-timeout (==2.4.0)", "scikit-learn (>0.22.1,<2.0)", "tensorboard (>=2.11,<3.0)", "tensorboardX (>=2.6,<3.0)", "uvicorn"] + +[[package]] +name = "lightning-utilities" +version = "0.15.2" +description = "Lightning toolbox for across the our ecosystem." +optional = false +python-versions = ">=3.9" +files = [ + {file = "lightning_utilities-0.15.2-py3-none-any.whl", hash = "sha256:ad3ab1703775044bbf880dbf7ddaaac899396c96315f3aa1779cec9d618a9841"}, + {file = "lightning_utilities-0.15.2.tar.gz", hash = "sha256:cdf12f530214a63dacefd713f180d1ecf5d165338101617b4742e8f22c032e24"}, +] + +[package.dependencies] +packaging = ">=17.1" +setuptools = "*" +typing_extensions = "*" + +[package.extras] +cli = ["jsonargparse[signatures] (>=4.38.0)", "tomlkit"] +docs = ["requests (>=2.0.0)"] +typing = ["mypy (>=1.0.0)", "types-setuptools"] + [[package]] name = "markupsafe" version = "3.0.3" @@ -1631,6 +2195,239 @@ files = [ {file = "mistune-3.1.4.tar.gz", hash = "sha256:b5a7f801d389f724ec702840c11d8fc48f2b33519102fc7ee739e8177b672164"}, ] +[[package]] +name = "ml-dtypes" +version = "0.5.3" +description = "ml_dtypes is a stand-alone implementation of several NumPy dtype extensions used in machine learning." +optional = false +python-versions = ">=3.9" +files = [ + {file = "ml_dtypes-0.5.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0a1d68a7cb53e3f640b2b6a34d12c0542da3dd935e560fdf463c0c77f339fc20"}, + {file = "ml_dtypes-0.5.3-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cd5a6c711b5350f3cbc2ac28def81cd1c580075ccb7955e61e9d8f4bfd40d24"}, + {file = "ml_dtypes-0.5.3-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bdcf26c2dbc926b8a35ec8cbfad7eff1a8bd8239e12478caca83a1fc2c400dc2"}, + {file = "ml_dtypes-0.5.3-cp310-cp310-win_amd64.whl", hash = "sha256:aecbd7c5272c82e54d5b99d8435fd10915d1bc704b7df15e4d9ca8dc3902be61"}, + {file = "ml_dtypes-0.5.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4a177b882667c69422402df6ed5c3428ce07ac2c1f844d8a1314944651439458"}, + {file = "ml_dtypes-0.5.3-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9849ce7267444c0a717c80c6900997de4f36e2815ce34ac560a3edb2d9a64cd2"}, + {file = "ml_dtypes-0.5.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c3f5ae0309d9f888fd825c2e9d0241102fadaca81d888f26f845bc8c13c1e4ee"}, + {file = "ml_dtypes-0.5.3-cp311-cp311-win_amd64.whl", hash = "sha256:58e39349d820b5702bb6f94ea0cb2dc8ec62ee81c0267d9622067d8333596a46"}, + {file = "ml_dtypes-0.5.3-cp311-cp311-win_arm64.whl", hash = "sha256:66c2756ae6cfd7f5224e355c893cfd617fa2f747b8bbd8996152cbdebad9a184"}, + {file = "ml_dtypes-0.5.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:156418abeeda48ea4797db6776db3c5bdab9ac7be197c1233771e0880c304057"}, + {file = "ml_dtypes-0.5.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1db60c154989af253f6c4a34e8a540c2c9dce4d770784d426945e09908fbb177"}, + {file = "ml_dtypes-0.5.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1b255acada256d1fa8c35ed07b5f6d18bc21d1556f842fbc2d5718aea2cd9e55"}, + {file = "ml_dtypes-0.5.3-cp312-cp312-win_amd64.whl", hash = "sha256:da65e5fd3eea434ccb8984c3624bc234ddcc0d9f4c81864af611aaebcc08a50e"}, + {file = "ml_dtypes-0.5.3-cp312-cp312-win_arm64.whl", hash = "sha256:8bb9cd1ce63096567f5f42851f5843b5a0ea11511e50039a7649619abfb4ba6d"}, + {file = "ml_dtypes-0.5.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5103856a225465371fe119f2fef737402b705b810bd95ad5f348e6e1a6ae21af"}, + {file = "ml_dtypes-0.5.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cae435a68861660af81fa3c5af16b70ca11a17275c5b662d9c6f58294e0f113"}, + {file = "ml_dtypes-0.5.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6936283b56d74fbec431ca57ce58a90a908fdbd14d4e2d22eea6d72bb208a7b7"}, + {file = "ml_dtypes-0.5.3-cp313-cp313-win_amd64.whl", hash = "sha256:d0f730a17cf4f343b2c7ad50cee3bd19e969e793d2be6ed911f43086460096e4"}, + {file = "ml_dtypes-0.5.3-cp313-cp313-win_arm64.whl", hash = "sha256:2db74788fc01914a3c7f7da0763427280adfc9cd377e9604b6b64eb8097284bd"}, + {file = "ml_dtypes-0.5.3-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:93c36a08a6d158db44f2eb9ce3258e53f24a9a4a695325a689494f0fdbc71770"}, + {file = "ml_dtypes-0.5.3-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0e44a3761f64bc009d71ddb6d6c71008ba21b53ab6ee588dadab65e2fa79eafc"}, + {file = "ml_dtypes-0.5.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bdf40d2aaabd3913dec11840f0d0ebb1b93134f99af6a0a4fd88ffe924928ab4"}, + {file = "ml_dtypes-0.5.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:aec640bd94c4c85c0d11e2733bd13cbb10438fb004852996ec0efbc6cacdaf70"}, + {file = "ml_dtypes-0.5.3-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bda32ce212baa724e03c68771e5c69f39e584ea426bfe1a701cb01508ffc7035"}, + {file = "ml_dtypes-0.5.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c205cac07d24a29840c163d6469f61069ce4b065518519216297fc2f261f8db9"}, + {file = "ml_dtypes-0.5.3-cp314-cp314-win_amd64.whl", hash = "sha256:cd7c0bb22d4ff86d65ad61b5dd246812e8993fbc95b558553624c33e8b6903ea"}, + {file = "ml_dtypes-0.5.3-cp314-cp314-win_arm64.whl", hash = "sha256:9d55ea7f7baf2aed61bf1872116cefc9d0c3693b45cae3916897ee27ef4b835e"}, + {file = "ml_dtypes-0.5.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:e12e29764a0e66a7a31e9b8bf1de5cc0423ea72979f45909acd4292de834ccd3"}, + {file = "ml_dtypes-0.5.3-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:19f6c3a4f635c2fc9e2aa7d91416bd7a3d649b48350c51f7f715a09370a90d93"}, + {file = "ml_dtypes-0.5.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ab039ffb40f3dc0aeeeba84fd6c3452781b5e15bef72e2d10bcb33e4bbffc39"}, + {file = "ml_dtypes-0.5.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5ee72568d46b9533ad54f78b1e1f3067c0534c5065120ea8ecc6f210d22748b3"}, + {file = "ml_dtypes-0.5.3-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:01de48de4537dc3c46e684b969a40ec36594e7eeb7c69e9a093e7239f030a28a"}, + {file = "ml_dtypes-0.5.3-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8b1a6e231b0770f2894910f1dce6d2f31d65884dbf7668f9b08d73623cdca909"}, + {file = "ml_dtypes-0.5.3-cp39-cp39-win_amd64.whl", hash = "sha256:a4f39b9bf6555fab9bfb536cf5fdd1c1c727e8d22312078702e9ff005354b37f"}, + {file = "ml_dtypes-0.5.3.tar.gz", hash = "sha256:95ce33057ba4d05df50b1f3cfefab22e351868a843b3b15a46c65836283670c9"}, +] + +[package.dependencies] +numpy = {version = ">=1.23.3", markers = "python_version >= \"3.11\" and python_version < \"3.12\""} + +[package.extras] +dev = ["absl-py", "pyink", "pylint (>=2.6.0)", "pytest", "pytest-xdist"] + +[[package]] +name = "mpmath" +version = "1.3.0" +description = "Python library for arbitrary-precision floating-point arithmetic" +optional = false +python-versions = "*" +files = [ + {file = "mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c"}, + {file = "mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f"}, +] + +[package.extras] +develop = ["codecov", "pycodestyle", "pytest (>=4.6)", "pytest-cov", "wheel"] +docs = ["sphinx"] +gmpy = ["gmpy2 (>=2.1.0a4)"] +tests = ["pytest (>=4.6)"] + +[[package]] +name = "multidict" +version = "6.7.0" +description = "multidict implementation" +optional = false +python-versions = ">=3.9" +files = [ + {file = "multidict-6.7.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9f474ad5acda359c8758c8accc22032c6abe6dc87a8be2440d097785e27a9349"}, + {file = "multidict-6.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4b7a9db5a870f780220e931d0002bbfd88fb53aceb6293251e2c839415c1b20e"}, + {file = "multidict-6.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:03ca744319864e92721195fa28c7a3b2bc7b686246b35e4078c1e4d0eb5466d3"}, + {file = "multidict-6.7.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f0e77e3c0008bc9316e662624535b88d360c3a5d3f81e15cf12c139a75250046"}, + {file = "multidict-6.7.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08325c9e5367aa379a3496aa9a022fe8837ff22e00b94db256d3a1378c76ab32"}, + {file = "multidict-6.7.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e2862408c99f84aa571ab462d25236ef9cb12a602ea959ba9c9009a54902fc73"}, + {file = "multidict-6.7.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4d72a9a2d885f5c208b0cb91ff2ed43636bb7e345ec839ff64708e04f69a13cc"}, + {file = "multidict-6.7.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:478cc36476687bac1514d651cbbaa94b86b0732fb6855c60c673794c7dd2da62"}, + {file = "multidict-6.7.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6843b28b0364dc605f21481c90fadb5f60d9123b442eb8a726bb74feef588a84"}, + {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:23bfeee5316266e5ee2d625df2d2c602b829435fc3a235c2ba2131495706e4a0"}, + {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:680878b9f3d45c31e1f730eef731f9b0bc1da456155688c6745ee84eb818e90e"}, + {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:eb866162ef2f45063acc7a53a88ef6fe8bf121d45c30ea3c9cd87ce7e191a8d4"}, + {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:df0e3bf7993bdbeca5ac25aa859cf40d39019e015c9c91809ba7093967f7a648"}, + {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:661709cdcd919a2ece2234f9bae7174e5220c80b034585d7d8a755632d3e2111"}, + {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:096f52730c3fb8ed419db2d44391932b63891b2c5ed14850a7e215c0ba9ade36"}, + {file = "multidict-6.7.0-cp310-cp310-win32.whl", hash = "sha256:afa8a2978ec65d2336305550535c9c4ff50ee527914328c8677b3973ade52b85"}, + {file = "multidict-6.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:b15b3afff74f707b9275d5ba6a91ae8f6429c3ffb29bbfd216b0b375a56f13d7"}, + {file = "multidict-6.7.0-cp310-cp310-win_arm64.whl", hash = "sha256:4b73189894398d59131a66ff157837b1fafea9974be486d036bb3d32331fdbf0"}, + {file = "multidict-6.7.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4d409aa42a94c0b3fa617708ef5276dfe81012ba6753a0370fcc9d0195d0a1fc"}, + {file = "multidict-6.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:14c9e076eede3b54c636f8ce1c9c252b5f057c62131211f0ceeec273810c9721"}, + {file = "multidict-6.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c09703000a9d0fa3c3404b27041e574cc7f4df4c6563873246d0e11812a94b6"}, + {file = "multidict-6.7.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a265acbb7bb33a3a2d626afbe756371dce0279e7b17f4f4eda406459c2b5ff1c"}, + {file = "multidict-6.7.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51cb455de290ae462593e5b1cb1118c5c22ea7f0d3620d9940bf695cea5a4bd7"}, + {file = "multidict-6.7.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:db99677b4457c7a5c5a949353e125ba72d62b35f74e26da141530fbb012218a7"}, + {file = "multidict-6.7.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f470f68adc395e0183b92a2f4689264d1ea4b40504a24d9882c27375e6662bb9"}, + {file = "multidict-6.7.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0db4956f82723cc1c270de9c6e799b4c341d327762ec78ef82bb962f79cc07d8"}, + {file = "multidict-6.7.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3e56d780c238f9e1ae66a22d2adf8d16f485381878250db8d496623cd38b22bd"}, + {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9d14baca2ee12c1a64740d4531356ba50b82543017f3ad6de0deb943c5979abb"}, + {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:295a92a76188917c7f99cda95858c822f9e4aae5824246bba9b6b44004ddd0a6"}, + {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:39f1719f57adbb767ef592a50ae5ebb794220d1188f9ca93de471336401c34d2"}, + {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:0a13fb8e748dfc94749f622de065dd5c1def7e0d2216dba72b1d8069a389c6ff"}, + {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e3aa16de190d29a0ea1b48253c57d99a68492c8dd8948638073ab9e74dc9410b"}, + {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a048ce45dcdaaf1defb76b2e684f997fb5abf74437b6cb7b22ddad934a964e34"}, + {file = "multidict-6.7.0-cp311-cp311-win32.whl", hash = "sha256:a90af66facec4cebe4181b9e62a68be65e45ac9b52b67de9eec118701856e7ff"}, + {file = "multidict-6.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:95b5ffa4349df2887518bb839409bcf22caa72d82beec453216802f475b23c81"}, + {file = "multidict-6.7.0-cp311-cp311-win_arm64.whl", hash = "sha256:329aa225b085b6f004a4955271a7ba9f1087e39dcb7e65f6284a988264a63912"}, + {file = "multidict-6.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8a3862568a36d26e650a19bb5cbbba14b71789032aebc0423f8cc5f150730184"}, + {file = "multidict-6.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:960c60b5849b9b4f9dcc9bea6e3626143c252c74113df2c1540aebce70209b45"}, + {file = "multidict-6.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2049be98fb57a31b4ccf870bf377af2504d4ae35646a19037ec271e4c07998aa"}, + {file = "multidict-6.7.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0934f3843a1860dd465d38895c17fce1f1cb37295149ab05cd1b9a03afacb2a7"}, + {file = "multidict-6.7.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b3e34f3a1b8131ba06f1a73adab24f30934d148afcd5f5de9a73565a4404384e"}, + {file = "multidict-6.7.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:efbb54e98446892590dc2458c19c10344ee9a883a79b5cec4bc34d6656e8d546"}, + {file = "multidict-6.7.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a35c5fc61d4f51eb045061e7967cfe3123d622cd500e8868e7c0c592a09fedc4"}, + {file = "multidict-6.7.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29fe6740ebccba4175af1b9b87bf553e9c15cd5868ee967e010efcf94e4fd0f1"}, + {file = "multidict-6.7.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:123e2a72e20537add2f33a79e605f6191fba2afda4cbb876e35c1a7074298a7d"}, + {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b284e319754366c1aee2267a2036248b24eeb17ecd5dc16022095e747f2f4304"}, + {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:803d685de7be4303b5a657b76e2f6d1240e7e0a8aa2968ad5811fa2285553a12"}, + {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c04a328260dfd5db8c39538f999f02779012268f54614902d0afc775d44e0a62"}, + {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8a19cdb57cd3df4cd865849d93ee14920fb97224300c88501f16ecfa2604b4e0"}, + {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b2fd74c52accced7e75de26023b7dccee62511a600e62311b918ec5c168fc2a"}, + {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3e8bfdd0e487acf992407a140d2589fe598238eaeffa3da8448d63a63cd363f8"}, + {file = "multidict-6.7.0-cp312-cp312-win32.whl", hash = "sha256:dd32a49400a2c3d52088e120ee00c1e3576cbff7e10b98467962c74fdb762ed4"}, + {file = "multidict-6.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:92abb658ef2d7ef22ac9f8bb88e8b6c3e571671534e029359b6d9e845923eb1b"}, + {file = "multidict-6.7.0-cp312-cp312-win_arm64.whl", hash = "sha256:490dab541a6a642ce1a9d61a4781656b346a55c13038f0b1244653828e3a83ec"}, + {file = "multidict-6.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bee7c0588aa0076ce77c0ea5d19a68d76ad81fcd9fe8501003b9a24f9d4000f6"}, + {file = "multidict-6.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7ef6b61cad77091056ce0e7ce69814ef72afacb150b7ac6a3e9470def2198159"}, + {file = "multidict-6.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9c0359b1ec12b1d6849c59f9d319610b7f20ef990a6d454ab151aa0e3b9f78ca"}, + {file = "multidict-6.7.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cd240939f71c64bd658f186330603aac1a9a81bf6273f523fca63673cb7378a8"}, + {file = "multidict-6.7.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a60a4d75718a5efa473ebd5ab685786ba0c67b8381f781d1be14da49f1a2dc60"}, + {file = "multidict-6.7.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53a42d364f323275126aff81fb67c5ca1b7a04fda0546245730a55c8c5f24bc4"}, + {file = "multidict-6.7.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3b29b980d0ddbecb736735ee5bef69bb2ddca56eff603c86f3f29a1128299b4f"}, + {file = "multidict-6.7.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f8a93b1c0ed2d04b97a5e9336fd2d33371b9a6e29ab7dd6503d63407c20ffbaf"}, + {file = "multidict-6.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ff96e8815eecacc6645da76c413eb3b3d34cfca256c70b16b286a687d013c32"}, + {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7516c579652f6a6be0e266aec0acd0db80829ca305c3d771ed898538804c2036"}, + {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:040f393368e63fb0f3330e70c26bfd336656bed925e5cbe17c9da839a6ab13ec"}, + {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b3bc26a951007b1057a1c543af845f1c7e3e71cc240ed1ace7bf4484aa99196e"}, + {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7b022717c748dd1992a83e219587aabe45980d88969f01b316e78683e6285f64"}, + {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:9600082733859f00d79dee64effc7aef1beb26adb297416a4ad2116fd61374bd"}, + {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:94218fcec4d72bc61df51c198d098ce2b378e0ccbac41ddbed5ef44092913288"}, + {file = "multidict-6.7.0-cp313-cp313-win32.whl", hash = "sha256:a37bd74c3fa9d00be2d7b8eca074dc56bd8077ddd2917a839bd989612671ed17"}, + {file = "multidict-6.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:30d193c6cc6d559db42b6bcec8a5d395d34d60c9877a0b71ecd7c204fcf15390"}, + {file = "multidict-6.7.0-cp313-cp313-win_arm64.whl", hash = "sha256:ea3334cabe4d41b7ccd01e4d349828678794edbc2d3ae97fc162a3312095092e"}, + {file = "multidict-6.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:ad9ce259f50abd98a1ca0aa6e490b58c316a0fce0617f609723e40804add2c00"}, + {file = "multidict-6.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07f5594ac6d084cbb5de2df218d78baf55ef150b91f0ff8a21cc7a2e3a5a58eb"}, + {file = "multidict-6.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0591b48acf279821a579282444814a2d8d0af624ae0bc600aa4d1b920b6e924b"}, + {file = "multidict-6.7.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:749a72584761531d2b9467cfbdfd29487ee21124c304c4b6cb760d8777b27f9c"}, + {file = "multidict-6.7.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b4c3d199f953acd5b446bf7c0de1fe25d94e09e79086f8dc2f48a11a129cdf1"}, + {file = "multidict-6.7.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9fb0211dfc3b51efea2f349ec92c114d7754dd62c01f81c3e32b765b70c45c9b"}, + {file = "multidict-6.7.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a027ec240fe73a8d6281872690b988eed307cd7d91b23998ff35ff577ca688b5"}, + {file = "multidict-6.7.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1d964afecdf3a8288789df2f5751dc0a8261138c3768d9af117ed384e538fad"}, + {file = "multidict-6.7.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:caf53b15b1b7df9fbd0709aa01409000a2b4dd03a5f6f5cc548183c7c8f8b63c"}, + {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:654030da3197d927f05a536a66186070e98765aa5142794c9904555d3a9d8fb5"}, + {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:2090d3718829d1e484706a2f525e50c892237b2bf9b17a79b059cb98cddc2f10"}, + {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2d2cfeec3f6f45651b3d408c4acec0ebf3daa9bc8a112a084206f5db5d05b754"}, + {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:4ef089f985b8c194d341eb2c24ae6e7408c9a0e2e5658699c92f497437d88c3c"}, + {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e93a0617cd16998784bf4414c7e40f17a35d2350e5c6f0bd900d3a8e02bd3762"}, + {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f0feece2ef8ebc42ed9e2e8c78fc4aa3cf455733b507c09ef7406364c94376c6"}, + {file = "multidict-6.7.0-cp313-cp313t-win32.whl", hash = "sha256:19a1d55338ec1be74ef62440ca9e04a2f001a04d0cc49a4983dc320ff0f3212d"}, + {file = "multidict-6.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3da4fb467498df97e986af166b12d01f05d2e04f978a9c1c680ea1988e0bc4b6"}, + {file = "multidict-6.7.0-cp313-cp313t-win_arm64.whl", hash = "sha256:b4121773c49a0776461f4a904cdf6264c88e42218aaa8407e803ca8025872792"}, + {file = "multidict-6.7.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3bab1e4aff7adaa34410f93b1f8e57c4b36b9af0426a76003f441ee1d3c7e842"}, + {file = "multidict-6.7.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b8512bac933afc3e45fb2b18da8e59b78d4f408399a960339598374d4ae3b56b"}, + {file = "multidict-6.7.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:79dcf9e477bc65414ebfea98ffd013cb39552b5ecd62908752e0e413d6d06e38"}, + {file = "multidict-6.7.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:31bae522710064b5cbeddaf2e9f32b1abab70ac6ac91d42572502299e9953128"}, + {file = "multidict-6.7.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a0df7ff02397bb63e2fd22af2c87dfa39e8c7f12947bc524dbdc528282c7e34"}, + {file = "multidict-6.7.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7a0222514e8e4c514660e182d5156a415c13ef0aabbd71682fc714e327b95e99"}, + {file = "multidict-6.7.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2397ab4daaf2698eb51a76721e98db21ce4f52339e535725de03ea962b5a3202"}, + {file = "multidict-6.7.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8891681594162635948a636c9fe0ff21746aeb3dd5463f6e25d9bea3a8a39ca1"}, + {file = "multidict-6.7.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18706cc31dbf402a7945916dd5cddf160251b6dab8a2c5f3d6d5a55949f676b3"}, + {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f844a1bbf1d207dd311a56f383f7eda2d0e134921d45751842d8235e7778965d"}, + {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4393e3581e84e5645506923816b9cc81f5609a778c7e7534054091acc64d1c6"}, + {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:fbd18dc82d7bf274b37aa48d664534330af744e03bccf696d6f4c6042e7d19e7"}, + {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:b6234e14f9314731ec45c42fc4554b88133ad53a09092cc48a88e771c125dadb"}, + {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:08d4379f9744d8f78d98c8673c06e202ffa88296f009c71bbafe8a6bf847d01f"}, + {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9fe04da3f79387f450fd0061d4dd2e45a72749d31bf634aecc9e27f24fdc4b3f"}, + {file = "multidict-6.7.0-cp314-cp314-win32.whl", hash = "sha256:fbafe31d191dfa7c4c51f7a6149c9fb7e914dcf9ffead27dcfd9f1ae382b3885"}, + {file = "multidict-6.7.0-cp314-cp314-win_amd64.whl", hash = "sha256:2f67396ec0310764b9222a1728ced1ab638f61aadc6226f17a71dd9324f9a99c"}, + {file = "multidict-6.7.0-cp314-cp314-win_arm64.whl", hash = "sha256:ba672b26069957ee369cfa7fc180dde1fc6f176eaf1e6beaf61fbebbd3d9c000"}, + {file = "multidict-6.7.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:c1dcc7524066fa918c6a27d61444d4ee7900ec635779058571f70d042d86ed63"}, + {file = "multidict-6.7.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:27e0b36c2d388dc7b6ced3406671b401e84ad7eb0656b8f3a2f46ed0ce483718"}, + {file = "multidict-6.7.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2a7baa46a22e77f0988e3b23d4ede5513ebec1929e34ee9495be535662c0dfe2"}, + {file = "multidict-6.7.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7bf77f54997a9166a2f5675d1201520586439424c2511723a7312bdb4bcc034e"}, + {file = "multidict-6.7.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e011555abada53f1578d63389610ac8a5400fc70ce71156b0aa30d326f1a5064"}, + {file = "multidict-6.7.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:28b37063541b897fd6a318007373930a75ca6d6ac7c940dbe14731ffdd8d498e"}, + {file = "multidict-6.7.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05047ada7a2fde2631a0ed706f1fd68b169a681dfe5e4cf0f8e4cb6618bbc2cd"}, + {file = "multidict-6.7.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:716133f7d1d946a4e1b91b1756b23c088881e70ff180c24e864c26192ad7534a"}, + {file = "multidict-6.7.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d1bed1b467ef657f2a0ae62844a607909ef1c6889562de5e1d505f74457d0b96"}, + {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ca43bdfa5d37bd6aee89d85e1d0831fb86e25541be7e9d376ead1b28974f8e5e"}, + {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:44b546bd3eb645fd26fb949e43c02a25a2e632e2ca21a35e2e132c8105dc8599"}, + {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a6ef16328011d3f468e7ebc326f24c1445f001ca1dec335b2f8e66bed3006394"}, + {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:5aa873cbc8e593d361ae65c68f85faadd755c3295ea2c12040ee146802f23b38"}, + {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:3d7b6ccce016e29df4b7ca819659f516f0bc7a4b3efa3bb2012ba06431b044f9"}, + {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:171b73bd4ee683d307599b66793ac80981b06f069b62eea1c9e29c9241aa66b0"}, + {file = "multidict-6.7.0-cp314-cp314t-win32.whl", hash = "sha256:b2d7f80c4e1fd010b07cb26820aae86b7e73b681ee4889684fb8d2d4537aab13"}, + {file = "multidict-6.7.0-cp314-cp314t-win_amd64.whl", hash = "sha256:09929cab6fcb68122776d575e03c6cc64ee0b8fca48d17e135474b042ce515cd"}, + {file = "multidict-6.7.0-cp314-cp314t-win_arm64.whl", hash = "sha256:cc41db090ed742f32bd2d2c721861725e6109681eddf835d0a82bd3a5c382827"}, + {file = "multidict-6.7.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:363eb68a0a59bd2303216d2346e6c441ba10d36d1f9969fcb6f1ba700de7bb5c"}, + {file = "multidict-6.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d874eb056410ca05fed180b6642e680373688efafc7f077b2a2f61811e873a40"}, + {file = "multidict-6.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8b55d5497b51afdfde55925e04a022f1de14d4f4f25cdfd4f5d9b0aa96166851"}, + {file = "multidict-6.7.0-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f8e5c0031b90ca9ce555e2e8fd5c3b02a25f14989cbc310701823832c99eb687"}, + {file = "multidict-6.7.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cf41880c991716f3c7cec48e2f19ae4045fc9db5fc9cff27347ada24d710bb5"}, + {file = "multidict-6.7.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8cfc12a8630a29d601f48d47787bd7eb730e475e83edb5d6c5084317463373eb"}, + {file = "multidict-6.7.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3996b50c3237c4aec17459217c1e7bbdead9a22a0fcd3c365564fbd16439dde6"}, + {file = "multidict-6.7.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7f5170993a0dd3ab871c74f45c0a21a4e2c37a2f2b01b5f722a2ad9c6650469e"}, + {file = "multidict-6.7.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ec81878ddf0e98817def1e77d4f50dae5ef5b0e4fe796fae3bd674304172416e"}, + {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9281bf5b34f59afbc6b1e477a372e9526b66ca446f4bf62592839c195a718b32"}, + {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:68af405971779d8b37198726f2b6fe3955db846fee42db7a4286fc542203934c"}, + {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3ba3ef510467abb0667421a286dc906e30eb08569365f5cdb131d7aff7c2dd84"}, + {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b61189b29081a20c7e4e0b49b44d5d44bb0dc92be3c6d06a11cc043f81bf9329"}, + {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:fb287618b9c7aa3bf8d825f02d9201b2f13078a5ed3b293c8f4d953917d84d5e"}, + {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:521f33e377ff64b96c4c556b81c55d0cfffb96a11c194fd0c3f1e56f3d8dd5a4"}, + {file = "multidict-6.7.0-cp39-cp39-win32.whl", hash = "sha256:ce8fdc2dca699f8dbf055a61d73eaa10482569ad20ee3c36ef9641f69afa8c91"}, + {file = "multidict-6.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:7e73299c99939f089dd9b2120a04a516b95cdf8c1cd2b18c53ebf0de80b1f18f"}, + {file = "multidict-6.7.0-cp39-cp39-win_arm64.whl", hash = "sha256:6bdce131e14b04fd34a809b6380dbfd826065c3e2fe8a50dbae659fa0c390546"}, + {file = "multidict-6.7.0-py3-none-any.whl", hash = "sha256:394fc5c42a333c9ffc3e421a4c85e08580d990e08b99f6bf35b4132114c5dcb3"}, + {file = "multidict-6.7.0.tar.gz", hash = "sha256:c6e99d9a65ca282e578dfea819cfa9c0a62b2499d8677392e09feaf305e9e6f5"}, +] + +[[package]] +name = "multipledispatch" +version = "1.0.0" +description = "Multiple dispatch" +optional = false +python-versions = "*" +files = [ + {file = "multipledispatch-1.0.0-py3-none-any.whl", hash = "sha256:0c53cd8b077546da4e48869f49b13164bebafd0c2a5afceb6bb6a316e7fb46e4"}, + {file = "multipledispatch-1.0.0.tar.gz", hash = "sha256:5c839915465c68206c3e9c473357908216c28383b425361e5d144594bf85a7e0"}, +] + [[package]] name = "nbclient" version = "0.10.2" @@ -1721,6 +2518,26 @@ files = [ {file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"}, ] +[[package]] +name = "networkx" +version = "3.5" +description = "Python package for creating and manipulating graphs and networks" +optional = false +python-versions = ">=3.11" +files = [ + {file = "networkx-3.5-py3-none-any.whl", hash = "sha256:0030d386a9a06dee3565298b4a734b68589749a544acbb6c412dc9e2489ec6ec"}, + {file = "networkx-3.5.tar.gz", hash = "sha256:d4c6f9cf81f52d69230866796b82afbccdec3db7ae4fbd1b65ea750feed50037"}, +] + +[package.extras] +default = ["matplotlib (>=3.8)", "numpy (>=1.25)", "pandas (>=2.0)", "scipy (>=1.11.2)"] +developer = ["mypy (>=1.15)", "pre-commit (>=4.1)"] +doc = ["intersphinx-registry", "myst-nb (>=1.1)", "numpydoc (>=1.8.0)", "pillow (>=10)", "pydata-sphinx-theme (>=0.16)", "sphinx (>=8.0)", "sphinx-gallery (>=0.18)", "texext (>=0.6.7)"] +example = ["cairocffi (>=1.7)", "contextily (>=1.6)", "igraph (>=0.11)", "momepy (>=0.7.2)", "osmnx (>=2.0.0)", "scikit-learn (>=1.5)", "seaborn (>=0.13)"] +extra = ["lxml (>=4.6)", "pydot (>=3.0.1)", "pygraphviz (>=1.14)", "sympy (>=1.10)"] +test = ["pytest (>=7.2)", "pytest-cov (>=4.0)", "pytest-xdist (>=3.0)"] +test-extras = ["pytest-mpl", "pytest-randomly"] + [[package]] name = "notebook" version = "7.4.7" @@ -1844,6 +2661,257 @@ files = [ {file = "numpy-2.3.4.tar.gz", hash = "sha256:a7d018bfedb375a8d979ac758b120ba846a7fe764911a64465fd87b8729f4a6a"}, ] +[[package]] +name = "numpyro" +version = "0.19.0" +description = "Pyro PPL on NumPy" +optional = false +python-versions = ">=3.9" +files = [ + {file = "numpyro-0.19.0-py3-none-any.whl", hash = "sha256:1063a2c131a0785719e13c8e55f1b82e41850d814df149418097531f4dbdeda8"}, + {file = "numpyro-0.19.0.tar.gz", hash = "sha256:bbf5b772a6ba8b7a79448fa6787afb069e5eb2dff8295078c3ec04d3e6276742"}, +] + +[package.dependencies] +jax = ">=0.4.25" +jaxlib = ">=0.4.25" +multipledispatch = "*" +numpy = "*" +tqdm = "*" + +[package.extras] +cpu = ["jax[cpu] (>=0.4.25)"] +cuda = ["jax[cuda] (>=0.4.25)"] +dev = ["dm-haiku (<0.0.14)", "dm-haiku (>=0.0.14)", "equinox", "flax", "funsor (>=0.4.1)", "graphviz", "jaxns (>=2.6.3,<=2.6.9)", "matplotlib", "optax (>=0.0.6)", "pylab-sdk", "pytest-cov", "pyyaml", "requests", "tfp-nightly"] +doc = ["ipython", "nbsphinx (>=0.8.9)", "readthedocs-sphinx-search (>=0.3.2)", "sphinx (>=5)", "sphinx-gallery", "sphinx_rtd_theme"] +examples = ["arviz", "jupyter", "matplotlib", "pandas", "scikit-learn", "seaborn", "wordcloud"] +test = ["importlib-metadata (<5.0)", "mypy (>=1.13)", "pyro-api (>=0.1.1)", "pytest (>=4.1)", "ruff (>=0.1.8)", "scikit-learn", "scipy (>=1.9)"] +tpu = ["jax[tpu] (>=0.4.25)"] + +[[package]] +name = "nvidia-cublas-cu12" +version = "12.8.4.1" +description = "CUBLAS native runtime libraries" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_cublas_cu12-12.8.4.1-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:b86f6dd8935884615a0683b663891d43781b819ac4f2ba2b0c9604676af346d0"}, + {file = "nvidia_cublas_cu12-12.8.4.1-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:8ac4e771d5a348c551b2a426eda6193c19aa630236b418086020df5ba9667142"}, + {file = "nvidia_cublas_cu12-12.8.4.1-py3-none-win_amd64.whl", hash = "sha256:47e9b82132fa8d2b4944e708049229601448aaad7e6f296f630f2d1a32de35af"}, +] + +[[package]] +name = "nvidia-cuda-cupti-cu12" +version = "12.8.90" +description = "CUDA profiling tools runtime libs." +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_cuda_cupti_cu12-12.8.90-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4412396548808ddfed3f17a467b104ba7751e6b58678a4b840675c56d21cf7ed"}, + {file = "nvidia_cuda_cupti_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ea0cb07ebda26bb9b29ba82cda34849e73c166c18162d3913575b0c9db9a6182"}, + {file = "nvidia_cuda_cupti_cu12-12.8.90-py3-none-win_amd64.whl", hash = "sha256:bb479dcdf7e6d4f8b0b01b115260399bf34154a1a2e9fe11c85c517d87efd98e"}, +] + +[[package]] +name = "nvidia-cuda-nvrtc-cu12" +version = "12.8.93" +description = "NVRTC native runtime libraries" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_cuda_nvrtc_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:a7756528852ef889772a84c6cd89d41dfa74667e24cca16bb31f8f061e3e9994"}, + {file = "nvidia_cuda_nvrtc_cu12-12.8.93-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fc1fec1e1637854b4c0a65fb9a8346b51dd9ee69e61ebaccc82058441f15bce8"}, + {file = "nvidia_cuda_nvrtc_cu12-12.8.93-py3-none-win_amd64.whl", hash = "sha256:7a4b6b2904850fe78e0bd179c4b655c404d4bb799ef03ddc60804247099ae909"}, +] + +[[package]] +name = "nvidia-cuda-runtime-cu12" +version = "12.8.90" +description = "CUDA Runtime native Libraries" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_cuda_runtime_cu12-12.8.90-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:52bf7bbee900262ffefe5e9d5a2a69a30d97e2bc5bb6cc866688caa976966e3d"}, + {file = "nvidia_cuda_runtime_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:adade8dcbd0edf427b7204d480d6066d33902cab2a4707dcfc48a2d0fd44ab90"}, + {file = "nvidia_cuda_runtime_cu12-12.8.90-py3-none-win_amd64.whl", hash = "sha256:c0c6027f01505bfed6c3b21ec546f69c687689aad5f1a377554bc6ca4aa993a8"}, +] + +[[package]] +name = "nvidia-cudnn-cu12" +version = "9.10.2.21" +description = "cuDNN runtime libraries" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_cudnn_cu12-9.10.2.21-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:c9132cc3f8958447b4910a1720036d9eff5928cc3179b0a51fb6d167c6cc87d8"}, + {file = "nvidia_cudnn_cu12-9.10.2.21-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:949452be657fa16687d0930933f032835951ef0892b37d2d53824d1a84dc97a8"}, + {file = "nvidia_cudnn_cu12-9.10.2.21-py3-none-win_amd64.whl", hash = "sha256:c6288de7d63e6cf62988f0923f96dc339cea362decb1bf5b3141883392a7d65e"}, +] + +[package.dependencies] +nvidia-cublas-cu12 = "*" + +[[package]] +name = "nvidia-cufft-cu12" +version = "11.3.3.83" +description = "CUFFT native runtime libraries" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_cufft_cu12-11.3.3.83-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:848ef7224d6305cdb2a4df928759dca7b1201874787083b6e7550dd6765ce69a"}, + {file = "nvidia_cufft_cu12-11.3.3.83-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d2dd21ec0b88cf61b62e6b43564355e5222e4a3fb394cac0db101f2dd0d4f74"}, + {file = "nvidia_cufft_cu12-11.3.3.83-py3-none-win_amd64.whl", hash = "sha256:7a64a98ef2a7c47f905aaf8931b69a3a43f27c55530c698bb2ed7c75c0b42cb7"}, +] + +[package.dependencies] +nvidia-nvjitlink-cu12 = "*" + +[[package]] +name = "nvidia-cufile-cu12" +version = "1.13.1.3" +description = "cuFile GPUDirect libraries" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_cufile_cu12-1.13.1.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1d069003be650e131b21c932ec3d8969c1715379251f8d23a1860554b1cb24fc"}, + {file = "nvidia_cufile_cu12-1.13.1.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:4beb6d4cce47c1a0f1013d72e02b0994730359e17801d395bdcbf20cfb3bb00a"}, +] + +[[package]] +name = "nvidia-curand-cu12" +version = "10.3.9.90" +description = "CURAND native runtime libraries" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_curand_cu12-10.3.9.90-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:dfab99248034673b779bc6decafdc3404a8a6f502462201f2f31f11354204acd"}, + {file = "nvidia_curand_cu12-10.3.9.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:b32331d4f4df5d6eefa0554c565b626c7216f87a06a4f56fab27c3b68a830ec9"}, + {file = "nvidia_curand_cu12-10.3.9.90-py3-none-win_amd64.whl", hash = "sha256:f149a8ca457277da854f89cf282d6ef43176861926c7ac85b2a0fbd237c587ec"}, +] + +[[package]] +name = "nvidia-cusolver-cu12" +version = "11.7.3.90" +description = "CUDA solver native runtime libraries" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_cusolver_cu12-11.7.3.90-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:db9ed69dbef9715071232caa9b69c52ac7de3a95773c2db65bdba85916e4e5c0"}, + {file = "nvidia_cusolver_cu12-11.7.3.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:4376c11ad263152bd50ea295c05370360776f8c3427b30991df774f9fb26c450"}, + {file = "nvidia_cusolver_cu12-11.7.3.90-py3-none-win_amd64.whl", hash = "sha256:4a550db115fcabc4d495eb7d39ac8b58d4ab5d8e63274d3754df1c0ad6a22d34"}, +] + +[package.dependencies] +nvidia-cublas-cu12 = "*" +nvidia-cusparse-cu12 = "*" +nvidia-nvjitlink-cu12 = "*" + +[[package]] +name = "nvidia-cusparse-cu12" +version = "12.5.8.93" +description = "CUSPARSE native runtime libraries" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_cusparse_cu12-12.5.8.93-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9b6c161cb130be1a07a27ea6923df8141f3c295852f4b260c65f18f3e0a091dc"}, + {file = "nvidia_cusparse_cu12-12.5.8.93-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ec05d76bbbd8b61b06a80e1eaf8cf4959c3d4ce8e711b65ebd0443bb0ebb13b"}, + {file = "nvidia_cusparse_cu12-12.5.8.93-py3-none-win_amd64.whl", hash = "sha256:9a33604331cb2cac199f2e7f5104dfbb8a5a898c367a53dfda9ff2acb6b6b4dd"}, +] + +[package.dependencies] +nvidia-nvjitlink-cu12 = "*" + +[[package]] +name = "nvidia-cusparselt-cu12" +version = "0.7.1" +description = "NVIDIA cuSPARSELt" +optional = false +python-versions = "*" +files = [ + {file = "nvidia_cusparselt_cu12-0.7.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:8878dce784d0fac90131b6817b607e803c36e629ba34dc5b433471382196b6a5"}, + {file = "nvidia_cusparselt_cu12-0.7.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f1bb701d6b930d5a7cea44c19ceb973311500847f81b634d802b7b539dc55623"}, + {file = "nvidia_cusparselt_cu12-0.7.1-py3-none-win_amd64.whl", hash = "sha256:f67fbb5831940ec829c9117b7f33807db9f9678dc2a617fbe781cac17b4e1075"}, +] + +[[package]] +name = "nvidia-nccl-cu12" +version = "2.27.5" +description = "NVIDIA Collective Communication Library (NCCL) Runtime" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_nccl_cu12-2.27.5-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:31432ad4d1fb1004eb0c56203dc9bc2178a1ba69d1d9e02d64a6938ab5e40e7a"}, + {file = "nvidia_nccl_cu12-2.27.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ad730cf15cb5d25fe849c6e6ca9eb5b76db16a80f13f425ac68d8e2e55624457"}, +] + +[[package]] +name = "nvidia-nvjitlink-cu12" +version = "12.8.93" +description = "Nvidia JIT LTO Library" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_nvjitlink_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:81ff63371a7ebd6e6451970684f916be2eab07321b73c9d244dc2b4da7f73b88"}, + {file = "nvidia_nvjitlink_cu12-12.8.93-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:adccd7161ace7261e01bb91e44e88da350895c270d23f744f0820c818b7229e7"}, + {file = "nvidia_nvjitlink_cu12-12.8.93-py3-none-win_amd64.whl", hash = "sha256:bd93fbeeee850917903583587f4fc3a4eafa022e34572251368238ab5e6bd67f"}, +] + +[[package]] +name = "nvidia-nvshmem-cu12" +version = "3.3.20" +description = "NVSHMEM creates a global address space that provides efficient and scalable communication for NVIDIA GPU clusters." +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_nvshmem_cu12-3.3.20-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0b0b960da3842212758e4fa4696b94f129090b30e5122fea3c5345916545cff0"}, + {file = "nvidia_nvshmem_cu12-3.3.20-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d00f26d3f9b2e3c3065be895e3059d6479ea5c638a3f38c9fec49b1b9dd7c1e5"}, +] + +[[package]] +name = "nvidia-nvtx-cu12" +version = "12.8.90" +description = "NVIDIA Tools Extension" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_nvtx_cu12-12.8.90-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d7ad891da111ebafbf7e015d34879f7112832fc239ff0d7d776b6cb685274615"}, + {file = "nvidia_nvtx_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5b17e2001cc0d751a5bc2c6ec6d26ad95913324a4adb86788c944f8ce9ba441f"}, + {file = "nvidia_nvtx_cu12-12.8.90-py3-none-win_amd64.whl", hash = "sha256:619c8304aedc69f02ea82dd244541a83c3d9d40993381b3b590f1adaed3db41e"}, +] + +[[package]] +name = "opt-einsum" +version = "3.4.0" +description = "Path optimization of einsum functions." +optional = false +python-versions = ">=3.8" +files = [ + {file = "opt_einsum-3.4.0-py3-none-any.whl", hash = "sha256:69bb92469f86a1565195ece4ac0323943e83477171b91d24c35afe028a90d7cd"}, + {file = "opt_einsum-3.4.0.tar.gz", hash = "sha256:96ca72f1b886d148241348783498194c577fa30a8faac108586b14f1ba4473ac"}, +] + +[[package]] +name = "optax" +version = "0.2.6" +description = "A gradient processing and optimization library in JAX." +optional = false +python-versions = ">=3.10" +files = [ + {file = "optax-0.2.6-py3-none-any.whl", hash = "sha256:f875251a5ab20f179d4be57478354e8e21963373b10f9c3b762b94dcb8c36d91"}, + {file = "optax-0.2.6.tar.gz", hash = "sha256:ba8d1e12678eba2657484d6feeca4fb281b8066bdfd5efbfc0f41b87663109c0"}, +] + +[package.dependencies] +absl-py = ">=0.7.1" +chex = ">=0.1.87" +jax = ">=0.5.3" +jaxlib = ">=0.5.3" +numpy = ">=1.18.0" + +[package.extras] +docs = ["flax", "ipython (>=8.8.0)", "matplotlib (>=3.5.0)", "myst-nb (>=1.0.0)", "setuptools", "sphinx (>=6.0.0)", "sphinx-autodoc-typehints", "sphinx-book-theme (>=1.0.1)", "sphinx-collections (>=0.0.1)", "sphinx-gallery (>=0.14.0)", "sphinx_contributors", "sphinxcontrib-katex"] +test = ["flax (>=0.5.3)", "scikit-learn", "scipy (>=1.7.1)"] + [[package]] name = "overrides" version = "7.7.0" @@ -2188,6 +3256,159 @@ files = [ [package.dependencies] wcwidth = "*" +[[package]] +name = "propcache" +version = "0.4.1" +description = "Accelerated property cache" +optional = false +python-versions = ">=3.9" +files = [ + {file = "propcache-0.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c2d1fa3201efaf55d730400d945b5b3ab6e672e100ba0f9a409d950ab25d7db"}, + {file = "propcache-0.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1eb2994229cc8ce7fe9b3db88f5465f5fd8651672840b2e426b88cdb1a30aac8"}, + {file = "propcache-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:66c1f011f45a3b33d7bcb22daed4b29c0c9e2224758b6be00686731e1b46f925"}, + {file = "propcache-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9a52009f2adffe195d0b605c25ec929d26b36ef986ba85244891dee3b294df21"}, + {file = "propcache-0.4.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5d4e2366a9c7b837555cf02fb9be2e3167d333aff716332ef1b7c3a142ec40c5"}, + {file = "propcache-0.4.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9d2b6caef873b4f09e26ea7e33d65f42b944837563a47a94719cc3544319a0db"}, + {file = "propcache-0.4.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b16ec437a8c8a965ecf95739448dd938b5c7f56e67ea009f4300d8df05f32b7"}, + {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:296f4c8ed03ca7476813fe666c9ea97869a8d7aec972618671b33a38a5182ef4"}, + {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:1f0978529a418ebd1f49dad413a2b68af33f85d5c5ca5c6ca2a3bed375a7ac60"}, + {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fd138803047fb4c062b1c1dd95462f5209456bfab55c734458f15d11da288f8f"}, + {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8c9b3cbe4584636d72ff556d9036e0c9317fa27b3ac1f0f558e7e84d1c9c5900"}, + {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f93243fdc5657247533273ac4f86ae106cc6445a0efacb9a1bfe982fcfefd90c"}, + {file = "propcache-0.4.1-cp310-cp310-win32.whl", hash = "sha256:a0ee98db9c5f80785b266eb805016e36058ac72c51a064040f2bc43b61101cdb"}, + {file = "propcache-0.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:1cdb7988c4e5ac7f6d175a28a9aa0c94cb6f2ebe52756a3c0cda98d2809a9e37"}, + {file = "propcache-0.4.1-cp310-cp310-win_arm64.whl", hash = "sha256:d82ad62b19645419fe79dd63b3f9253e15b30e955c0170e5cebc350c1844e581"}, + {file = "propcache-0.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:60a8fda9644b7dfd5dece8c61d8a85e271cb958075bfc4e01083c148b61a7caf"}, + {file = "propcache-0.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c30b53e7e6bda1d547cabb47c825f3843a0a1a42b0496087bb58d8fedf9f41b5"}, + {file = "propcache-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6918ecbd897443087a3b7cd978d56546a812517dcaaca51b49526720571fa93e"}, + {file = "propcache-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d902a36df4e5989763425a8ab9e98cd8ad5c52c823b34ee7ef307fd50582566"}, + {file = "propcache-0.4.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a9695397f85973bb40427dedddf70d8dc4a44b22f1650dd4af9eedf443d45165"}, + {file = "propcache-0.4.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2bb07ffd7eaad486576430c89f9b215f9e4be68c4866a96e97db9e97fead85dc"}, + {file = "propcache-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd6f30fdcf9ae2a70abd34da54f18da086160e4d7d9251f81f3da0ff84fc5a48"}, + {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fc38cba02d1acba4e2869eef1a57a43dfbd3d49a59bf90dda7444ec2be6a5570"}, + {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:67fad6162281e80e882fb3ec355398cf72864a54069d060321f6cd0ade95fe85"}, + {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f10207adf04d08bec185bae14d9606a1444715bc99180f9331c9c02093e1959e"}, + {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e9b0d8d0845bbc4cfcdcbcdbf5086886bc8157aa963c31c777ceff7846c77757"}, + {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:981333cb2f4c1896a12f4ab92a9cc8f09ea664e9b7dbdc4eff74627af3a11c0f"}, + {file = "propcache-0.4.1-cp311-cp311-win32.whl", hash = "sha256:f1d2f90aeec838a52f1c1a32fe9a619fefd5e411721a9117fbf82aea638fe8a1"}, + {file = "propcache-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:364426a62660f3f699949ac8c621aad6977be7126c5807ce48c0aeb8e7333ea6"}, + {file = "propcache-0.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:e53f3a38d3510c11953f3e6a33f205c6d1b001129f972805ca9b42fc308bc239"}, + {file = "propcache-0.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e153e9cd40cc8945138822807139367f256f89c6810c2634a4f6902b52d3b4e2"}, + {file = "propcache-0.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd547953428f7abb73c5ad82cbb32109566204260d98e41e5dfdc682eb7f8403"}, + {file = "propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f048da1b4f243fc44f205dfd320933a951b8d89e0afd4c7cacc762a8b9165207"}, + {file = "propcache-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec17c65562a827bba85e3872ead335f95405ea1674860d96483a02f5c698fa72"}, + {file = "propcache-0.4.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:405aac25c6394ef275dee4c709be43745d36674b223ba4eb7144bf4d691b7367"}, + {file = "propcache-0.4.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0013cb6f8dde4b2a2f66903b8ba740bdfe378c943c4377a200551ceb27f379e4"}, + {file = "propcache-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15932ab57837c3368b024473a525e25d316d8353016e7cc0e5ba9eb343fbb1cf"}, + {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:031dce78b9dc099f4c29785d9cf5577a3faf9ebf74ecbd3c856a7b92768c3df3"}, + {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ab08df6c9a035bee56e31af99be621526bd237bea9f32def431c656b29e41778"}, + {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4d7af63f9f93fe593afbf104c21b3b15868efb2c21d07d8732c0c4287e66b6a6"}, + {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cfc27c945f422e8b5071b6e93169679e4eb5bf73bbcbf1ba3ae3a83d2f78ebd9"}, + {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35c3277624a080cc6ec6f847cbbbb5b49affa3598c4535a0a4682a697aaa5c75"}, + {file = "propcache-0.4.1-cp312-cp312-win32.whl", hash = "sha256:671538c2262dadb5ba6395e26c1731e1d52534bfe9ae56d0b5573ce539266aa8"}, + {file = "propcache-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:cb2d222e72399fcf5890d1d5cc1060857b9b236adff2792ff48ca2dfd46c81db"}, + {file = "propcache-0.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:204483131fb222bdaaeeea9f9e6c6ed0cac32731f75dfc1d4a567fc1926477c1"}, + {file = "propcache-0.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:43eedf29202c08550aac1d14e0ee619b0430aaef78f85864c1a892294fbc28cf"}, + {file = "propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d62cdfcfd89ccb8de04e0eda998535c406bf5e060ffd56be6c586cbcc05b3311"}, + {file = "propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74"}, + {file = "propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:333ddb9031d2704a301ee3e506dc46b1fe5f294ec198ed6435ad5b6a085facfe"}, + {file = "propcache-0.4.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fd0858c20f078a32cf55f7e81473d96dcf3b93fd2ccdb3d40fdf54b8573df3af"}, + {file = "propcache-0.4.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:678ae89ebc632c5c204c794f8dab2837c5f159aeb59e6ed0539500400577298c"}, + {file = "propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f"}, + {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4d3df5fa7e36b3225954fba85589da77a0fe6a53e3976de39caf04a0db4c36f1"}, + {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ee17f18d2498f2673e432faaa71698032b0127ebf23ae5974eeaf806c279df24"}, + {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:580e97762b950f993ae618e167e7be9256b8353c2dcd8b99ec100eb50f5286aa"}, + {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:501d20b891688eb8e7aa903021f0b72d5a55db40ffaab27edefd1027caaafa61"}, + {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a0bd56e5b100aef69bd8562b74b46254e7c8812918d3baa700c8a8009b0af66"}, + {file = "propcache-0.4.1-cp313-cp313-win32.whl", hash = "sha256:bcc9aaa5d80322bc2fb24bb7accb4a30f81e90ab8d6ba187aec0744bc302ad81"}, + {file = "propcache-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e"}, + {file = "propcache-0.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:8873eb4460fd55333ea49b7d189749ecf6e55bf85080f11b1c4530ed3034cba1"}, + {file = "propcache-0.4.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:92d1935ee1f8d7442da9c0c4fa7ac20d07e94064184811b685f5c4fada64553b"}, + {file = "propcache-0.4.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:473c61b39e1460d386479b9b2f337da492042447c9b685f28be4f74d3529e566"}, + {file = "propcache-0.4.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c0ef0aaafc66fbd87842a3fe3902fd889825646bc21149eafe47be6072725835"}, + {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95393b4d66bfae908c3ca8d169d5f79cd65636ae15b5e7a4f6e67af675adb0e"}, + {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c07fda85708bc48578467e85099645167a955ba093be0a2dcba962195676e859"}, + {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:af223b406d6d000830c6f65f1e6431783fc3f713ba3e6cc8c024d5ee96170a4b"}, + {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a78372c932c90ee474559c5ddfffd718238e8673c340dc21fe45c5b8b54559a0"}, + {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:564d9f0d4d9509e1a870c920a89b2fec951b44bf5ba7d537a9e7c1ccec2c18af"}, + {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:17612831fda0138059cc5546f4d12a2aacfb9e47068c06af35c400ba58ba7393"}, + {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:41a89040cb10bd345b3c1a873b2bf36413d48da1def52f268a055f7398514874"}, + {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e35b88984e7fa64aacecea39236cee32dd9bd8c55f57ba8a75cf2399553f9bd7"}, + {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f8b465489f927b0df505cbe26ffbeed4d6d8a2bbc61ce90eb074ff129ef0ab1"}, + {file = "propcache-0.4.1-cp313-cp313t-win32.whl", hash = "sha256:2ad890caa1d928c7c2965b48f3a3815c853180831d0e5503d35cf00c472f4717"}, + {file = "propcache-0.4.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f7ee0e597f495cf415bcbd3da3caa3bd7e816b74d0d52b8145954c5e6fd3ff37"}, + {file = "propcache-0.4.1-cp313-cp313t-win_arm64.whl", hash = "sha256:929d7cbe1f01bb7baffb33dc14eb5691c95831450a26354cd210a8155170c93a"}, + {file = "propcache-0.4.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3f7124c9d820ba5548d431afb4632301acf965db49e666aa21c305cbe8c6de12"}, + {file = "propcache-0.4.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c0d4b719b7da33599dfe3b22d3db1ef789210a0597bc650b7cee9c77c2be8c5c"}, + {file = "propcache-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f302f4783709a78240ebc311b793f123328716a60911d667e0c036bc5dcbded"}, + {file = "propcache-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c80ee5802e3fb9ea37938e7eecc307fb984837091d5fd262bb37238b1ae97641"}, + {file = "propcache-0.4.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ed5a841e8bb29a55fb8159ed526b26adc5bdd7e8bd7bf793ce647cb08656cdf4"}, + {file = "propcache-0.4.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55c72fd6ea2da4c318e74ffdf93c4fe4e926051133657459131a95c846d16d44"}, + {file = "propcache-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8326e144341460402713f91df60ade3c999d601e7eb5ff8f6f7862d54de0610d"}, + {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:060b16ae65bc098da7f6d25bf359f1f31f688384858204fe5d652979e0015e5b"}, + {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:89eb3fa9524f7bec9de6e83cf3faed9d79bffa560672c118a96a171a6f55831e"}, + {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dee69d7015dc235f526fe80a9c90d65eb0039103fe565776250881731f06349f"}, + {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5558992a00dfd54ccbc64a32726a3357ec93825a418a401f5cc67df0ac5d9e49"}, + {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c9b822a577f560fbd9554812526831712c1436d2c046cedee4c3796d3543b144"}, + {file = "propcache-0.4.1-cp314-cp314-win32.whl", hash = "sha256:ab4c29b49d560fe48b696cdcb127dd36e0bc2472548f3bf56cc5cb3da2b2984f"}, + {file = "propcache-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:5a103c3eb905fcea0ab98be99c3a9a5ab2de60228aa5aceedc614c0281cf6153"}, + {file = "propcache-0.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:74c1fb26515153e482e00177a1ad654721bf9207da8a494a0c05e797ad27b992"}, + {file = "propcache-0.4.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:824e908bce90fb2743bd6b59db36eb4f45cd350a39637c9f73b1c1ea66f5b75f"}, + {file = "propcache-0.4.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2b5e7db5328427c57c8e8831abda175421b709672f6cfc3d630c3b7e2146393"}, + {file = "propcache-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6f6ff873ed40292cd4969ef5310179afd5db59fdf055897e282485043fc80ad0"}, + {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49a2dc67c154db2c1463013594c458881a069fcf98940e61a0569016a583020a"}, + {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:005f08e6a0529984491e37d8dbc3dd86f84bd78a8ceb5fa9a021f4c48d4984be"}, + {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c3310452e0d31390da9035c348633b43d7e7feb2e37be252be6da45abd1abcc"}, + {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3c70630930447f9ef1caac7728c8ad1c56bc5015338b20fed0d08ea2480b3a"}, + {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e57061305815dfc910a3634dcf584f08168a8836e6999983569f51a8544cd89"}, + {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:521a463429ef54143092c11a77e04056dd00636f72e8c45b70aaa3140d639726"}, + {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:120c964da3fdc75e3731aa392527136d4ad35868cc556fd09bb6d09172d9a367"}, + {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d8f353eb14ee3441ee844ade4277d560cdd68288838673273b978e3d6d2c8f36"}, + {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ab2943be7c652f09638800905ee1bab2c544e537edb57d527997a24c13dc1455"}, + {file = "propcache-0.4.1-cp314-cp314t-win32.whl", hash = "sha256:05674a162469f31358c30bcaa8883cb7829fa3110bf9c0991fe27d7896c42d85"}, + {file = "propcache-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:990f6b3e2a27d683cb7602ed6c86f15ee6b43b1194736f9baaeb93d0016633b1"}, + {file = "propcache-0.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ecef2343af4cc68e05131e45024ba34f6095821988a9d0a02aa7c73fcc448aa9"}, + {file = "propcache-0.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3d233076ccf9e450c8b3bc6720af226b898ef5d051a2d145f7d765e6e9f9bcff"}, + {file = "propcache-0.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:357f5bb5c377a82e105e44bd3d52ba22b616f7b9773714bff93573988ef0a5fb"}, + {file = "propcache-0.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cbc3b6dfc728105b2a57c06791eb07a94229202ea75c59db644d7d496b698cac"}, + {file = "propcache-0.4.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:182b51b421f0501952d938dc0b0eb45246a5b5153c50d42b495ad5fb7517c888"}, + {file = "propcache-0.4.1-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4b536b39c5199b96fc6245eb5fb796c497381d3942f169e44e8e392b29c9ebcc"}, + {file = "propcache-0.4.1-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:db65d2af507bbfbdcedb254a11149f894169d90488dd3e7190f7cdcb2d6cd57a"}, + {file = "propcache-0.4.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd2dbc472da1f772a4dae4fa24be938a6c544671a912e30529984dd80400cd88"}, + {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:daede9cd44e0f8bdd9e6cc9a607fc81feb80fae7a5fc6cecaff0e0bb32e42d00"}, + {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:71b749281b816793678ae7f3d0d84bd36e694953822eaad408d682efc5ca18e0"}, + {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:0002004213ee1f36cfb3f9a42b5066100c44276b9b72b4e1504cddd3d692e86e"}, + {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:fe49d0a85038f36ba9e3ffafa1103e61170b28e95b16622e11be0a0ea07c6781"}, + {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:99d43339c83aaf4d32bda60928231848eee470c6bda8d02599cc4cebe872d183"}, + {file = "propcache-0.4.1-cp39-cp39-win32.whl", hash = "sha256:a129e76735bc792794d5177069691c3217898b9f5cee2b2661471e52ffe13f19"}, + {file = "propcache-0.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:948dab269721ae9a87fd16c514a0a2c2a1bdb23a9a61b969b0f9d9ee2968546f"}, + {file = "propcache-0.4.1-cp39-cp39-win_arm64.whl", hash = "sha256:5fd37c406dd6dc85aa743e214cef35dc54bbdd1419baac4f6ae5e5b1a2976938"}, + {file = "propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237"}, + {file = "propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d"}, +] + +[[package]] +name = "prophetverse" +version = "0.10.0" +description = "A multiverse of prophet models, for forecasting and Marketing Mix Modeling." +optional = false +python-versions = "<3.13,>=3.9" +files = [ + {file = "prophetverse-0.10.0-py3-none-any.whl", hash = "sha256:f75ace261351599cc3c3b00882c0884d83abb0e5a187efa2f6f4cb93e698b3df"}, + {file = "prophetverse-0.10.0.tar.gz", hash = "sha256:17305f5fe96401b49f36b5f73507a48e0ea1d83ed670ac2b79c651aaf45d3be6"}, +] + +[package.dependencies] +graphviz = ">=0.20.3,<0.22.0" +numpyro = ">=0.19.0" +optax = ">=0.2.4" +scikit-base = ">=0.12.0,<0.13.0" +skpro = ">=2.9.2,<3.0.0" +sktime = ">=0.30.0" + +[package.extras] +dev = ["black (>=24.4.2,<26.0.0)", "commitlint (>=1.0.0,<2.0.0)", "ipykernel (>=6.26.0,<7.0.0)", "isort (>=5.13.2,<7.0.0)", "jupyterlab (>=4.4.2,<5.0.0)", "matplotlib (>=3.8.2,<4.0.0)", "mypy (>=1.10.0,<2.0.0)", "pre-commit (>=3.7.1,<5.0.0)", "pydocstyle (>=6.3.0,<7.0.0)", "pylint (>=3.2.2,<4.0.0)", "pytest (>=8.0.0,<9.0.0)", "pytest-cov (>=5.0.0,<8.0.0)", "pyyaml (>=6.0.2,<7.0.0)", "quartodoc (>=0.9.1,<0.12.0)", "seaborn (>=0.13.2,<0.14.0)", "statsmodels (>=0.14.4,<0.15.0)"] + [[package]] name = "psutil" version = "7.1.0" @@ -2323,6 +3544,64 @@ files = [ [package.extras] dev = ["backports.zoneinfo", "black", "build", "freezegun", "mdx_truly_sane_lists", "mike", "mkdocs", "mkdocs-awesome-pages-plugin", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-material (>=8.5)", "mkdocstrings[python]", "msgspec", "mypy", "orjson", "pylint", "pytest", "tzdata", "validate-pyproject[all]"] +[[package]] +name = "pytorch-forecasting" +version = "1.5.0" +description = "Forecasting timeseries with PyTorch - dataloaders, normalizers, metrics and models" +optional = false +python-versions = "<3.14,>=3.10" +files = [ + {file = "pytorch_forecasting-1.5.0-py3-none-any.whl", hash = "sha256:ff805102840d61037aa58cc7f3c889121221feb6228ba0e21fe73471f41ff904"}, + {file = "pytorch_forecasting-1.5.0.tar.gz", hash = "sha256:b65c46eef684ffcef6388dfc241c902cdca9233701dfa59b2e3ac79f6431286d"}, +] + +[package.dependencies] +lightning = ">=2.0.0,<3.0.0" +numpy = "<=3.0.0" +pandas = ">=1.3.0,<3.0.0" +scikit-learn = ">=1.2,<2.0" +scipy = ">=1.8,<2.0" +torch = ">=2.0.0,<2.0.1 || >2.0.1,<3.0.0" + +[package.extras] +all-extras = ["cpflows", "matplotlib", "optuna (>=3.1.0,<5.0.0)", "optuna-integration", "pytorch_optimizer (>=2.5.1,<4.0.0)", "statsmodels"] +dev = ["black[jupyter]", "coverage", "invoke", "ipykernel", "ipywidgets (>=8.0.1,<9.0.0)", "mypy", "nbconvert", "nbsphinx", "pandoc (>=2.3,<3.0.0)", "pre-commit (>=3.2.0,<5.0.0)", "pyarrow", "pydata-sphinx-theme", "pydocstyle (>=6.1.1,<7.0.0)", "pylint", "pytest", "pytest-cov", "pytest-dotenv (>=0.5.2,<1.0.0)", "pytest-sugar", "pytest-xdist", "recommonmark", "ruff", "scikit-base", "sphinx", "tensorboard (>=2.12.1,<3.0.0)"] +docs = ["docutils", "nbconvert", "nbsphinx", "pandoc", "pydata-sphinx-theme", "recommonmark", "sphinx (>3.2,<8.2.4)"] +github-actions = ["pytest-github-actions-annotate-failures"] +graph = ["networkx"] +mqf2 = ["cpflows"] +tuning = ["optuna (>=3.1.0,<5.0.0)", "optuna-integration", "statsmodels"] + +[[package]] +name = "pytorch-lightning" +version = "2.5.5" +description = "PyTorch Lightning is the lightweight PyTorch wrapper for ML researchers. Scale your models. Write less boilerplate." +optional = false +python-versions = ">=3.9" +files = [ + {file = "pytorch_lightning-2.5.5-py3-none-any.whl", hash = "sha256:0b533991df2353c0c6ea9ca10a7d0728b73631fd61f5a15511b19bee2aef8af0"}, + {file = "pytorch_lightning-2.5.5.tar.gz", hash = "sha256:d6fc8173d1d6e49abfd16855ea05d2eb2415e68593f33d43e59028ecb4e64087"}, +] + +[package.dependencies] +fsspec = {version = ">=2022.5.0", extras = ["http"]} +lightning-utilities = ">=0.10.0" +packaging = ">=20.0" +PyYAML = ">5.4" +torch = ">=2.1.0" +torchmetrics = ">0.7.0" +tqdm = ">=4.57.0" +typing-extensions = ">4.5.0" + +[package.extras] +all = ["bitsandbytes (>=0.45.2)", "deepspeed (>=0.14.1,<=0.15.0)", "hydra-core (>=1.2.0)", "ipython[all] (>=8.0.0)", "jsonargparse[jsonnet,signatures] (>=4.39.0)", "matplotlib (>3.1)", "omegaconf (>=2.2.3)", "requests (<2.33.0)", "rich (>=12.3.0)", "tensorboardX (>=2.2)", "torchmetrics (>=0.10.0)", "torchvision (>=0.16.0)"] +deepspeed = ["deepspeed (>=0.14.1,<=0.15.0)"] +dev = ["bitsandbytes (>=0.45.2)", "cloudpickle (>=1.3)", "coverage (==7.10.6)", "deepspeed (>=0.14.1,<=0.15.0)", "fastapi", "hydra-core (>=1.2.0)", "ipython[all] (>=8.0.0)", "jsonargparse[jsonnet,signatures] (>=4.39.0)", "matplotlib (>3.1)", "numpy (>1.20.0)", "omegaconf (>=2.2.3)", "onnx (>1.12.0)", "onnxruntime (>=1.12.0)", "onnxscript (>=0.1.0)", "pandas (>2.0)", "psutil (<7.0.1)", "pytest (==8.4.1)", "pytest-cov (==6.2.1)", "pytest-random-order (==1.2.0)", "pytest-rerunfailures (==16.0)", "pytest-timeout (==2.4.0)", "requests (<2.33.0)", "rich (>=12.3.0)", "scikit-learn (>0.22.1)", "tensorboard (>=2.11)", "tensorboardX (>=2.2)", "torchmetrics (>=0.10.0)", "torchvision (>=0.16.0)", "uvicorn"] +examples = ["ipython[all] (>=8.0.0)", "requests (<2.33.0)", "torchmetrics (>=0.10.0)", "torchvision (>=0.16.0)"] +extra = ["bitsandbytes (>=0.45.2)", "hydra-core (>=1.2.0)", "jsonargparse[jsonnet,signatures] (>=4.39.0)", "matplotlib (>3.1)", "omegaconf (>=2.2.3)", "rich (>=12.3.0)", "tensorboardX (>=2.2)"] +strategies = ["deepspeed (>=0.14.1,<=0.15.0)"] +test = ["cloudpickle (>=1.3)", "coverage (==7.10.6)", "fastapi", "numpy (>1.20.0)", "onnx (>1.12.0)", "onnxruntime (>=1.12.0)", "onnxscript (>=0.1.0)", "pandas (>2.0)", "psutil (<7.0.1)", "pytest (==8.4.1)", "pytest-cov (==6.2.1)", "pytest-random-order (==1.2.0)", "pytest-rerunfailures (==16.0)", "pytest-timeout (==2.4.0)", "scikit-learn (>0.22.1)", "tensorboard (>=2.11)", "uvicorn"] + [[package]] name = "pytz" version = "2025.2" @@ -3030,6 +4309,31 @@ files = [ {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, ] +[[package]] +name = "skpro" +version = "2.10.0" +description = "A unified framework for tabular probabilistic regression, time-to-event prediction, and probability distributions in python" +optional = false +python-versions = "<3.15,>=3.9" +files = [ + {file = "skpro-2.10.0-py3-none-any.whl", hash = "sha256:cd6e2ccb4d9832f3d848944daa2c8e802488807be62557d860cfe4bcaac52e44"}, + {file = "skpro-2.10.0.tar.gz", hash = "sha256:f15682a0ec5f38769bc0354264415c0ecb98c813295e9e23d027a1ad57bd15d8"}, +] + +[package.dependencies] +numpy = ">=1.21.0,<2.4" +packaging = "*" +pandas = ">=1.1.0,<2.4.0" +scikit-base = ">=0.6.1,<0.14.0" +scikit-learn = ">=0.24.0,<1.8.0" +scipy = ">=1.2.0,<2.0.0" + +[package.extras] +all-extras = ["cyclic-boosting (>=1.4.0)", "distfit", "lifelines (<0.31.0)", "mapie", "matplotlib (>=3.3.2)", "ngboost (<0.6.0)", "polars (<1.34.0)", "pyarrow (<14.0.0)", "pymc", "statsmodels (>=0.12.1)", "xgboostlss"] +binder = ["jupyter"] +dev = ["backoff", "httpx", "pre-commit", "pytest", "pytest-cov", "pytest-randomly", "pytest-timeout", "pytest-xdist", "wheel"] +docs = ["jupyter", "myst-parser", "nbsphinx (>=0.8.6)", "numpydoc", "pydata-sphinx-theme", "sphinx (!=7.2.0,<9.0.0)", "sphinx-design (<0.7.0)", "sphinx-gallery (<0.20.0)", "sphinx-issues (<6.0.0)", "sphinx-panels", "tabulate"] + [[package]] name = "sktime" version = "0.39.0" @@ -3173,6 +4477,23 @@ build = ["cython (>=3.0.10)"] develop = ["colorama", "cython (>=3.0.10)", "cython (>=3.0.10,<4)", "flake8", "isort", "jinja2", "joblib", "matplotlib (>=3)", "pytest (>=7.3.0,<8)", "pytest-cov", "pytest-randomly", "pytest-xdist", "pywinpty", "setuptools_scm[toml] (>=8.0,<9.0)"] docs = ["ipykernel", "jupyter_client", "matplotlib", "nbconvert", "nbformat", "numpydoc", "pandas-datareader", "sphinx"] +[[package]] +name = "sympy" +version = "1.14.0" +description = "Computer algebra system (CAS) in Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5"}, + {file = "sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517"}, +] + +[package.dependencies] +mpmath = ">=1.1.0,<1.4" + +[package.extras] +dev = ["hypothesis (>=6.70.0)", "pytest (>=7.1.0)"] + [[package]] name = "terminado" version = "0.18.1" @@ -3223,6 +4544,114 @@ webencodings = ">=0.4" doc = ["sphinx", "sphinx_rtd_theme"] test = ["pytest", "ruff"] +[[package]] +name = "toolz" +version = "1.0.0" +description = "List processing tools and functional utilities" +optional = false +python-versions = ">=3.8" +files = [ + {file = "toolz-1.0.0-py3-none-any.whl", hash = "sha256:292c8f1c4e7516bf9086f8850935c799a874039c8bcf959d47b600e4c44a6236"}, + {file = "toolz-1.0.0.tar.gz", hash = "sha256:2c86e3d9a04798ac556793bced838816296a2f085017664e4995cb40a1047a02"}, +] + +[[package]] +name = "torch" +version = "2.9.0" +description = "Tensors and Dynamic neural networks in Python with strong GPU acceleration" +optional = false +python-versions = ">=3.10" +files = [ + {file = "torch-2.9.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:030bbfe367379ae6a4ae4042b6c44da25383343b8b3c68abaa9c7231efbaf2dd"}, + {file = "torch-2.9.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:51cb63902182a78e90886e8068befd8ea102af4b00e420263591a3d70c7d3c6c"}, + {file = "torch-2.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:3f6aad4d2f0ee2248bac25339d74858ff846c3969b27d14ac235821f055af83d"}, + {file = "torch-2.9.0-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:413e1654c9203733138858780e184d9fc59442f0b3b209e16f39354eb893db9b"}, + {file = "torch-2.9.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:c596708b5105d0b199215acf0c9be7c1db5f1680d88eddadf4b75a299259a677"}, + {file = "torch-2.9.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:51de31219c97c51cf4bf2be94d622e3deb5dcc526c6dc00e97c17eaec0fc1d67"}, + {file = "torch-2.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:dd515c70059afd95f48b8192733764c08ca37a1d19803af6401b5ecad7c8676e"}, + {file = "torch-2.9.0-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:614a185e4986326d526a91210c8fc1397e76e8cfafa78baf6296a790e53a9eec"}, + {file = "torch-2.9.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:e5f7af1dc4c0a7c4a260c2534f41ddaf209714f7c89145e644c44712fbd6b642"}, + {file = "torch-2.9.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:01cff95ecd9a212ea2f141db28acccdceb6a4c54f64e6c51091146f5e2a772c6"}, + {file = "torch-2.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:4582b162f541651f0cb184d3e291c05c2f556c7117c64a9873e2ee158d40062b"}, + {file = "torch-2.9.0-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:33f58e9a102a91259af289d50525c30323b5c9ae1d31322b6447c0814da68695"}, + {file = "torch-2.9.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:c30a17fc83eeab346913e237c64b15b5ba6407fff812f6c541e322e19bc9ea0e"}, + {file = "torch-2.9.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:8f25033b8667b57857dfd01458fbf2a9e6a6df1f8def23aef0dc46292f6aa642"}, + {file = "torch-2.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:d037f1b4ffd25013be4a7bf3651a0a910c68554956c7b2c92ebe87c76475dece"}, + {file = "torch-2.9.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e4e5b5cba837a2a8d1a497ba9a58dae46fa392593eaa13b871c42f71847503a5"}, + {file = "torch-2.9.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:64693568f5dc4dbd5f880a478b1cea0201cc6b510d91d1bc54fea86ac5d1a637"}, + {file = "torch-2.9.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:f8ed31ddd7d10bfb3fbe0b9fe01b1243577f13d75e6f4a0839a283915ce3791e"}, + {file = "torch-2.9.0-cp313-cp313t-win_amd64.whl", hash = "sha256:eff527d4e4846e6f70d2afd8058b73825761203d66576a7e04ea2ecfebcb4ab8"}, + {file = "torch-2.9.0-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:f8877779cf56d1ce431a7636703bdb13307f5960bb1af49716d8b179225e0e6a"}, + {file = "torch-2.9.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7e614fae699838038d888729f82b687c03413c5989ce2a9481f9a7e7a396e0bb"}, + {file = "torch-2.9.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:dfb5b8cd310ba3436c7e14e8b7833ef658cf3045e50d2bdaed23c8fc517065eb"}, + {file = "torch-2.9.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:b3d29524993a478e46f5d598b249cd824b7ed98d7fba538bd9c4cde6c803948f"}, + {file = "torch-2.9.0-cp314-cp314-win_amd64.whl", hash = "sha256:71c7578984f5ec0eb645eb4816ac8435fcf3e3e2ae1901bcd2f519a9cafb5125"}, + {file = "torch-2.9.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:71d9309aee457bbe0b164bce2111cd911c4ed4e847e65d5077dbbcd3aba6befc"}, + {file = "torch-2.9.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:c08fb654d783899e204a32cca758a7ce8a45b2d78eeb89517cc937088316f78e"}, + {file = "torch-2.9.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:ec8feb0099b2daa5728fbc7abb0b05730fd97e0f359ff8bda09865aaa7bd7d4b"}, + {file = "torch-2.9.0-cp314-cp314t-win_amd64.whl", hash = "sha256:695ba920f234ad4170c9c50e28d56c848432f8f530e6bc7f88fcb15ddf338e75"}, +] + +[package.dependencies] +filelock = "*" +fsspec = ">=0.8.5" +jinja2 = "*" +networkx = ">=2.5.1" +nvidia-cublas-cu12 = {version = "12.8.4.1", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cuda-cupti-cu12 = {version = "12.8.90", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cuda-nvrtc-cu12 = {version = "12.8.93", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cuda-runtime-cu12 = {version = "12.8.90", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cudnn-cu12 = {version = "9.10.2.21", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cufft-cu12 = {version = "11.3.3.83", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cufile-cu12 = {version = "1.13.1.3", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-curand-cu12 = {version = "10.3.9.90", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cusolver-cu12 = {version = "11.7.3.90", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cusparse-cu12 = {version = "12.5.8.93", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cusparselt-cu12 = {version = "0.7.1", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-nccl-cu12 = {version = "2.27.5", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-nvjitlink-cu12 = {version = "12.8.93", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-nvshmem-cu12 = {version = "3.3.20", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-nvtx-cu12 = {version = "12.8.90", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +setuptools = {version = "*", markers = "python_version >= \"3.12\""} +sympy = ">=1.13.3" +triton = {version = "3.5.0", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +typing-extensions = ">=4.10.0" + +[package.extras] +opt-einsum = ["opt-einsum (>=3.3)"] +optree = ["optree (>=0.13.0)"] +pyyaml = ["pyyaml"] + +[[package]] +name = "torchmetrics" +version = "1.8.2" +description = "PyTorch native Metrics" +optional = false +python-versions = ">=3.9" +files = [ + {file = "torchmetrics-1.8.2-py3-none-any.whl", hash = "sha256:08382fd96b923e39e904c4d570f3d49e2cc71ccabd2a94e0f895d1f0dac86242"}, + {file = "torchmetrics-1.8.2.tar.gz", hash = "sha256:cf64a901036bf107f17a524009eea7781c9c5315d130713aeca5747a686fe7a5"}, +] + +[package.dependencies] +lightning-utilities = ">=0.8.0" +numpy = ">1.20.0" +packaging = ">17.1" +torch = ">=2.0.0" + +[package.extras] +all = ["SciencePlots (>=2.0.0)", "einops (>=0.7.0)", "einops (>=0.7.0)", "gammatone (>=1.0.0)", "ipadic (>=1.0.0)", "librosa (>=0.10.0)", "matplotlib (>=3.6.0)", "mecab-python3 (>=1.0.6)", "mypy (==1.17.1)", "nltk (>3.8.1)", "onnxruntime (>=1.12.0)", "pesq (>=0.0.4)", "piq (<=0.8.0)", "pycocotools (>2.0.0)", "pystoi (>=0.4.0)", "regex (>=2021.9.24)", "requests (>=2.19.0)", "scipy (>1.0.0)", "sentencepiece (>=0.2.0)", "timm (>=0.9.0)", "torch (==2.8.0)", "torch-fidelity (<=0.4.0)", "torch_linear_assignment (>=0.0.2)", "torchaudio (>=2.0.1)", "torchvision (>=0.15.1)", "torchvision (>=0.15.1)", "tqdm (<4.68.0)", "transformers (>=4.43.0)", "transformers (>=4.43.0)", "types-PyYAML", "types-emoji", "types-protobuf", "types-requests", "types-setuptools", "types-six", "types-tabulate", "vmaf-torch (>=1.1.0)"] +audio = ["gammatone (>=1.0.0)", "librosa (>=0.10.0)", "onnxruntime (>=1.12.0)", "pesq (>=0.0.4)", "pystoi (>=0.4.0)", "requests (>=2.19.0)", "torchaudio (>=2.0.1)"] +clustering = ["torch_linear_assignment (>=0.0.2)"] +detection = ["pycocotools (>2.0.0)", "torchvision (>=0.15.1)"] +dev = ["PyTDC (==0.4.1)", "SciencePlots (>=2.0.0)", "aeon (>=1.0.0)", "bert_score (==0.3.13)", "dists-pytorch (==0.1)", "dython (==0.7.9)", "einops (>=0.7.0)", "einops (>=0.7.0)", "fairlearn", "fast-bss-eval (>=0.1.0)", "faster-coco-eval (>=1.6.3)", "gammatone (>=1.0.0)", "huggingface-hub (<0.35)", "ipadic (>=1.0.0)", "jiwer (>=2.3.0)", "kornia (>=0.6.7)", "librosa (>=0.10.0)", "lpips (<=0.1.4)", "matplotlib (>=3.6.0)", "mecab-ko (>=1.0.0,<1.1.0)", "mecab-ko-dic (>=1.0.0)", "mecab-python3 (>=1.0.6)", "mir-eval (>=0.6)", "monai (==1.4.0)", "mypy (==1.17.1)", "netcal (>1.0.0)", "nltk (>3.8.1)", "numpy (<2.4.0)", "onnxruntime (>=1.12.0)", "pandas (>1.4.0)", "permetrics (==2.0.0)", "pesq (>=0.0.4)", "piq (<=0.8.0)", "properscoring (==0.1)", "pycocotools (>2.0.0)", "pystoi (>=0.4.0)", "pytorch-msssim (==1.0.0)", "regex (>=2021.9.24)", "requests (>=2.19.0)", "rouge-score (>0.1.0)", "sacrebleu (>=2.3.0)", "scikit-image (>=0.19.0)", "scipy (>1.0.0)", "scipy (>1.0.0)", "sentencepiece (>=0.2.0)", "sewar (>=0.4.4)", "statsmodels (>0.13.5)", "timm (>=0.9.0)", "torch (==2.8.0)", "torch-fidelity (<=0.4.0)", "torch_complex (<0.5.0)", "torch_linear_assignment (>=0.0.2)", "torchaudio (>=2.0.1)", "torchvision (>=0.15.1)", "torchvision (>=0.15.1)", "tqdm (<4.68.0)", "transformers (>=4.43.0)", "transformers (>=4.43.0)", "types-PyYAML", "types-emoji", "types-protobuf", "types-requests", "types-setuptools", "types-six", "types-tabulate", "vmaf-torch (>=1.1.0)"] +image = ["scipy (>1.0.0)", "torch-fidelity (<=0.4.0)", "torchvision (>=0.15.1)"] +multimodal = ["einops (>=0.7.0)", "piq (<=0.8.0)", "timm (>=0.9.0)", "transformers (>=4.43.0)"] +text = ["ipadic (>=1.0.0)", "mecab-python3 (>=1.0.6)", "nltk (>3.8.1)", "regex (>=2021.9.24)", "sentencepiece (>=0.2.0)", "tqdm (<4.68.0)", "transformers (>=4.43.0)"] +typing = ["mypy (==1.17.1)", "torch (==2.8.0)", "types-PyYAML", "types-emoji", "types-protobuf", "types-requests", "types-setuptools", "types-six", "types-tabulate"] +video = ["einops (>=0.7.0)", "vmaf-torch (>=1.1.0)"] +visual = ["SciencePlots (>=2.0.0)", "matplotlib (>=3.6.0)"] + [[package]] name = "tornado" version = "6.5.2" @@ -3244,6 +4673,27 @@ files = [ {file = "tornado-6.5.2.tar.gz", hash = "sha256:ab53c8f9a0fa351e2c0741284e06c7a45da86afb544133201c5cc8578eb076a0"}, ] +[[package]] +name = "tqdm" +version = "4.67.1" +description = "Fast, Extensible Progress Meter" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, + {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +dev = ["nbval", "pytest (>=6)", "pytest-asyncio (>=0.24)", "pytest-cov", "pytest-timeout"] +discord = ["requests"] +notebook = ["ipywidgets (>=6)"] +slack = ["slack-sdk"] +telegram = ["requests"] + [[package]] name = "traitlets" version = "5.14.3" @@ -3259,6 +4709,34 @@ files = [ docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"] +[[package]] +name = "triton" +version = "3.5.0" +description = "A language and compiler for custom Deep Learning operations" +optional = false +python-versions = "<3.15,>=3.10" +files = [ + {file = "triton-3.5.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6f90de6a6566bb619b4c0adc9855729e1b1b5e26533fca1bf6206e96b6d277a3"}, + {file = "triton-3.5.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5d3b3d480debf24eaa739623c9a42446b0b77f95593d30eb1f64cd2278cc1f0"}, + {file = "triton-3.5.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8457b22148defefdcb7fa8144b05ce211b9faefad650a1ce85b23df488d5549c"}, + {file = "triton-3.5.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f34bfa21c5b3a203c0f0eab28dcc1e49bd1f67d22724e77fb6665a659200a4ec"}, + {file = "triton-3.5.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7da21fccceafc163e3a5e857abe34351ef76345af06cabf9637a914742671f0b"}, + {file = "triton-3.5.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9e71db82261c4ffa3921cd050cd5faa18322d2d405c30eb56084afaff3b0833"}, + {file = "triton-3.5.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:188da5b81fa2f8322c27fec1627703eac24cb9bb7ab0dfbe9925973bc1b070d3"}, + {file = "triton-3.5.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e6bb9aa5519c084a333acdba443789e50012a4b851cd486c54f0b8dc2a8d3a12"}, + {file = "triton-3.5.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:03127d9b33aaf979c856676b394bc059ec1d68cb6da68ae03f62dd8ad77a04ae"}, + {file = "triton-3.5.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c83f2343e1a220a716c7b3ab9fccfcbe3ad4020d189549200e2d2e8d5868bed9"}, + {file = "triton-3.5.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:468936651d383f4a6d10068d34a627505e13af55be5d002b9f27b987e7a5f0ac"}, + {file = "triton-3.5.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da0fa67ccd76c3dcfb0bffe1b1c57c685136a6bd33d141c24d9655d4185b1289"}, + {file = "triton-3.5.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7ceef21410229ac23173a28eee5cfc0e37c1dfdb8b4bc11ecda2e3ecec7c686"}, + {file = "triton-3.5.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:317fe477ea8fd4524a6a8c499fb0a36984a56d0b75bf9c9cb6133a1c56d5a6e7"}, +] + +[package.extras] +build = ["cmake (>=3.20,<4.0)", "lit"] +tests = ["autopep8", "isort", "llnl-hatchet", "numpy", "pytest", "pytest-forked", "pytest-xdist", "scipy (>=1.7.1)"] +tutorials = ["matplotlib", "pandas", "tabulate"] + [[package]] name = "types-python-dateutil" version = "2.9.0.20251008" @@ -3383,7 +4861,151 @@ files = [ {file = "widgetsnbextension-4.0.14.tar.gz", hash = "sha256:a3629b04e3edb893212df862038c7232f62973373869db5084aed739b437b5af"}, ] +[[package]] +name = "yarl" +version = "1.22.0" +description = "Yet another URL library" +optional = false +python-versions = ">=3.9" +files = [ + {file = "yarl-1.22.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c7bd6683587567e5a49ee6e336e0612bec8329be1b7d4c8af5687dcdeb67ee1e"}, + {file = "yarl-1.22.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5cdac20da754f3a723cceea5b3448e1a2074866406adeb4ef35b469d089adb8f"}, + {file = "yarl-1.22.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07a524d84df0c10f41e3ee918846e1974aba4ec017f990dc735aad487a0bdfdf"}, + {file = "yarl-1.22.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1b329cb8146d7b736677a2440e422eadd775d1806a81db2d4cded80a48efc1a"}, + {file = "yarl-1.22.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:75976c6945d85dbb9ee6308cd7ff7b1fb9409380c82d6119bd778d8fcfe2931c"}, + {file = "yarl-1.22.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:80ddf7a5f8c86cb3eb4bc9028b07bbbf1f08a96c5c0bc1244be5e8fefcb94147"}, + {file = "yarl-1.22.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d332fc2e3c94dad927f2112395772a4e4fedbcf8f80efc21ed7cdfae4d574fdb"}, + {file = "yarl-1.22.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0cf71bf877efeac18b38d3930594c0948c82b64547c1cf420ba48722fe5509f6"}, + {file = "yarl-1.22.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:663e1cadaddae26be034a6ab6072449a8426ddb03d500f43daf952b74553bba0"}, + {file = "yarl-1.22.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:6dcbb0829c671f305be48a7227918cfcd11276c2d637a8033a99a02b67bf9eda"}, + {file = "yarl-1.22.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f0d97c18dfd9a9af4490631905a3f131a8e4c9e80a39353919e2cfed8f00aedc"}, + {file = "yarl-1.22.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:437840083abe022c978470b942ff832c3940b2ad3734d424b7eaffcd07f76737"}, + {file = "yarl-1.22.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a899cbd98dce6f5d8de1aad31cb712ec0a530abc0a86bd6edaa47c1090138467"}, + {file = "yarl-1.22.0-cp310-cp310-win32.whl", hash = "sha256:595697f68bd1f0c1c159fcb97b661fc9c3f5db46498043555d04805430e79bea"}, + {file = "yarl-1.22.0-cp310-cp310-win_amd64.whl", hash = "sha256:cb95a9b1adaa48e41815a55ae740cfda005758104049a640a398120bf02515ca"}, + {file = "yarl-1.22.0-cp310-cp310-win_arm64.whl", hash = "sha256:b85b982afde6df99ecc996990d4ad7ccbdbb70e2a4ba4de0aecde5922ba98a0b"}, + {file = "yarl-1.22.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ab72135b1f2db3fed3997d7e7dc1b80573c67138023852b6efb336a5eae6511"}, + {file = "yarl-1.22.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:669930400e375570189492dc8d8341301578e8493aec04aebc20d4717f899dd6"}, + {file = "yarl-1.22.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:792a2af6d58177ef7c19cbf0097aba92ca1b9cb3ffdd9c7470e156c8f9b5e028"}, + {file = "yarl-1.22.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ea66b1c11c9150f1372f69afb6b8116f2dd7286f38e14ea71a44eee9ec51b9d"}, + {file = "yarl-1.22.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3e2daa88dc91870215961e96a039ec73e4937da13cf77ce17f9cad0c18df3503"}, + {file = "yarl-1.22.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba440ae430c00eee41509353628600212112cd5018d5def7e9b05ea7ac34eb65"}, + {file = "yarl-1.22.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e6438cc8f23a9c1478633d216b16104a586b9761db62bfacb6425bac0a36679e"}, + {file = "yarl-1.22.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c52a6e78aef5cf47a98ef8e934755abf53953379b7d53e68b15ff4420e6683d"}, + {file = "yarl-1.22.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3b06bcadaac49c70f4c88af4ffcfbe3dc155aab3163e75777818092478bcbbe7"}, + {file = "yarl-1.22.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:6944b2dc72c4d7f7052683487e3677456050ff77fcf5e6204e98caf785ad1967"}, + {file = "yarl-1.22.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d5372ca1df0f91a86b047d1277c2aaf1edb32d78bbcefffc81b40ffd18f027ed"}, + {file = "yarl-1.22.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:51af598701f5299012b8416486b40fceef8c26fc87dc6d7d1f6fc30609ea0aa6"}, + {file = "yarl-1.22.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b266bd01fedeffeeac01a79ae181719ff848a5a13ce10075adbefc8f1daee70e"}, + {file = "yarl-1.22.0-cp311-cp311-win32.whl", hash = "sha256:a9b1ba5610a4e20f655258d5a1fdc7ebe3d837bb0e45b581398b99eb98b1f5ca"}, + {file = "yarl-1.22.0-cp311-cp311-win_amd64.whl", hash = "sha256:078278b9b0b11568937d9509b589ee83ef98ed6d561dfe2020e24a9fd08eaa2b"}, + {file = "yarl-1.22.0-cp311-cp311-win_arm64.whl", hash = "sha256:b6a6f620cfe13ccec221fa312139135166e47ae169f8253f72a0abc0dae94376"}, + {file = "yarl-1.22.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e340382d1afa5d32b892b3ff062436d592ec3d692aeea3bef3a5cfe11bbf8c6f"}, + {file = "yarl-1.22.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f1e09112a2c31ffe8d80be1b0988fa6a18c5d5cad92a9ffbb1c04c91bfe52ad2"}, + {file = "yarl-1.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:939fe60db294c786f6b7c2d2e121576628468f65453d86b0fe36cb52f987bd74"}, + {file = "yarl-1.22.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1651bf8e0398574646744c1885a41198eba53dc8a9312b954073f845c90a8df"}, + {file = "yarl-1.22.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b8a0588521a26bf92a57a1705b77b8b59044cdceccac7151bd8d229e66b8dedb"}, + {file = "yarl-1.22.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:42188e6a615c1a75bcaa6e150c3fe8f3e8680471a6b10150c5f7e83f47cc34d2"}, + {file = "yarl-1.22.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f6d2cb59377d99718913ad9a151030d6f83ef420a2b8f521d94609ecc106ee82"}, + {file = "yarl-1.22.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50678a3b71c751d58d7908edc96d332af328839eea883bb554a43f539101277a"}, + {file = "yarl-1.22.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e8fbaa7cec507aa24ea27a01456e8dd4b6fab829059b69844bd348f2d467124"}, + {file = "yarl-1.22.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:433885ab5431bc3d3d4f2f9bd15bfa1614c522b0f1405d62c4f926ccd69d04fa"}, + {file = "yarl-1.22.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b790b39c7e9a4192dc2e201a282109ed2985a1ddbd5ac08dc56d0e121400a8f7"}, + {file = "yarl-1.22.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31f0b53913220599446872d757257be5898019c85e7971599065bc55065dc99d"}, + {file = "yarl-1.22.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a49370e8f711daec68d09b821a34e1167792ee2d24d405cbc2387be4f158b520"}, + {file = "yarl-1.22.0-cp312-cp312-win32.whl", hash = "sha256:70dfd4f241c04bd9239d53b17f11e6ab672b9f1420364af63e8531198e3f5fe8"}, + {file = "yarl-1.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:8884d8b332a5e9b88e23f60bb166890009429391864c685e17bd73a9eda9105c"}, + {file = "yarl-1.22.0-cp312-cp312-win_arm64.whl", hash = "sha256:ea70f61a47f3cc93bdf8b2f368ed359ef02a01ca6393916bc8ff877427181e74"}, + {file = "yarl-1.22.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8dee9c25c74997f6a750cd317b8ca63545169c098faee42c84aa5e506c819b53"}, + {file = "yarl-1.22.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01e73b85a5434f89fc4fe27dcda2aff08ddf35e4d47bbbea3bdcd25321af538a"}, + {file = "yarl-1.22.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:22965c2af250d20c873cdbee8ff958fb809940aeb2e74ba5f20aaf6b7ac8c70c"}, + {file = "yarl-1.22.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4f15793aa49793ec8d1c708ab7f9eded1aa72edc5174cae703651555ed1b601"}, + {file = "yarl-1.22.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5542339dcf2747135c5c85f68680353d5cb9ffd741c0f2e8d832d054d41f35a"}, + {file = "yarl-1.22.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5c401e05ad47a75869c3ab3e35137f8468b846770587e70d71e11de797d113df"}, + {file = "yarl-1.22.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:243dda95d901c733f5b59214d28b0120893d91777cb8aa043e6ef059d3cddfe2"}, + {file = "yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bec03d0d388060058f5d291a813f21c011041938a441c593374da6077fe21b1b"}, + {file = "yarl-1.22.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0748275abb8c1e1e09301ee3cf90c8a99678a4e92e4373705f2a2570d581273"}, + {file = "yarl-1.22.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:47fdb18187e2a4e18fda2c25c05d8251a9e4a521edaed757fef033e7d8498d9a"}, + {file = "yarl-1.22.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c7044802eec4524fde550afc28edda0dd5784c4c45f0be151a2d3ba017daca7d"}, + {file = "yarl-1.22.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:139718f35149ff544caba20fce6e8a2f71f1e39b92c700d8438a0b1d2a631a02"}, + {file = "yarl-1.22.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e1b51bebd221006d3d2f95fbe124b22b247136647ae5dcc8c7acafba66e5ee67"}, + {file = "yarl-1.22.0-cp313-cp313-win32.whl", hash = "sha256:d3e32536234a95f513bd374e93d717cf6b2231a791758de6c509e3653f234c95"}, + {file = "yarl-1.22.0-cp313-cp313-win_amd64.whl", hash = "sha256:47743b82b76d89a1d20b83e60d5c20314cbd5ba2befc9cda8f28300c4a08ed4d"}, + {file = "yarl-1.22.0-cp313-cp313-win_arm64.whl", hash = "sha256:5d0fcda9608875f7d052eff120c7a5da474a6796fe4d83e152e0e4d42f6d1a9b"}, + {file = "yarl-1.22.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:719ae08b6972befcba4310e49edb1161a88cdd331e3a694b84466bd938a6ab10"}, + {file = "yarl-1.22.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:47d8a5c446df1c4db9d21b49619ffdba90e77c89ec6e283f453856c74b50b9e3"}, + {file = "yarl-1.22.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cfebc0ac8333520d2d0423cbbe43ae43c8838862ddb898f5ca68565e395516e9"}, + {file = "yarl-1.22.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4398557cbf484207df000309235979c79c4356518fd5c99158c7d38203c4da4f"}, + {file = "yarl-1.22.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2ca6fd72a8cd803be290d42f2dec5cdcd5299eeb93c2d929bf060ad9efaf5de0"}, + {file = "yarl-1.22.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca1f59c4e1ab6e72f0a23c13fca5430f889634166be85dbf1013683e49e3278e"}, + {file = "yarl-1.22.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c5010a52015e7c70f86eb967db0f37f3c8bd503a695a49f8d45700144667708"}, + {file = "yarl-1.22.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d7672ecf7557476642c88497c2f8d8542f8e36596e928e9bcba0e42e1e7d71f"}, + {file = "yarl-1.22.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3b7c88eeef021579d600e50363e0b6ee4f7f6f728cd3486b9d0f3ee7b946398d"}, + {file = "yarl-1.22.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f4afb5c34f2c6fecdcc182dfcfc6af6cccf1aa923eed4d6a12e9d96904e1a0d8"}, + {file = "yarl-1.22.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:59c189e3e99a59cf8d83cbb31d4db02d66cda5a1a4374e8a012b51255341abf5"}, + {file = "yarl-1.22.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:5a3bf7f62a289fa90f1990422dc8dff5a458469ea71d1624585ec3a4c8d6960f"}, + {file = "yarl-1.22.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:de6b9a04c606978fdfe72666fa216ffcf2d1a9f6a381058d4378f8d7b1e5de62"}, + {file = "yarl-1.22.0-cp313-cp313t-win32.whl", hash = "sha256:1834bb90991cc2999f10f97f5f01317f99b143284766d197e43cd5b45eb18d03"}, + {file = "yarl-1.22.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff86011bd159a9d2dfc89c34cfd8aff12875980e3bd6a39ff097887520e60249"}, + {file = "yarl-1.22.0-cp313-cp313t-win_arm64.whl", hash = "sha256:7861058d0582b847bc4e3a4a4c46828a410bca738673f35a29ba3ca5db0b473b"}, + {file = "yarl-1.22.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:34b36c2c57124530884d89d50ed2c1478697ad7473efd59cfd479945c95650e4"}, + {file = "yarl-1.22.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:0dd9a702591ca2e543631c2a017e4a547e38a5c0f29eece37d9097e04a7ac683"}, + {file = "yarl-1.22.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:594fcab1032e2d2cc3321bb2e51271e7cd2b516c7d9aee780ece81b07ff8244b"}, + {file = "yarl-1.22.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3d7a87a78d46a2e3d5b72587ac14b4c16952dd0887dbb051451eceac774411e"}, + {file = "yarl-1.22.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:852863707010316c973162e703bddabec35e8757e67fcb8ad58829de1ebc8590"}, + {file = "yarl-1.22.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:131a085a53bfe839a477c0845acf21efc77457ba2bcf5899618136d64f3303a2"}, + {file = "yarl-1.22.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:078a8aefd263f4d4f923a9677b942b445a2be970ca24548a8102689a3a8ab8da"}, + {file = "yarl-1.22.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bca03b91c323036913993ff5c738d0842fc9c60c4648e5c8d98331526df89784"}, + {file = "yarl-1.22.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:68986a61557d37bb90d3051a45b91fa3d5c516d177dfc6dd6f2f436a07ff2b6b"}, + {file = "yarl-1.22.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:4792b262d585ff0dff6bcb787f8492e40698443ec982a3568c2096433660c694"}, + {file = "yarl-1.22.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ebd4549b108d732dba1d4ace67614b9545b21ece30937a63a65dd34efa19732d"}, + {file = "yarl-1.22.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f87ac53513d22240c7d59203f25cc3beac1e574c6cd681bbfd321987b69f95fd"}, + {file = "yarl-1.22.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:22b029f2881599e2f1b06f8f1db2ee63bd309e2293ba2d566e008ba12778b8da"}, + {file = "yarl-1.22.0-cp314-cp314-win32.whl", hash = "sha256:6a635ea45ba4ea8238463b4f7d0e721bad669f80878b7bfd1f89266e2ae63da2"}, + {file = "yarl-1.22.0-cp314-cp314-win_amd64.whl", hash = "sha256:0d6e6885777af0f110b0e5d7e5dda8b704efed3894da26220b7f3d887b839a79"}, + {file = "yarl-1.22.0-cp314-cp314-win_arm64.whl", hash = "sha256:8218f4e98d3c10d683584cb40f0424f4b9fd6e95610232dd75e13743b070ee33"}, + {file = "yarl-1.22.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:45c2842ff0e0d1b35a6bf1cd6c690939dacb617a70827f715232b2e0494d55d1"}, + {file = "yarl-1.22.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d947071e6ebcf2e2bee8fce76e10faca8f7a14808ca36a910263acaacef08eca"}, + {file = "yarl-1.22.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:334b8721303e61b00019474cc103bdac3d7b1f65e91f0bfedeec2d56dfe74b53"}, + {file = "yarl-1.22.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e7ce67c34138a058fd092f67d07a72b8e31ff0c9236e751957465a24b28910c"}, + {file = "yarl-1.22.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d77e1b2c6d04711478cb1c4ab90db07f1609ccf06a287d5607fcd90dc9863acf"}, + {file = "yarl-1.22.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4647674b6150d2cae088fc07de2738a84b8bcedebef29802cf0b0a82ab6face"}, + {file = "yarl-1.22.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efb07073be061c8f79d03d04139a80ba33cbd390ca8f0297aae9cce6411e4c6b"}, + {file = "yarl-1.22.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e51ac5435758ba97ad69617e13233da53908beccc6cfcd6c34bbed8dcbede486"}, + {file = "yarl-1.22.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:33e32a0dd0c8205efa8e83d04fc9f19313772b78522d1bdc7d9aed706bfd6138"}, + {file = "yarl-1.22.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:bf4a21e58b9cde0e401e683ebd00f6ed30a06d14e93f7c8fd059f8b6e8f87b6a"}, + {file = "yarl-1.22.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:e4b582bab49ac33c8deb97e058cd67c2c50dac0dd134874106d9c774fd272529"}, + {file = "yarl-1.22.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0b5bcc1a9c4839e7e30b7b30dd47fe5e7e44fb7054ec29b5bb8d526aa1041093"}, + {file = "yarl-1.22.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c0232bce2170103ec23c454e54a57008a9a72b5d1c3105dc2496750da8cfa47c"}, + {file = "yarl-1.22.0-cp314-cp314t-win32.whl", hash = "sha256:8009b3173bcd637be650922ac455946197d858b3630b6d8787aa9e5c4564533e"}, + {file = "yarl-1.22.0-cp314-cp314t-win_amd64.whl", hash = "sha256:9fb17ea16e972c63d25d4a97f016d235c78dd2344820eb35bc034bc32012ee27"}, + {file = "yarl-1.22.0-cp314-cp314t-win_arm64.whl", hash = "sha256:9f6d73c1436b934e3f01df1e1b21ff765cd1d28c77dfb9ace207f746d4610ee1"}, + {file = "yarl-1.22.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3aa27acb6de7a23785d81557577491f6c38a5209a254d1191519d07d8fe51748"}, + {file = "yarl-1.22.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:af74f05666a5e531289cb1cc9c883d1de2088b8e5b4de48004e5ca8a830ac859"}, + {file = "yarl-1.22.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:62441e55958977b8167b2709c164c91a6363e25da322d87ae6dd9c6019ceecf9"}, + {file = "yarl-1.22.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b580e71cac3f8113d3135888770903eaf2f507e9421e5697d6ee6d8cd1c7f054"}, + {file = "yarl-1.22.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e81fda2fb4a07eda1a2252b216aa0df23ebcd4d584894e9612e80999a78fd95b"}, + {file = "yarl-1.22.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:99b6fc1d55782461b78221e95fc357b47ad98b041e8e20f47c1411d0aacddc60"}, + {file = "yarl-1.22.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:088e4e08f033db4be2ccd1f34cf29fe994772fb54cfe004bbf54db320af56890"}, + {file = "yarl-1.22.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e4e1f6f0b4da23e61188676e3ed027ef0baa833a2e633c29ff8530800edccba"}, + {file = "yarl-1.22.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:84fc3ec96fce86ce5aa305eb4aa9358279d1aa644b71fab7b8ed33fe3ba1a7ca"}, + {file = "yarl-1.22.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5dbeefd6ca588b33576a01b0ad58aa934bc1b41ef89dee505bf2932b22ddffba"}, + {file = "yarl-1.22.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:14291620375b1060613f4aab9ebf21850058b6b1b438f386cc814813d901c60b"}, + {file = "yarl-1.22.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:a4fcfc8eb2c34148c118dfa02e6427ca278bfd0f3df7c5f99e33d2c0e81eae3e"}, + {file = "yarl-1.22.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:029866bde8d7b0878b9c160e72305bbf0a7342bcd20b9999381704ae03308dc8"}, + {file = "yarl-1.22.0-cp39-cp39-win32.whl", hash = "sha256:4dcc74149ccc8bba31ce1944acee24813e93cfdee2acda3c172df844948ddf7b"}, + {file = "yarl-1.22.0-cp39-cp39-win_amd64.whl", hash = "sha256:10619d9fdee46d20edc49d3479e2f8269d0779f1b031e6f7c2aa1c76be04b7ed"}, + {file = "yarl-1.22.0-cp39-cp39-win_arm64.whl", hash = "sha256:dd7afd3f8b0bfb4e0d9fc3c31bfe8a4ec7debe124cfd90619305def3c8ca8cd2"}, + {file = "yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff"}, + {file = "yarl-1.22.0.tar.gz", hash = "sha256:bebf8557577d4401ba8bd9ff33906f1376c877aa78d1fe216ad01b4d6745af71"}, +] + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" +propcache = ">=0.2.1" + [metadata] lock-version = "2.0" python-versions = ">=3.11,<3.14" -content-hash = "aa48a1f57b52ca8a5d01bc984b656d3c87d58d8387fb80e7bc33db660fd7d83d" +content-hash = "c75bc898212f8e4dc92569b94b8c35c084bccaed12d83bd1287403f9bc3f5b3d" diff --git a/pyproject.toml b/pyproject.toml index 5ad77ec..1b40694 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,9 @@ seaborn = "^0.13.2" jupyter = "^1.1.1" statsmodels = "^0.14.5" scikit-learn = "^1.7.2" +lightgbm = "^4.6.0" +prophetverse = {version = "^0.10.0", python = "<3.12,>=3.9"} +pytorch-forecasting = "^1.5.0" [tool.poetry.group.dev.dependencies] diff --git a/reduction.ipynb b/reduction.ipynb deleted file mode 100644 index 54127de..0000000 --- a/reduction.ipynb +++ /dev/null @@ -1,312 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Modelos de Machine Learning" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
promo
date
2020-01-010.0
2020-01-020.0
2020-01-030.0
2020-01-040.0
2020-01-050.0
......
2024-07-011.0
2024-07-020.0
2024-07-030.0
2024-07-040.0
2024-07-050.0
\n", - "

1648 rows × 1 columns

\n", - "
" - ], - "text/plain": [ - " promo\n", - "date \n", - "2020-01-01 0.0\n", - "2020-01-02 0.0\n", - "2020-01-03 0.0\n", - "2020-01-04 0.0\n", - "2020-01-05 0.0\n", - "... ...\n", - "2024-07-01 1.0\n", - "2024-07-02 0.0\n", - "2024-07-03 0.0\n", - "2024-07-04 0.0\n", - "2024-07-05 0.0\n", - "\n", - "[1648 rows x 1 columns]" - ] - }, - "execution_count": 1, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from tsbook.datasets.retail import SyntheticRetail\n", - "from sktime.utils.plotting import plot_series\n", - "from sktime.forecasting.naive import NaiveForecaster\n", - "\n", - "dataset = SyntheticRetail(\"univariate\")\n", - "y_train, X_train, y_test, X_test = dataset.load(\n", - " \"y_train\", \"X_train\", \"y_test\", \"X_test\"\n", - ")\n", - "\n", - "X_train" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "from tsbook.forecasting.global_reduction import GlobalReductionForecaster\n", - "from sklearn.ensemble import RandomForestRegressor\n", - "from sktime.transformations.series.difference import Differencer\n", - "\n", - "regressor = RandomForestRegressor(n_estimators=100, random_state=42)\n", - "model = Differencer() * GlobalReductionForecaster(\n", - " regressor,\n", - " window_length=30,\n", - " steps_ahead=1,\n", - ")\n", - "\n", - "model.fit(y_train, X=X_train)\n", - "y_pred = model.predict(fh=y_test.index, X=X_test)" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(
, )" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "plot_series(\n", - " y_train, y_test, y_pred, labels=[\"Treino\", \"Teste\", \"Previsão com ML + Diferença\"]\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\n", - "from typing import Callable, Tuple\n", - "\n", - "model = GlobalReductionForecaster(\n", - " regressor,\n", - " window_length=30,\n", - " steps_ahead=1,\n", - " normalization_strategy=\"divide_mean\",\n", - ")\n", - "\n", - "model.fit(y_train, X=X_train)\n", - "y_pred = model.predict(fh=y_test.index, X=X_test)" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(
, )" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAABSwAAAFfCAYAAABEEoKYAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjcsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvTLEjVAAAAAlwSFlzAAAPYQAAD2EBqD+naQAApwxJREFUeJzt3QecVPXV//Ezs32XXXZBujRBUBBBsSZGsUESsJuosSWPSf4mltg1UbEnauw9eUzUJ2pssWIi2LBrbChFUQEp0nVhYfvu3P/r/Gbv7J07987c2Z3tn/frNZl2586dhuHLOb8TsizLEgAAAAAAAADoBMIdfQAAAAAAAAAAYCOwBAAAAAAAANBpEFgCAAAAAAAA6DQILAEAAAAAAAB0GgSWAAAAAAAAADoNAksAAAAAAAAAnQaBJQAAAAAAAIBOI7ujD6AriEQisnr1aikuLpZQKNTRhwMAAAAAAAB0KZZlyZYtW2Tw4MESDievoSSwDEDDyqFDh2bq8wEAAAAAAAB6pJUrV8q2226bdBsCywC0stJ+Q0tKSjLz6QAAAAAAAAA9REVFhSkItHO2ZAgsA7DbwDWsJLAEAAAAAAAAWibIcosM3QEAAAAAAADQaRBYAgAAAAAAAOg0CCwBAAAAAAAAdBqsYZlBjY2NUl9fn8ldAgDaUE5OjmRlZfEeAwAAAEAnQmCZAZZlydq1a2XTpk2Z2B0AoB2VlpbKwIEDAy38DAAAAABoewSWGWCHlf3795fCwkL+0gsAXeQfm6qqqmT9+vXm+qBBgzr6kAAAAAAABJaZaQO3w8q+ffvypQKALqSgoMCca2ipf47THg4AAAAAHY+hO61kr1mplZUAgK7H/vObNYgBAAAAoHMgsMwQ1j4DgK6JP78BAAAAZFrlV0/Jqgcny9e3F5tzvY7gCCwBAAAAAACADNFwcv2sY6R+4wKxGmvNuV4ntAyOwBIAAAAAAADIkPJ3r9ZeLh312XSLnodk07vX8B4HRGCJjJk7d65prdQhRAAAAAAAAD1RQ/kXjrDSZkl9+eIOOqKuh8Cyh9JgMdnp8ssvT3uf3/ve92TNmjXSu3fvNjlmAAAAAACAzi67bExThWW8rMKBHXI8XVF2Rx8Amj05f41cOWexfLGhUsb0K5KZU8fKkRMGtclbpMGi7dFHH5WZM2fK4sXNSX+vXr1ily3LksbGRsnOTv51yc3NlYED+fEBAAAAAICeq2yvS8yalW4NW5abdSyLRh/RIcfVlVBh2QY04KusbUjr9PBHq+ToBz6Q+Wu2SE1DxJzrdb09nf3ocwehwaJ90opIraq0r3/++edSXFws//nPf2Ty5MmSl5cnb775pkQiEfnTn/4kI0eOlIKCApk4caI88cQTvi3h999/v5SWlsrs2bNlxx13NCHoD3/4w7iwVPd55ZVXyrbbbmueZ9KkSfLCCy+0wacCAAAAAADQ9jSQzCoe5nEP61gGRYVlG6iqa5Tii//Tosc6l2NVJzz8cVqP33LNj6QoLzMf60UXXSQ33HCDbLfddlJWVmbCygcffFDuuece2X777eX111+XE044Qfr16yf77bef5z6qqqrMPv7xj39IOBw225933nny0EMPmftvvfVWufHGG+Uvf/mL7LLLLvL3v/9dDj30UFm4cKF5DgAAAAAAgK4mUrXO41bWsQyKwBK+tPLx4IMPNpdra2vlj3/8o7z00kuy9957m9s0yNTKSw0b/QLL+vp6E3COGjXKXD/99NPNfm0aZl544YVy7LHHmuvXXXedvPrqq3LLLbfInXfeyacDAAAAAAC65DqW9Rvnu24NSU7Z2A46oq6FwLINFOZmmUrHdOx9+5uycO2WuBlSoZDITgOK5e0z9knruTNlt912i13+6quvTLWkHWDa6urqTGWk7/EUFsbCSjVo0CBZv369uVxRUSGrV6+W73//+3GP0euffPJJxl4HAAAAAABAx65jqUN4LCnd6xI+iAAILNuAruOYblv25dPGmjUrNaTUZSjtc709Uy3e6SoqKopd3rp1qzl//vnnZciQIXHb6dqTfnJychLem6DrbAIAAAAAAHTVdSyzS0ZKQ8Uycz2nz45S9r3LpWj04R19aF0CQ3c6CZ0G/sTJu8nOA0skPztszv918m5yRBtNCU/XuHHjTDC5YsUKGT16dNxp6NChLdpnSUmJDB48WN5666242/W6Ph8AAAAAAEBXFc4vjV0eePizhJVpoMKyk4WWeuqMdGq4Dss5++yzzWTvffbZRzZv3mzCRQ0eTz755Bbt9/zzz5fLLrvMtI3rhPD77rtP5s2bFxvKAwAAAAAA0CVZkeaLjbUdeihdDYElArvqqqvMRHCdFr506VIpLS2VXXfdVf7whz+0+F0888wzTfB57rnnmrUttbLy2WefZUI4AAAAAADoRoFlTYceSlcTslhQMCUdDtO7d28TrGk1oVNNTY0sW7ZMRo4cKfn5+W33SQEA2gR/jgMAAABoC6v+MUnqv11kLg8+9m3JG9g83LgnqkiSr7mxhiUAAAAAAACQaVRYthiBJQAAAAAAAJBprGHZYgSWAAAAAAAAQIZZzsCygTUs00FgCQAAAAAAAGQaLeFdM7C8/PLLJRQKxZ122GGHuEEIp512mvTt21d69eolRx11lKxbty5uHytWrJDp06dLYWGh9O/fX84//3xpaGiI22bu3LlmmnVeXp6MHj1a7r///nZ7jQAAAAAAAOiBqLDsuhWW48ePlzVr1sROb775Zuy+s88+W5577jl5/PHH5bXXXpPVq1fLkUceGbu/sbHRhJV1dXXy9ttvywMPPGDCyJkzZ8a20Qneus3+++8v8+bNk7POOkt++ctfyuzZs9v9tQIAAAAAAKAHtoQ31nbosXQ12R1+ANnZMnDgwITbdcT53/72N3n44YflgAMOMLfdd999suOOO8q7774re+21l8yZM0cWLVokL730kgwYMEAmTZokV111lVx44YWmejM3N1fuueceGTlypNx4441mH/p4DUVvvvlmmTZtmucx1dbWmpNz7DoAAAAAAAAQGIFl162w/PLLL2Xw4MGy3XbbyfHHH29avNWHH34o9fX1ctBBB8W21XbxYcOGyTvvvGOu6/mECRNMWGnTEFIDxoULF8a2ce7D3sbeh5c//elP0rt379hp6NChGX/dAAAAAAAA6MasxuaLDN3pOoHlnnvuaVq4X3jhBbn77rtN+/YPfvAD2bJli6xdu9ZUSJaWlsY9RsNJvU/puTOstO+370u2jYaa1dXVnsf1+9//3lR42qeVK1dm9HUDAAAAAACgm2PoTtcMLH/0ox/JT37yE9l5551N1eO///1v2bRpkzz22GMdeVhmOE9JSUncqbtxDztyn7SlvjX7fvrppzN6vAAAAAAAAF11Dcvyd66UVQ9OlsqvnurQY+oqOrwl3EmrKceMGSNfffWVWddSh+logOmkU8LtNS/13D013L6eahsNIQsKCqSncg46uuWWW8z74bztvPPO6+hDBAAAAAAAaHMaImqY+PXtxRkNFa0GR2ev1Sj1GxfI+lnHEFp2tcBy69atsmTJEhk0aJBMnjxZcnJy5OWXX47dv3jxYrPG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lpdffrndjrmr+Prrr8372r9/f9myZUvcfRrwOj/3zu7999+XX//61+32fN35zwQAAAAA7c9qbA4sLSsza026W8BpCU8PgWUP98Mf/tAETV9++aWce+65JiT585//7LltXZ3jXxxaoaCgwIQ0QT344INyyCGHyN/+9jf56quvZI899pDuZOjQoabq1Ombb74xId+gQYMy+lwaeLqfK+h3RAO2//znP7L//vvL7373O5kxY0ZcuD1w4EDJy8uLXV+yZInss88+Mnz4cOnbt2+LjjdT37n2MHfuXFNpmC4NK2+44YaMHktjY2NCoNyW+vXrJ4WFhe32fN39zwQAAAAAHbiGZaZat2kJbxUCy07kya/nyy5P3yiF/3eROdfrbU0DJg2aNFTSSqWDDjpInn322Vh13eGHHy7XXHONDB48WMaOHWtuX7lypfz0pz81bdp9+vSRww47zIRZas6cOaYFeNOmTXHPowHXAQcc4NkS/sknn5gQrLi4WEpKSmTy5MnywQcfmPu+/fZbOe6442TIkCEmEJkwYYL885//jNt3bW2tnHnmmSYE1efWkEwrvpLRx1x44YUmLNT3QKtLNfywvfbaayYE0fs0NLzoooviwjkN/s444wxT7VhWVmYqD//3f/9XKisr5Re/+IV5LbpPDfhS0eBv48aN8tZbb8Vue+CBB2Tq1KlpBbtt/R3Rz2DXXXeVP/zhD/LMM8+Y1+YMP50t4Xr5ww8/NBW7zirRZN+dln7nnI/T4E8/Lw1IdemA+vr6QJ+5BnynnHKKjBw50gTq+ry33nprO7y7Yr5HN910k6xfv953m/LycjnppJPMd01/B1rJqv/IYLN/U/rbHTdunHl9K1asMAHq1VdfbR7bq1cv8zvXbTZs2GDeQ71t5513jv3egv7m3Jwt4XosdlWu82R/B/S3qVW622yzjfTu3Vv2228/+eijj+L2p39+/L//9//M70p/0zvttJPMmjWrTf9MAAAAANBzOdewFKuNAktawtNCYNkGLMuSyvratE7/XPKR/OTVB2R++RqpbWww53pdb09nP/rcraFhjbOqTav8tKVX147UwEADoGnTpplA7o033jAhm4YeWoWnjzvwwANNcPKvf/0rtg8Ngx599FE5/vjjPZ9Tb992221NoKAhl4aD2n6sampqTID5/PPPy4IFC0zb6Yknnij//e9/Y4+/4IILzPNpyKfBhwZReozfffed7+vUAEdDjttuu00+++wz+ctf/mJeh13d+OMf/1h23313E6befffdJtjS4MdJn09DFz0WDZ008P3JT34i3/ve98xxaOCox1pVVZX0Pdd2fH0P7rvvvthtGvr8z//8j3RWGj5PnDjRtOZ60YrM8ePHm6pdvXzeeeel/O609Dtne/XVV01Vp57rZ6PvoTNQTfaZazWifgcff/xxWbRokcycOdMEs4899pi0NQ3f7OUY/Gggq6Giho3vvPOO+Z3rd9QZyOr37LrrrpN7771XFi5cGAu7b775Zvn+978vH3/8sVlmQL+T+l6ccMIJ5ns6atQoc93+syPIby7Vkg/6mdsnfc+zs7PNMdgVpdrC/eabb8q7774r22+/vXktdlu8fhYayOrnrJWU+nnokglZWVlt+mcCAAAAgB4s0gYt4axh2SrZrXs4vFQ11EnJgxe36M2xXOcnvP5wWo+vOOEaKcrJS/95LcsERbNnzzbhm03XINQAREM1pQGCBgp6m1ZNKQ3aNKTUllgN6Y499lh5+OGHTcWa0v1qxdRRRx3l+dxaCXb++efLDjvsYK5rgGHTKioNu2x6bHqMGiRpBaRWNGqgqMGUvX6iVjpq2KUho+7XTdfP1MfrNlpRqrbbbrvY/XfddZepwrvjjjvMa9Tj0rXytDpPg6xwOJrza2B3ySWXmMu///3vTaiiAeavfvUrc5tuq8f26aefmuEzyWg4+YMf/MBU9Wlou3nzZlN52ZnXMdT3RV+bF63I1JBKA0G9HPS709LvnNLqQ/3MNNjSY9NwTr97+nmk+sw1IL/iiiti17XSUoNBfYxWdrYlew1TbXE+++yzTYDopJWUGlRqgKdhuHrooYfMd1QrWjUkVxpe6ndXv5dOGgZqtaLzO6lhvP04/V7vvffesm7dulglbbLfXJB/9NCT0gBZK13/+Mc/mqpKZVda2/7617+az1KrmvU7/9JLL5nwUUPlMWPGJHxWbfFnAgAAAICeLa7CMmPTvF1rWGaqcrOHILDs4bSCTUMlDTs0FPrZz34WF5Jpu6UdHCmtONQ147TazUmrnjScUFotqAGdhnza1qvhioZHfpPBzznnHPnlL39phmhomKRBih3aaHWmhh0aRmjlo1bUabunvV6ePqceu129ZYdPGlxo4OFl3rx5JtTSVlQv+jgNcOxwTOn+t27dKqtWrZJhw4aZ27SV1qb70zZkfb9s2s6qkrX62jRk0qD2iSeeMBWCWjGmgV9r6XunJ1t1dbWpajv99NNjt2kFm/2a0g25ne9RKkG+Oy39zimt6LSr8JS2hs+fPz/QZ67uvPNOM0xIA3R9n/S7lmq6uV2haX9X9bvpvE2rGO+55x5JRav/tG350ksvNWG/+/uo34U999wzdpt+17Rt3fkd1/fM+Z20OW+zv5N+31MNLFP95oKyQ3f97TtDQg1GNejXsFmfU59Pq0P1fbc/K612tcNKt7b4MwEAAABAzxY/dCdDg4hpCW8VAss2UJidayod0/G9WbfLwk1rY5WVKiQh2alsgLw1/Yy0njsdunakViNp2KHhojsk02o3Jw3ttB1TQ0ivwRtKq7c0cHzkkUdMm/RTTz2VdNCLBqQalGqLp66LqNOl9bFHHHGEGQCkVYe6Pp6GLHo8um5ka4ax2NVfrWW3rdvsSdrO6yro8BOtstTQTAPEoO23qZx66qlxFYIaJmul65FHHhm7TT/3ltDwRysRgwry3Wnpd87v87Df+1SfuX7ftGrvxhtvNGG1hqP63XvvvfeSPk7DNZtuq9WKGsTZdE3WoLTKUp+7pRWA+hq9AmSv72Sy72kmfnMaKmpruL5+raB00nZwXYdSn0PX1NT1NvV12/tP9Vm1xZ8JAAAAAHo2K9IGFZYElq1CYNkGNABIty37sl2mmTUrNaS0xIqd6+0tafEOSv+yr+u7BaVDV3Q9Sl0fL1kYo8GYBkxaKaUt1FpllYxWU+lJW2J1TT9t+dXAUttgdTiIVqrZoYq29+pgEaXBqIatup2GH0qrq3Q9TA0xvGjIofvRFlS7Pdhpxx13NOvfOSsIdf8aYunraSsa2mpoptWW9utrLR1Qoyf3hPZ0PnMvr7zyiqle1M8r09+dTD0unc/cbrf+7W9/G7vNWb3px/k+avWtBv4tfW+1AlCDZF3D1f191IFPGojaLeEa+Ok6n5n6njil+s0Fod8L/X7oups69Ma9f21d11Z1e6CSDp1yVoTqe6nP6VVl2RZ/JgAAAADo2WpWvhq7XL3yFan86ikpGn1Eq/bpbgGnJTw9DN3pJI4cMUEe3/9kmdBnoORnZZvzJw44WY4Y3ty62RloEKnrNGpgoANQli1bZirKdCKvhgzO7XTYhU57Pvroo00VlRdtvdX2ZN3H8uXLTcigwYKGNErbpHXtubfffttU9OlafNpS6gxctYpTq9JeeOEFU52oaxZqi6m9hqbXRGOt8tKKRl0D0H4N9oAVDa00RNG18T7//HMzEVurPrV13V6/si3oGow6pETXXUxGJzxrZZ/z5HxPMk3bbdeuXWvab/Uz1XZc/fy13VeHtWT6u5Opx6Xzmev3TMM1XQtRwy9tze6IqdL6e9EwWMNImx6bvnb9XuugGm2R17BO13LU2zMt1W8uFf3HBg0ktRVeA3/97uhJK2Xt/evyD7pvDWH183VWVWrb/r777msqgfU49LPSymv9fbfVnwkAAAAAei4NJze91zxk16rbIutnHWNubxUqLFuFwLKThZYfH3auVJ50rTnvbGGl0nXiXn/9dbPmoVaDabCoIYCuJ+isftMqM60Y06EsftPBla4rqNViGnxpNZW2L+ugDHsAiq51pxV2usbflClTzBp7hx9+eEIrrYYbuu6jbqvrHWrwpAGgH22D1yBVw0kd0KKBhg7rUBoE/fvf/zZt2VrtqG3V+hrtATttSdf5dLdEu+kah7vsskvcSYeKtBUNfXQ9SA39dDK3rrGpk7Y1yHWuGZmp706mHpfsM9chLs7PXEMv3be2MetakfqddFZbthf9DWioqq/NHQJqW7yGxNo+rdW/+h11t8FnQpDfXDJaxaot4Yceeqj53tinG264wdyvg2/Ky8vNc+hvVoNne6K5TSucdWkJ/Tz0s9Kp37rPtvwzAQAAAEDPVP6uhpXu5bVCsund9Jb6Sx1YMnQnHSFL/+aLpCoqKqR3795miIQ7INFgQSuAdC0/d+sjgM5JA0oNxw888MCOPhSkoCGthp36DwtthT/HAQAAgJ7r69uLxWqsTbg9lJUnI87Y0uL91m74RFY/tHvsenbpaBn680XSk1UkydfcqLAE0GPoH4q6NqWucfjss8929OEgCZ0grv8YpJ+VtoQDAAAAQFvILhvjWWGZUzY2wxWW1Aumg8ASQI+h63BqC70O8Uk1CAodS9fqHD9+vFlb9IADDuDjAAAAANAmyvbS5d/cYaIlpeb2VqAlvFWYEg6gx9BJ0lqCjs7v4IMPNoNyAAAAAKAt6TTwXuN/IVsX3meuh7ILpd8P75ei0cHX8g8SWFruABNJUWEJAAAAAACAHiu3T3P7d+42O7U+rPQKKAks00JgmSHMLgKArok/vwEAAICezTl0x8rUNG9awluFlvBWysnJMefaulhQUNDa3QEA2pndem7/eQ4AAACgZ7EaapqvRFoWWFZ+9ZSUv3u1NJR/YQb5FG1/lOtJaAlPB4FlK2VlZUlpaamZaKsKCwslFHJPlwIAdMbKSg0r9c9v/XNc/zwHAAAA0LMrLKUFFZYaVq6fdUzTtHFL6jcukE0b58c/B4FlWggsM2DgwIHm3A4tAQBdh4aV9p/jAAAAAHp4S3ikIe3Ha2WlHVY27cVxvek8U63mPQSBZQZoReWgQYOkf//+Ul9fn4ldAgDagbaBU1kJAAAA9GxWQ3NgGandJKsenBxr7S7b6xIzSTwZ3bY5rIztNXqWlSPSWEdLeJoILDNI/9LLX3wBAAAAAAC6ZoVlY+UaaaxcG2vt1lbv/jMeTRpaarCp28aHltHKylAoWyypy9wwnx6CKeEAAAAAAADosazGGvctjvOQbHr3mqSP1ypMr7DSXAo3DfdkDcu0EFgCAAAAAACgx4obupN4r9SXL076eK2+7Lv/rbHr2b1HSe/dL2puCTe7YUp4OmgJBwAAAAAAQI9l6RqTvkKSUzY25T7yh06JXd7mgNtMCLrZzD2JRm+0hKeHwBIAAAAAAAA9ltVQk+xeKTUt32kM7qnfIhLKil6hwrJFaAkHAAAAAABAj+VsCQ/lFsfd13/GY1I0+vDUO4nUN1+s2xprAbcrLPV65VdPmQnkX99ebM71Ojp5YHnttddKKBSSs846K3ZbTU2NnHbaadK3b1/p1auXHHXUUbJu3bq4x61YsUKmT58uhYWF0r9/fzn//POloaEhbpu5c+fKrrvuKnl5eTJ69Gi5//772+11AQAAAAAAoGsM3YkFjE0ChZWutnKrfqvUrH7XXG6o+LrpxoiZOK7TxDUgtSeQE1p24sDy/fffl7/85S+y8847x91+9tlny3PPPSePP/64vPbaa7J69Wo58sgjY/c3NjaasLKurk7efvtteeCBB0wYOXPmzNg2y5YtM9vsv//+Mm/ePBOI/vKXv5TZs2e362sEAAAAAABA566wtKyGVu+jZs27UvHRTfY9ntPDg04g76lClmU537l2t3XrVlP9eNddd8nVV18tkyZNkltuuUU2b94s/fr1k4cffliOPvpos+3nn38uO+64o7zzzjuy1157yX/+8x+ZMWOGCTIHDBhgtrnnnnvkwgsvlA0bNkhubq65/Pzzz8uCBQtiz3nsscfKpk2b5IUXXgh0jBUVFdK7d29zTCUlJW30TgAAAAAAAKA9aYXj+n+fqH3c0RvCuVpmKdIUQOZsM0HK9rrETAJPpmrZC7LumUPTfv45uTvIPYNPlsUVG2RsST+5dNJUOXLEBOmO0snXOrzCUlu+tQLyoIMOirv9ww8/lPr6+rjbd9hhBxk2bJgJLJWeT5gwIRZWqmnTppk3YOHChbFt3PvWbex9eKmtrTX7cJ4AAAAAAADQzcLKWcc0h5VKLzuqJYO2blevej3httnZo2R6r+NkXMlvzblej79/tPy2YKrML18jtY0N5vwnrz4gT349X3q6Dg0sH3nkEfnoo4/kT3/6U8J9a9euNRWSpaWlcbdrOKn32ds4w0r7fvu+ZNtoCFldXe15XHo8mvjap6FDh7bylQIAAAAAAKAzKX/36qY27WSCtW5XfvFo3HUNJ08rmi5fhLeRulC2OdfrztDytvzdY8/Q/EwhueqTOdLTdVhguXLlSvnd734nDz30kOTn50tn8vvf/96Up9onPVYAAAAAAAB0Hw3lX7jWmPRjSX354qRbNFZGC+dst+XvoeswiqXt5bqHUEhCVkRuz98jts2ycJnHM1nyxeYN0tN1WGCpLd/r168361dmZ2ebkw7Wue2228xlrYLUYTq61qSTTgkfOHCguazn7qnh9vVU22ivfEFBgeex6TRxvd95AgAAAAAAQOehbdqrHpwsX99ebM7TnbidXTYmQIWlCklO2dikW2QV9o9rAV8c3iYWVtqsUFi+CveJXR8ZKU8ITLXCcmzv/tLTdVhgeeCBB8r8+fPN5G77tNtuu8nxxx8fu5yTkyMvv/xy7DGLFy+WFStWyN57722u67nuQ4NP24svvmgCxnHjxsW2ce7D3sbeBwAAAAAAALrm+pO6xqRO6A661qSTDtNJXWEZnexdarb190r/6XEt4GZwj5tlSUMoS3YoOc0Em/vWr0gITLXC8tJJB0tPl91RT1xcXCw77bRT3G1FRUXSt2/f2O2nnHKKnHPOOdKnTx8TQp5xxhkmaNQJ4Wrq1KkmmDzxxBPl+uuvN+tVXnLJJWaQj1ZJqlNPPVXuuOMOueCCC+R//ud/5JVXXpHHHnvMTA4HAAAAAABAV15/0kpYazLVRG+bbtd/xqPRwTs+sktHS599dJ+HJ93XTZXFIlZdQlVlHL2vKbTUYHNxfj/PzawgXerdXIdPCU/m5ptvlhkzZshRRx0l++67r2nvfvLJJ2P3Z2VlyaxZs8y5BpknnHCCnHTSSXLllVfGthk5cqQJJ7WqcuLEiXLjjTfKvffeayaFAwAAAAAAoLusP5l6rUk3E26G/OOxftP+njKsVItr6r2rKt0ca1p6JZMM3YkKWRa5bSo6UVynhesAHtazBAAAAAAA6Fi6ZqW2gceHliHJ3WaCDDnhg8D70Vjs61ujXbpeBh41RwqGTkm5n8L7z5PaiBUstEwhPytbKk+6VnpyvtapKywBAAAAAACAYOtPpl5rMoHVmPzuhppAu9GsMnVYmbrXm6E7UQSWAAAAAAAA6FLs9SebhaT/jMcCtW/HiTQHlqGsxEpLqzFYYLl9rsfik87r5rIz0PRqB2fojo3AEgAAAAAAAF1O3HCdrJz0w0qTIzoCy+yCxPsbqgPtZ/98dzt4/PUpDV/LqbkbXLWU8aHl4MLe8sQBJ8sRwydIT0dgCQAAAAAAgJ4pLrAs9Li7NuUunvx6vtxZ4R6iE5JekebqzLpQlsyty4/ft8Q/5oxx+xBWNiGwBAAAAAAAQI9kRVJVWKZuCb/ivcd1qnVcRaVWT9aEcmLXayVbllqJ+3c+Zkt96nC0pyCwBAAAAAAAQM/krLDMSb8lvPKrp+SLygqxXAN3tHqywRG71UmWDI9s8nj+5gpLAstmBJYAAAAAAADomZyBZVa+x93JKyzL371aRkbKoxWWcQ+0JOxYo1Jbwn9RtyBuk5AVocLSB4ElAAAAAAAAeqTY0J1Q2HtKeIqW8IbyL+TMmv8mVFhqq7dzqI5WWO4V3hK3yaBI/PXPNq1rwSvonggsAQAAAAAA0LW5KxyDijTK7OxRMr3oWBlduYtM73WcuR67O0VgmV02RqY1LJVLqufG3f6DhhXSGMqKXa8NZUl1ODdum9VZveOuv7thuYx87BozxKenI7AEAAAAAABAj/TUqsVyWtF0+SLcV2olLF+EtzHX7dAyWUu4BovTc6bLuJLfyIO5E+PuK7PiH6cVlpV18RWVCW3kIrKislx+8uoDPT60JLAEAAAAAABAl2O1tKrS4ZpF/zXBod3Sree6tuTt+XtEr/tUWF74/iwTLC6oqpK6ULZ8HS6Nu79CmieE22tYvp41NP743W3kTUISkqs+mSM9GYElAAAAAAAAuhyrsdZ5LTa1e9WDk+Xr24vNuV5P5outmxInfIfCsjRcZi5vXXh/wn60svKGBXMT1qx0qgjFr4dZJTlyR/6ewV6XWPLF5g3Sk2V39AEAAAAAAAAA6XJXP2qouH7WMaZGUWO/+o0LzPX+Mx6VotFHeO5jQF6BrKjemnB7v0il/Syx/WQVD5NI1Tq5tNfxItIr6bFtcQWWDVozqBWhPlWV7grLsb37S09GhSUAAAAAAAC6HKuxuvlKpFHK3706FlY2bWGub3r3Gv99OCZ5O1VLthnAM67ktzK917FmTcvGLStMVedSKz/lsbkrLE1QGSisjB7TpZMOlp6MwBIAAAAAAABdTtVXzzquaSWkTtd2B5CW1Jcv9t3H+lpH6OnwXVaRGcCj61O6B/GMjJR7TCWPv74uVGTOs61Gxybej8m16mO3TCgbJE8ccLIcMXyC9GS0hAMAAAAAAKBL0fbvb189w4SIt+XvIcvCZSZIPLPmvzKtYYljy5DklI313c/2RSWyoKI8vvqxqXXbOYhHrIicUzjNRIzbaLu4uc3Z4u2qnmy6vSGUlXCb89hUX6tG1oSiQ3remnGGFGbnSk9HhSUAAAAAAAC6FG3/1rBSKx/9KiHtBuvSvS7x3c8ftt8pMUj0at0OhaVWsszzrAmXJGy2b+i76AXPyeXR2/asXxl36z750dt7W81rcVY1NFdb9mQElgAAAAAAAOhSGsq/MJWVIcuKq4QMWRG5PX8Pcz27eJj0n/GYFI0+3Hc/h/UfJGErkvoJHdWU9vNYjqrKQmmIXkiyTuWC7PhBOkVWnTnPtxokPxyN6CobnJPPey4CSwAAAAAAAHQp2WVjTBu4HVbarFBYlobLzOWGLcs91rR0sRplG6sq9RN6PI/Td1Z2kgrLqEqJb/WuaIiub5krjVKYFW0dr2yIhpg9HYElAAAAAAAA2n0NylUPTpavby8253o9HWV7XWLWrNQKSyetfNxOh+JEryWdEK6sSKN8z9WqHYS7jvJbO4y017b02toVen7UEH1MntUohU2DdyrrCSwVgSUAAAAAAADajYaT62cdI/UbF4jVWGvO9Xo6oWXR6CPkom2HxFVYRtvDw3JmzXtJJ4Q/+fV82eXpG6Xw/y6Svd55RRpc1ZLJqiTN83jUbS6xolPBx8lmCaWs6ozer2tiqk2hPMmrja6BSYVlFIElAAAAAAAA2nVgTnzsZwWqhnQ7avSe8uuaD2LXB0e2yF2Vs2Rqw1LfCeEaVv7k1QdkfvkaqW1skIVbt8isXNcU8STrUKqRBUVy/Ha7ej5mkfSWcKrA0t5/0/nX4dJYhSVDd6IILAEAAAAAANCuA3MSaxS9qyGTeWbDWnnGETbu1/C1I6wUzwnhV82bkxCVJlRUpqiwPLx/f3lo6Ufed1qWNKYZt1WGciW/aWgPFZZRBJYAAAAAAABo14E5iatAJlZDJqOVkid+/qWsC/WK3fZw3s4yO3tU7HrZ965KmBC+uGJDYv2ju6LSo8Lyh72ahuqIyKNrvkk4+mSPTSXfqpdPs6ITxI+d+w8Z+dg15vX1ZASWAAAAAAAAaDc6MCe+wjLkWQ2ZjF0p6Q4Ib8/fI3Y5b8DkhMeNLemXGDa6KiqPrl0Yd/3Efn1kv01vxq6vq6tL0vRtJYaWKSo4q8J5UhvKiV1fUVlu2tZ7cmhJYAkAAAAAAIB2owNz+s94NHY9q3CA9J/xWEI1ZDKelZIi8nl4m1iV5YaXTk0Y5HPppKkpKywHWlvjrg9Z8YxU1tfGrjckbRlPXWGZ5T4Cn/1d9ckc6akILAEAAAAAANCuCrc7NHa5eOdfmcrEVQ9Olq9vLzbnqSaGe1ZKNjmtaLoJLRu3rEyYPn7kiAkyqKAk6b7LQwVx1+tC2TIva2CL2r51y+JQJO62RveR++zvi80bpKcisAQAAAAAAEC7shqbKxbrNsw3wWL9xvnmdj13B41usUpJd3ViKCQhK9LUGu49ffzb2sqkx7YplB93vV7C8nb20DReXPMx6aXGUFbCMfpt7zS2d3Rdy56IwBIAAAAAAADtymqsiV2uXvmy5zbfvXaB7+O1UvL+od6VklYoLEvDZZ7TxzUEHRiJb/lOFVguDveV71xVl0m5Asmq5EPHfSssL510sPRUzSOOAAAAAAAAgHZgNVQ3X67b4rlNw5blSfcxozhL8qRRal3xllZYbhcpt6/JS72/L3c+faMs3rRWRjSslx0bN8uK3NGOg4kOysmyGk01ZHk4PrCco9v6VEHmWQ1SG2p+/tyQSF3EY/BOMk3Pr+f6qCGhGrnlgFPliOETpKcisAQAAAAAAEC7shqaKyyT0YpIHdLjvF7+7tXSUP6FhHJ7S0nWDNmQ1SsurNQKyzNr3jNh5ezs7eS0xp0kVL7GtGd/Ed5GFmf1cxyIJQVSL9WSK4VWvWwJZcmisOP+ZG3coZAMjWyWr7L6OjdMPXfHDigd+9bjHhv5VmZt/Wd0AFEPDisVLeEAAAAAAADosJbwZJzrT2pYGV3rcoFZ6zJSvV7CztxP132MfCt3Vc6SqQ1LJVzYT+7u9xNzu10faZlwsLlaMkciMqbxO3N5i11ZmUZ15MjIprjrI7KtuP178ti/hqzLsvqkPS29uyKwBAAAAAAAQLuqWvZCoO2c609qZWU0lmwOBKtCObHLQ7PFVChqWKmKx/9cvqytc2zdHFrG9h/Kki2h3KY7Ui02ae/AklIrGrj2bjq3/XbTf5pC0Uha+wyJJTv0GUpY2YSWcAAAAAAAALSrLfP/N8BWIckpGxu7pm3g2uJ9W/4esixcJiMj5VLpiLaqI5bMzh4Vu3/01zUyIL9MVlTa61l6t2R/GypMXVnpfEwoJGMbNsp74aFS5gwsLUvuzt9NflXzobyRM8wM/olISBrENSXca/cS6tFDdtwILAEAAAAAANCuGrasTLFFtJKydK9LYrfMKZwkp4X3NC3XWsWo61E6qyW3Riw5rWh67P7P6iyx6txhZUQkFN9wvNk1ZMcdUk5qWCMLsvrHgsd8q176WVXm8nopdBxy9JgW5/eTOyufl2kNS2RcyW9TvxmWJUMiW2X3N34uXz/7hWSXjZGyvS6JW7uzp6ElHAAAAAAAAO0qu9dgUw05vddxJtTTc70eu790dNx6jrp+5S3Wdibcs0NKZ1ipaiS6PmXsfr0uISnMam4bN2Fl0Nbvpv00SFh6WXWxm/V6XSgaXs7NGRH3ELsd/Pb8Pcx1rQINsqblunCh1G+cb9bmrN+4wKzVqa+5pyKwBAAAAAAAQLt6ZeDhphpSKxLrQtnmXK/boWX/H94ft56jrl+5JNwnadu2BpTuENMSS2oiDbHroTSH6iht7a6017nUwDKUJZVZxeby5nBB4nGEwuYx6sya/6Ze09KyzD6bA1vLHKlz4FBPQ2AJAAAAAACAdjVzYyShWtJZmWg5QkZ7/cqwBnmpqhVd92uFZXF2XvPdLTjW6lCOGc7jVG6FkgaQ20W0FT0k0xqXmfZwnV6eazXIkEhF4uOaJpfbrz3Kihs41NOwhiUAAAAAAADazT/fvVdWNYaayh29KxPdgaWu6xipC6Wujoy7X+srRUpz82Vzffw073TkSKPUNUVoYSsikVBYNoWa1r30Op5QSM6seS8aj1qWWcty2tYlsbt3KDnNVFQ6aWBrv3abc+BQT0NgCQAAAAAAgHZz9WcfiFhFiWFfrDJRLzfIk1/Pl6vmzZHFFRtk+7zpMqBmrXwTLk4aWmZbjXFhYL+8IlleualVx6v71Lb1PKtB8q0G2RzKNyflXDPTfg1aRTm1Yanv/kZFvksYGKTVpbHX3sQ5cKinoSUcAAAAAACgh9BBLqsenCxf315szjtisMtSKz9FZaLI02u/kZ+8+oDML18jtY0NsrCqSr7JKol/nEc7dt9IlXOHsqG20uMImh4XcPhOVTjaUl4rWbI5FL1sr2npXJ/SnIdCcnHNG0n3517XUs+1utR+7apwu0Pj1vDsaTo0sLz77rtl5513lpKSEnPae++95T//+U/s/pqaGjnttNOkb9++0qtXLznqqKNk3bp1cftYsWKFTJ8+XQoLC6V///5y/vnnS0NDfNnw3LlzZdddd5W8vDwZPXq03H///e32GgEAAAAAADoDDSd1+rROoe7IadTbhWoSJ2drZWLj5lhl4rXLvjId41bcGJqQ5Eeap3V7hZe63mRq0cfFVzQGCC89QtYZtZ/H1qfU87sqn/eprmx+rLaIO9e1jD5uVtzjGiq+lp6sQ1vCt912W7n22mtl++23F8uy5IEHHpDDDjtMPv74Yxk/frycffbZ8vzzz8vjjz8uvXv3ltNPP12OPPJIeeutt8zjGxsbTVg5cOBAefvtt2XNmjVy0kknSU5Ojvzxj3802yxbtsxsc+qpp8pDDz0kL7/8svzyl7+UQYMGybRp0zry5QMAAAAAALQbnbQtHjGgTqMuGn1Emz63s727f24fserqoiGjHQK6KhO/rKpKiBDNxG+PQFLDzyyJSINkSUW4aW3JZJqet79VKUulj7lJHxVolUtXaDk/e4C8vPUf5nK4oJ9EGjY43uPoecnkc6Vm+YtmiI7VqIFr4rqWbvU9eOCOClmaFHYiffr0kT//+c9y9NFHS79+/eThhx82l9Xnn38uO+64o7zzzjuy1157mWrMGTNmyOrVq2XAgAFmm3vuuUcuvPBC2bBhg+Tm5prLGnouWLAg9hzHHnusbNq0SV544YVAx1RRUWEC082bN5tKUAAAAAAAgK5G28C1stItlJUnI87Y0qZhpbZ3J9MvbMnlW/5tgjx1+KCLTBt4QmjlDDmbTKv/Sj4LbyMrsko97/dzaN3n8mzuDubymEi5fOEaehOIZcmdVdHjzu49Svr84I8mANbAUYfm6DqUztZubcPXytZUFZ252+wsQ074QLqTdPK1TrOGpVZLPvLII1JZWWlawz/88EOpr6+Xgw46KLbNDjvsIMOGDTOBpdLzCRMmxMJKpVWT+gYsXLgwto1zH/Y29j681NbWmn04TwAAAAAAAF2ZTtpOFGrzadTn/vfZlNtsjITktKLpMjt7lLl+0bBtvSM9jzCyXPJlbbiX7/1uWVajOV8WLo3d9lWot5kA3hL2cTdsXmqqWEv3utgEwBo4utehLDODdOzqS3+lPXjgTqcILOfPn2/Wp9T1JbVt+6mnnpJx48bJ2rVrTYVkaWnzl0dpOKn3KT13hpX2/fZ9ybbRELK6utrzmP70pz+ZxNc+DR06NKOvGQAAAAAAoL1FwzKnaMtyW4djKyvjp197MRGeFZHb8/cw1w8pK5HH9z850P7/m7Ot1EnTZPAAjcQFVnT2yfzsgbHbIhKSSEhjshSPd++/aXhO9LitlOuCaut9/xmPSu42E3xDy+zi4T164E6nCCzHjh0r8+bNk/fee09+85vfyMknnyyLFi3q0GP6/e9/b8pT7dPKlSs79HgAAAAAAABay71OpYZm/Wc8lpFwLPn08WCrEeqk7KVNbdmW1SBHjtBQr1nIbz+utTCT2bf+azOhO/a42M5DTdd1tI/PPnzCUOdxO9cFTfY5aPVl/xmPxF6Z87zPfn+Wnq5FgaUGeKtWrYpd/+9//ytnnXWW/PWvf017X1pFqZO7J0+ebCobJ06cKLfeeqsZpFNXV2fWmnTSKeF6n9Jz99Rw+3qqbbRXvqCgwPOYtNrTnlxunwAAAAAAALoTr5bllvj2jYuapo/P95w+Prhxa6D9aKVibHJ3JNq27WT5BYlJQspf1cSvA7mNVSXV9gxq9+PMdUsm9BkoOeGwZ6hZJHXJj7vpSIMMzXFWW+o6opkMkHtkYPmzn/1MXn311VjL9cEHH2xCy4svvliuvPLKVh1QJBIxa0hqgKnTvnWqt23x4sWyYsUKs8al0nNtKV+/fn1smxdffNEEjNpWbm/j3Ie9jb0PAAAAAACAniC+6jHs27ac7j4rPrzJdWt8leGluV8H2pdWKp5Z8170ciTatu10o8zzeaB/BefISHwh3OZQflPrt8fjLEvyw1ny8WHnSs3J18udVS9IjjQHp0fULpLv1a9MCCudx53uuqB2taXfmpc9VYsCS524vcce0TUFHnvsMdlpp53k7bffloceekjuv//+tFqvX3/9dfn6669N8KjX586dK8cff7xZO/KUU06Rc845x4SjOoTnF7/4hQkadUK4mjp1qgkmTzzxRPnkk09k9uzZcskll8hpp51mqiSVrou5dOlSueCCC8yU8bvuussc89lnn92Slw4AAAAAANDlaLCoVY/NIknXWkzW7v3Pd++V7R67WrLvO096v/Gm7Nfr57FhOV5Vhj/73qlSFqlK+hx9wpbcVTlLpjYsbTq8BmlwVVnekzvZ+8Gxdu5EFaFoPmT7LlTg/bimtvIzxu8be83T6r+QcY0bY5vv1rhGdopscLxES8ZGvo0/7qbX3tOH5nRIYKnTu+1A8KWXXpJDDz00NsV7zZo1gfejlZEnnXSSWcfywAMPlPfff9+EjlqxqW6++WaZMWOGHHXUUbLvvvua9u4nn3wy9visrCyZNWuWOdcg84QTTjD7c1Z5jhw5Up5//nlTVant5jfeeKPce++9ZlI4AAAAAABAT6DTqxOHvCRfa9EO7v7xwh9kas1OsmPRKbJ3za5ywmefy/LKTaaOUtu0v8kqiZvwbbOrDLWKcEvYe1k+29T8+vjQz2qUfy79OG6br+qaAsyEcNKSsKQXWGZbjfKrmg8lTxrM/vT817Ufy7W7zXC8X7p9fuyxlxVMkbezhsSuD7AqZdbWf7rCyhBt3RnQ1LSfnvHjx8s999wj06dPN0HgVVddZW5fvXq19O3bN/B+/va3vyW9Pz8/X+68805z8jN8+HD597//nXQ/U6ZMkY8/jv+SAwAAAAAA9BQN5V94DL9Jvdbiw2/fY8JIuwJxddhnzodlmUnZ07Yuid3krDLcJhyRtZGmSd4eahrjqym1JfziD+PzHst3zUqd8B0KFFiuDxea8wYJy+s5w+SmqjkyrUGPOdQ0ubv5/bou7/uyMqu0+TYJy3s5w2LXi6zE9Sx1H7R1d1CF5XXXXSd/+ctfTBB43HHHmcpF9eyzz8ZaxQEAAAAAANA5ZJeN8aywTLXW4pV1w5s2TTGFOxRyTMoWKdzu0Fhwp1Wae1e7glFXlWRVJBJ3/blNW+Sbqgrv59LHeqw/6XX983B8YV11KDd2vF+Et3FUhsa3cev79UDeRJ9J4lGrw8UJVaW0gndghaUGlRs3bpSKigopK2v+Mv7617+WwsJoUg0AAAAAAIDOoWyvS1xrWAZba1FDuUAsK25SdtXSZ01Qqe3g2l493GpurVa9rDrZ6qh+rLEsE/7dlr+HLAuXSWiFc+q2B88J34nXP8vq53/IoZAZmqOVoceM/1lcZaS+X3Wvv5n0eWok2wSed1Y+b6o084fsR3VlR1ZYKsuyzCAcrbTcsmWLuS03N5fAEgAAAAAAoJPR4LB078ubb8jKD7TWok89pceGoYRJ2fb6mPXffW7CPadtrPghPIsbckz4p1WPdaFsqfUf/C3ZEl+NmUyVXVHpM5RHJ3xrZWjB8IMS3q+8cNhzvcwYR+Cpcsq2D3xcaIMKy+XLl8sPf/hDWbFihdTW1pohOcXFxaZVXK/r+pYAAAAAAACZ8OT8NXLlnMXyxYZKGdOvSGZOHStHThiU9jY9XV7/XWOXc3qPDFQNODhUI99I8oE5QyIVcnH16wmTsnV9TDOFPFIvNaH4COrrrOaOXbVO8iRkWabqMRUNLBvEfz1MpxxplDqNv3z2q4GjVoaGsuMrQNUZ4/eTGxbMja3fGQ0rQ56Bp9lXdvx6mWjnCsvf/e53sttuu0l5ebkUFDR/aY844gh5+eWXW3E4AAAAAAAA8UHk0Q98IPPXbJGahog51+t6ezrbQHPDaIesYTUEekuu23GXlNtcmrPcFVaKzM4eLTN6HS/93nhdpvc6Tr50rSXpVbkYJKxU7mpNT037z7dfp1eFpQlIw6YyNJSdGMrOLKuPmyTuM4hc+kUqzXkoi8CyQwPLN954Qy655BLTAu40YsQI+eabbzJ1bAAAAAAAoIfTqkm7tk3sGreQyFVzvkhrm2Q02Bx59YuSdd5z5jTympcCh5263aQb50rhRc+b884cklr1lc2XXUNu/By31y9lVLjWsZPE1O6O/D3jrutalKcV/Vg+l15SK1mmzfutpunaYSviO+nbr207gWv4TYJYRaQ0V3Z6hKFaGXpX5SwTtnoFlrr25oW1b8nCirvlq4rbZUjEEfh6yYrPydDOgWUkEpFG17h5tWrVKtMaDgAAAAAAkAna4p1Qi2eJLN6wNa1t/NjVmcs31Zh96Gl5eXWgCs32ruxsbTgaqXMEbpFgFZYqHHZUNHoEf19WVUsou3kI8+0F34sPkB0BY7HlCD99Qsa0Q0t3eOm4T9fDVKMaNsZtMqlhjby29YFYZWjYI7BsKI8PvDeEvQdNbwgXRZ+WCsuODSynTp0qt9xyS+x6KBSSrVu3ymWXXSY//vGPM3d0AAAAAACgR9P1KBNq8UIiY/v3SmsbP+c+u9D3vlQVmq2t7ExHJsJRq745wLUCtoQ/+fV8WdKQJD6yLBllbREJNW+zLKsssXu6KYzcHC4IXDVZlJ2iYrHpMZ4xpyv8XJK9jWRbzcV3IyKb4jd3BK627LIxcWtWjtS1Ll3HGJLm6eihrMR1MNGOgeWNN94ob731lowbN05qamrkZz/7WawdXAfvAAAAAAAAZIIOz3FGRCYgtEQ2VdfHKg2nju3vWWE582ANnPxp2KfVlH5SVWi2prIzXa0NR3X4zeaP72g+zrrm9vBkYeVPXn0g+UzuUEhOr35LxBEGjsrxmC7uVw3p2lfcMTfUJT0+HZizQ+RbGVeQl7hfj+sNoeZBPUW50apI2+pH940OCHIo2+uSuEE7Z9a8b6pF9XnN8+u6m9I8HZ0Kyw4OLLfddlv55JNP5A9/+IOcffbZsssuu8i1114rH3/8sfTv3z+DhwcAAAAAAHoynfR9+dTm4LF/r2jV3Yry6lil4Q1zl8h5U0bFtinJz5Z/nbybHJFiSriGgMmkqtBsTWVnuloTjmoQt37WMRKp3hC7LVK3KSGgc7tq3pym1+ffql0WqZIZJXliOVrMzyuN+FZYptX2nYSGhmZgTt0HMnPyjxP363XdEWLm6yAdh/pvF5r3yPmeFI0+QvrPeFRyt5lgwshDSgvl3pKNMjbyreRaDbJjdqPcW7gq1lZOYJk52S1+YHa2nHDCCRk8FAAAAAAAgESTty2NXc7LzvKsNHxxcXMYt8vg3inDSjsETCZVhaZWf2pbdrqVnS2h4aiGs1YLwlEdHtNUmxp3+6Z3rzGhnJuGdvqYz+t+IJY9tMaH7rV0r0tk/fPHxm6bXtgoA/N7y9qaFENqWmFwZKtcXPO6TG38WkaM2kOy3/xXXAWl98E2h5h59RWuO6OVlO73RC87rx/+3jUyZeUV5nKvnf5HIlX1UmXvnjUs2z+wfPbZZwPv9NBDD23p8QAAAAAAAMSpqm9uN16xKbGF211p+PrSb02ruAaKWqGZTgiocrJC8sgJk1OGnrrvJ07eLS60DFLZ2RLucNQuGEwWjmpLt1ZJavA4stdOcmbNf2Vaw5LY/fXliz3Dyn+88Ae5LX8PqQtnpRyGU5dTLIWjDtOx4803RhpkQ23qlvOW0nUki6U2VtlYtex5GR3ZJIvDfaIDfvw4Xkt+o9dSAJbnexL33OHmdTVD4RwJZTevWxliSnj7B5aHH354oO10AI/XBHEAAAAAAICW0AAyeRYhMqA4L7YepQaQ9lAaDRT9QkuvCkn1z+N3DRw6uvfdmrBS19TUNnWt/NQw1R249s7Pls010VbmbXvnyy2H7ZTwfHZIuWjzOmmIRKJ1laFs+SK8jZxWNF3urHw+FlrmlI1NOIaH377HbGeGywRo366OWGJF4nOgWZUijc4A08lrv/ZtAZ9TQ8ml4bLYdW3lPiN7tJxW9ONYq7hz7Umv5y3UMDZByPM9idvCEUpqeNlYtT52/bu3LpFQdp5n1SraaA3LSCQS6ERYCQAAAAAAMump+WuT3u81xyXIUBq7QjIvOz4e0SE+yUJFrd60B/64p3R73ZbOFPBPm6aA67lev2DWoth9dlipVm6qSagMtYfkzC9fY8JKcbbONwWCf8zfJ7a9tnK73VI32ISVCZWK+iZ7vNEaTNY3xg/HuXGz/3TvXhH3IB1HkBj3nJaU5npP3dbjsydz26Y1fGXCWHt9yTE6udxhW6mSnRrWNR9H8UD3XqPP6fGeONV991ns8pZF90vNqtdi1xu3rExYBxPtOHQHAAAAAACgvazfWut7nz1gZ92WxG2CDKXR0LJfUXzAtsetb3iGjnZwqNWb9sAfd4WmfVu6oaXfACAdKHTuswsTh/tIYhhrD8nxncMdCsk3Wb1ldrYOKApJ0ejEbtplWaWebdV6S4k0v8f5Vn3s8sYvnzL7nN7rOBlX8lv5rMEnbrIs2ZqwzmP8MBynfQeO8t5NqHkyt5NWjs7a+k9ZVHG3nFn7Xtx+j6xbJMMim2PXC1yvMbt4mPSf8Zjne2LTIHLrgr83H0ede43O6PPpOpjooKE7lZWV8tprr8mKFSukri4+HT/zzDNbeVgAAAAAAABRfQtzZN1Wd2Ve1A9G9jFt0dpCrVWJ0pKhNNXN4ZtavH6rZzu5horugT/JKjuTrZ+ZzgAgnYieMCFcEsPYxRUb/MPK2AMtOadwmtlu7NM3ysxJU+XIERNid/fPLZCVrpxH5VgNUix1UhGKVj3mWQ1So7FSKCR7vPO6rG1qI4+GnT5H4df27dkGHpIvN0cHKR05fII8uXx+7J4rql6JrV/pZXb2dnJ6/sFxgeVtebvLoMbmQTvnVg+WBXnflwtr3zLXG7YsTxb1Jh1e5Fb33aKk96ONAsuPP/5YfvzjH0tVVZUJLvv06SMbN26UwsJC6d+/P4ElAAAAAADImL2Gl8kzC9cl5F7qP5+vN23YB43ZJi6wDDKUxlZZ1xgodNRQMWUgGLCy080rcHVyx2R63R3Gji3pZ9rBkx5jKCS1VpY51221hfzx/U+OhZahnCIRj8AyHBIpjjRXWG4OF8Q+hLXh6HE0V2YmWYfSK5z0Wbvy883R9SHfXa9hYjPn4CAvOjAooa3dsmRNVknsaoOE5X/zJ5vL0dAycUK4W0O5VrSm/gaEUkxWRxu1hJ999tlyyCGHSHl5uRQUFMi7774ry5cvl8mTJ8sNN9zQkl0CAAAAAAB4GtK7IC44G1zcvLZhxIq2Yd/02rK4x0wYWJxyYre2bU+8YW7g0FFDxdQjYYJXdjrpgB3PfYnIsLICzwpLdxh76aSp8dv5tFq7w8Hz3n8mdnldjXdoWitZsjxcGr9vz7UnW8Dn8fbRr65qbuVWP+/7y6a2dm/LwmWJbe3OlNtx/f/ydg48ITy7TN/vAEOBLIZRd0hgOW/ePDn33HMlHA5LVlaW1NbWytChQ+X666+XP/zhD60+KAAAAAAAADtU/OfH38TejDH9eklhbpZnRaTTf3+3b8qwUtu+F6zdEjh01FAxaIVlkMpOt8KcsGdV5U8nDvZ+HvcNW7aRftWjY3cOirhem0+AuWLrprgqTc/nCoWl2tmo29qQ0j5I014e35KfwPVci+tDZpJ5LLQM58TdP1IH8rhfq08rem3sNaWeEF5mBvKk+gaEJLfPjim2QZsEljk5OSasVNoCrutYqt69e8vKlStbsksAAAAAAADPUNG5xuTn67fKkm8T13t051NV9cmr3NzrUQYJHbU9/C9H2xV5Itla7ulhRFlB0rDU73VW1Ucne9u0slKrRGcvXp9y6I69jw1rmkLWugL5XQvWUpw6xCe082nbbhHLMi3b43NF/m9EXxnbuDHxuZI93EwKj8jt+XuY6zl9doirfDyz5r++FZXu58mThsATwrVdvHD0kc03hJvDzubz1PtBGwWWu+yyi7z//vvm8n777SczZ86Uhx56SM466yzZaaedWrJLAAAAAAAAz1DRTdvA3dzbVacILJOtR6lDfvzayfcb1Tflp7TWY2J5S16nTj4//qGPTMt7qqE7sQA2Eq0+zc2qlu96aRjXrLdV4/n8w3qVmfMnv54vNyzQFnmvNzidsNKS/KYwL7ep4C22Gyti9nVn1fMyd0SBSChbPs0elHYwqhWfS8NlrsrH6OOmNSyVbG3LTtoWHn2ek+sWSO42E1JOCLenhFd99WTzDZHG2ITxUFZe4P2gjQLLP/7xjzJoUPTLdM0110hZWZn85je/MYN3/vKXv7RklwAAAAAAAHGCDrlRVpqBpa5H6ee7qnq5Ys5iU7WYbEBPWUGOZ+VjuutX+r3OmoaIOXnd536e2D4i0aAwktUo1xfsE/cYMyjHw417HGrOr5o3p+n1tLaSMiQjivuYS1OHaPVjlIaIYyPfyl2Vs8yU71B2oVz8zbfBhvJ43Ladtn43VT72n/GoCQzt4HB0qMpUccY/JiIhfZcsS/JDITl/whS5+9SXZcgJHwQKGZunhMd2aK6H83rLiDO2BN4P2iiwHD9+vOy5556xlvB77rlHrrjiChNeTpo0qSW7BAAAAAAAaNGQG6/27GpXe7Xb1LH9fe/TGEqrGrXF2h1aVtY1Vy1+W1kXaBhOJl+n3/MMKM6LXmiqsGwIZSUEfRrg5UWa2+sH5RfJEwecLEcMj04IX7xpbeCAOHoQ0a136R2/7uW5ueukKDvXXB5S2Dt2++1V/5FZW/9pwkq15ZN7ZFVdivUrzYF7vDuhkJxZ817sqoaWGhhqcFi618VyRuXrZvCOqeiMVXaG5c6qf8tXFbfLml1GybW7zUjn1fpMCU89rAftFFgedthh8n//93/m8qZNm2SvvfaSm266SQ4//HC5++67W7JLAAAAAADQTWnoN+nGuVJ40fPm3Kty0UvQITeaZ43eJr5isspRCellzuL1Se+3B/k414mMPm5D7LJXJPrTiYPSWr8yndfpdPnUMbHn0fdzeXl19I7GLN+gTwO8ulDz4Jz/t92EWFipRlpbEqsS/Thatwfkx1duPlJfJuurowN/Ntc1HZeI/Cl/n7jp3o1V61LPsLGfy2VI4+ZY8OlVCTmtYYncWfm8qejMtRriKjuNcDRQTYf3lPDUw3rQToHlRx99JD/4wQ/M5SeeeEIGDBggy5cvNyHmbbfd1pJdAgAAAACAbuiCWYtMpeKna7aY9ma/ykUvOuTmiZN3k/zs5vjCa86N5lmnfX9EWi3hn61rXv/Rj+7XuU6k+seHq5I+5l+frk0rlLVf58PH75p0G/fL3nN4dP3GhDUwi791LNOYOGTGectH5etia1fu8vSN8pVVYEJNvxbs+ANq3u7db9fG3fWNlSMrqzaby48smxe7fWW4d/x0b7FksHuSudfzaqVk05HbFZMX174V22TVg5PN+pLxlZC6luUSU9G5qOKuuMpOs5+s+MniQbjXymTITicLLKuqqqS4uNhcnjNnjhx55JFmarhWWmpwCQAAAAAAoKHdDXOXxL0RdtzjrlxMFuZt17cwbuDO4TsNjNvmjiN2kv1HbRN3W3VDY9Ljqvea3COJmZx7PcrVm70H19gaLSutUNZWURs/IMfNfbQNjVasclXDYHN/8QYJDXNMBvcaOuPw7JplcsRL98lPXn1APi1f09xGbm/nnrLt1nTbpoY69x0el6IVns7p3uoPNW8kPpfrOX5V84HsGK6VvKY1MP9vRH+ZWv9VbJP6jQtk/axjYqFltBIyuVALKiy91spkyE7baK4DTsPo0aPl6aefliOOOEJmz54tZ599trl9/fr1UlJSkuljBAAAAAAAXZBW/nlxT7hOpbYhvvm6X6/4sGmfkX2lwRVAJquw9DuuhOO0Etej7N8rT76pqAncTq6BayoaPJ76xKdJt9l3uz7y+tLvYtffWPatXP/qkvjKy/7L7eHX/i/IHVquXBi/jeN+baWuC+XEBuY0hsJiOZ8xwERvK8l0b2W3bmuIqbf3i1TK1lBubEBQWaRaLqx9W0LWArEaakQidZK9Yqw0eMTgm969xoSKWgmpAabjRSUcSSgr/cBS6f71hE5YYTlz5kw577zzZMSIEWb4zt577x2rttxll10yfYwAAAAAAKAL0snVftKZpF3XGB9YllfVJ4ST9ZFI4DUskx2Xm+UKFp1Dd9JtJ/cT19Id8Jgf+Xh1QgyXlVuZfMB3inAxjmVJXbi5bbqhKayMrXFphtik3l/Cio9WJDbd2+Zs3X5t6wNyWc1rsfv6WNE1MK26ChNWmmOp+NrrgGPDbzRQzC51BM1ZeZI/ZL+MBJboxIHl0UcfLStWrJAPPvhAXnjhhdjtBx54oNx8882ZPD4AAAAAANBF6fRrP+lM0q5rjK+O+8/n6xMCS22Rdrro+c98W7KDTuV2Dt3RfWmb96aaYIGlVzt5sjAyVYP6+q21cdfXVNQkPGZYZLP30Jygg3SSPk7XrIxIrjSYyss8HTnku1/LVfvYvP6kVlg6p3snCskSRwXmN+HiuEE9Kqf3qKTDb7Q1vGGTY8mBxlqp+aY5BFXfzj0nbt1LdIPAUg0cONBUU+ralbY99thDdthhh0wdGwAAAAAA6MJ0+rWXC6aMSmuS9hbX+o6VrupJDSxf/nJj3G2rNtckrCNpr/moA3fiorhQ6irJIFWQzv15tZP7CRKghl0HObAkL+ExZ9W+4z00J53KymSPa6qy1EpIK+l+m2+/oXK2jG3c6D2p28NLfabInfl7xq7XSLYZ1HNd3vdjt5XucVHS4Tc6JTxeYrDaULE8bt1LdJPAEgAAAAAAIBldv/FvP50Yd9tdR06Qa2eMS+uNq07S3m3ub4jI399fEahCUgfiOAfuaNS188ASGV5WkFiz56iSDFIFadP9/evk3QKHshrsptq3e43OGeMGJDxmVFWt3F75vJRYrjU2fSohB+Tm+T+hR/DpbOceqeceU8hV78ZoG7f6a/5kOaPmv56Tupv2GrsUziuVO/L2TBz2Y1nyv/mTY5WWm96/Xkomn+M7/MaeEp5c87qX6HwILAEAAAAAQJspr45fb7ImyfRuP6nCvJr6Rlm5qTpQhaR7X6UFOfLxufvJjYeOTxwQ46iSDNpGnp8dNvtLp4JUg93jdhki6Zg4uLc8cfJuseul+dnyUOPxJuipaBpYE+NTCbm+rlayQuFAoaW7nXvf+hW+U8g3ZzU//5fhvqZC0t3W3fSAuE8kUrtJvqis8N6vZcWmi9d/u1AqPrxJSve6WEacsUWGnPBBLKxsnhIe5NNqXvcSnQuBJQAAAAAAaBNa1Xjec4vibjvn2UW+a0t6ibgqC71U10dk2975CbcHqZDUQFWPR0NDDQAHFUerDssKcuKqJFNVQdrx2DZFubHW88KLnjfnQV7vyD6F5vxHO/SXIOobI3L4+IGx6z/cob+8a/1ALss5OPCalbpVow7PSaYpLHS3c7+eMyzQ82iLuoadGjaGsgul/4xHJXebnWOVkb3G/yJue8/KzabjaJ4unrw6UqeEJ7aMe7642LqX6FwILAEAAAAAQJvwWvdRr9tt2i2ZEO6UmxWOrWF5zKTECsWgFZL2WpcaWl504PbmtoPH9IurkrQDzV65WZ772LY0PxYk2q3nNQ0Rc+5eS9NLVX208nTCoBLzPKEALeLOtTytpvfq2+zclq9Z6UHDxh0iGxPauZdpeBjwebQyU8PGrJJhZoK3VkTalZE1q9+O2/bMmv/6tKNbruni/tWR+hzRYLS5ZbxwVHMFpte6l+hcCCwBAAAAAIAnDdlGXv2iZJ33nDmNvOaltKojvaoa9brdpu18Hr+KRGdgWZATH2NEmkKtP778ZeyyrV9RbuAKSedal/ZzaAjqpqHlDj6Tv+84YoI5/66qPq7R2XLt309VU/hYmJNlnmfCoOKkoWV9oyUVtc3t9hqU1jZEJK8+u+VTwV2STfX2rYT03E80bMwubK4ItTVULIu7Pq1hifyq5kNXO7plKjXjjyN5daQ7GC3e+Vdx9+dsMz5u3Ut0LgSWAAAAAAAggT2kZvmmGhO66Wl5eXWgakGbVjW6aQhnt2k7n8evIlFDONuew+yW4PhBNGu31Mr1ry6Ju+/E3bZNqJA8eMw2nsfpXOuyICcrruIx1cRyW+/8HHOuA3281sJ0h7RudkBa2FTBmaoFvSESiTuWipoGE+6WfRu88jGZLCuSdKp3rBIyFUfYGArnJD5PQWIL/IW1b8mdlc+bys7odPGNruNIvzpSKy1jwjky5PgPCSs7MQJLAAAAAADg2c7tJ2hLt4ZubhrC2W3a9vMkq0i0Kyyzw6FYmOgl5FGB6KQB6ItfbPR+rGOtS61w9KuwVBV+gWVBtjkPhxKPxbl/P5V1DXHPrwHrJQdF29N1fyX52QmvT0NK28tfbpQtNQ1SH4nfLiWfKsnf1b7rM9W7uRJSQ8UhjZtNBaTffvTY7bCxesVLsurByVL51VPR1/zVU9K4dZXv/vX5E6eLhxKmgqcbWIZzSySUwbZ5ZB6BJQAAAAAASPDZOv+KwE/XVAQaKKOhm56cskIiV8xZHHucZ9u4oyKxriF6b1522Ezg9uPeh119GSSAda51WZAisLSrGjWYdCrJy4kFq8mmjadqCS9yrJGp62iq7fsVyb4j+8beB/v13Ti3uarUroKt77c6GiAGVGLVet5e5JPnZRcPFwlnx0LF17Y+IF9W3GGqIBOe1wzr2egIGy2p37hA1s86xoSV5e9e7TMQx/9zzh2wa8JU8CBCWbnNe8/rndZj0f4ILAEAAAAAQBwNE7W12Y/eE3SgzPCygrjrWvj4adPj8i6Y5fkYZ0VibWNjbMDOhso63+cJebRMO2kw6vc451qXzWtYRjwnlm+tjR5P2FGhpyGlM0h89MRdY/eVFmTH7d9PlaslPK49va4xVmlqV2AuWFMhj3+a+L5X5eq6kx4hoEeYqCb3SVxXUhXn5ElO352aX2Pv0aaqcegpX8rIM6uk14RfR+8IhSW7eKhpETfP63yehHUn4yd8N5RrFW16621G6rw/x1Rqvnkjdrlh6+pYlSc6JwJLAAAAAAAQuBoxMXpK3iKuwaYfDUXd95v2cEtk6ph+poJz0o2vmdt1qM7CtVuSHkuylnCv9TTVuIHFcWFisgrLhz9e5VnBqZdf/GJD03GKbK5ubtUOSWLFpdegoW8218QFku5j0aE6zkDzzWXfeb4eHYbjVemYsN5k0/V3KqKVrFmu+4vCIjm9h8euDzgkvgW7YMj3zXn+tlOkbJ8/mmrLeyL/lSKrOVQ+uP4rn5by6ITv7DKtOk2cI59VNED8NJR/nnbYqNt/99p5zTc01pgqz2/fuCit/aD9EFgCAAAAAIBA1Yhe3FO/3UHc5+v8Q0YvGiyeP2WU/HnuElPBWdcUPG6uaZDNNc1Tsd1+teewuOuNTYGifTx+YedlrlZtZ1Wjk+7npH/O833+G19rbs/+9ROfxi6XV9cnVKF6DRpa8m2VR4Vlc7Wnu8JSp5F7OWrrUlPpqBO+lTl3Vz6qpus1kWi42ui6/7NQmYRymtfdDOUWx90fyokGwFZ9pYgVfa9+lP2t7NgYDW7Vx1mDZHb2KI+jjE74LjNDc+zYO3q7Xi/YboYko9WZ6Yi2nieq+PAmKi07KQJLAAAAAACQEBqmM5LEbt/2CuJeXfJtWu/u34+ZJI99stpcdlcm+h1TcV52QuC27Luq2PFoC7qr4DLWyn30xMFxt8WG7jTEB5b2cCA/X25sDnm9hu6c+8zCWJB73D8+jHt9zkP7f49/Egs3YxWWDdoSHt3KDi7dr9fWUDHADMPRCd/RCdvfyq9qPoivsPSquHS5w9pOnlrxWex62BVYhpsCy0hDpVhNgeV/ZIh8kLNtbJuNoUI5rWi6K7RsnvBdNPoI6T/jUTNER4fi2MN08gftlfTY6r5bJOmItp5nJvxEDwgs//SnP8nuu+8uxcXF0r9/fzn88MNl8eL4svOamho57bTTpG/fvtKrVy856qijZN26dXHbrFixQqZPny6FhYVmP+eff740NMRP7Zo7d67suuuukpeXJ6NHj5b777+/XV4jAAAAAABdjU73TmdlQXugjNfE73Td/PpSWV5e7XmfX4BZVpAtf//vyrj7tGX6N45Kx6Ca27AT18BM9np26Ndcjeg1dGf5pupYkJtsfVCttLQrMu1j0cd/sHKTuez33qip+e/K/yt5Rqa6JmxPaozPUQKxLLkma5fY1XCOq8Iy266wrNLFPc3lW63tEtaw1Pb02/P3aLoeTpjwraGlDtEZccaW2DCdcE5h8mOLNKRVGRltPfemrenofDo0sHzttddMGPnuu+/Kiy++KPX19TJ16lSprGz+V4mzzz5bnnvuOXn88cfN9qtXr5Yjjzwydn9jY6MJK+vq6uTtt9+WBx54wISRM2fOjG2zbNkys83+++8v8+bNk7POOkt++ctfyuzZs9v9NQMAAAAA0NnpZO8nTt5Nejnak52XnS6YMiq2BmSqUC+IJzwGydicg2700rCmgT4bfYbxJBvSY6896Z50/tKXG2It5RNvmCsXzFpktqlNshanmjl1jKnYTCbIe2MapEPRdUHnLF6f1mPPLHnMrKHpPorbNDB0BYkJLeJuoZB8k9U7Vh0ZyopOQbfZoWKkfmusJfxLqzChclPb078K9zGX+/zg2kATvkPZKQLLpqE9QUVbz8W3NR2dT4cGli+88IL8/Oc/l/Hjx8vEiRNN0KjVkh9+GC2N3rx5s/ztb3+Tm266SQ444ACZPHmy3HfffSaY1JBTzZkzRxYtWiQPPvigTJo0SX70ox/JVVddJXfeeacJMdU999wjI0eOlBtvvFF23HFHOf300+Xoo4+Wm2++2fO4amtrpaKiIu4EAAAAAEBPCy0nDi6JXdcp2A/+rLniTv39pxPl2hnjArWSn7PvdlLUFHpuU5TbomNyDrr58Y795e6jdvad6B2Uc9K5nn7+SPM6lfPXbpEb5i4xLeXJ4j193Rra6mCgTNDd6Lqgf3z5y7Qet13OavHKTJeFy3yH7qQ6kFh1pItzDUu7Jdx8uh5rZWZJ03qarirNlgeW0aE9QWkVZ8nkc9zPEmtNR+fTqdaw1IBS9ekTTd41uNSqy4MOOii2zQ477CDDhg2Td955x1zX8wkTJsiAAc0TpKZNm2ZCxoULF8a2ce7D3sbeh1ereu/evWOnoUOHtsGrBQAAAACg83APy9Hr9rqJdihY7hr0UudqbXa3kjsjsf1G9ZUfjOwT10LeGm9/XS7vryw3l3Oy0llx07+iMeh0dNXPEbpu2zta6Zmk0zstejy6Lmg6w4/U0vrBnoWTvpPDAxzIUg07RWTVg5Pj2rCbA0ttCW8a3GMfvGsfjU3xk73uZSpxLeGhLM9J4ulWRvb9wbVN62XuHLdeZqpqT/TwwDISiZhW7e9///uy0047mdvWrl0rubm5UlpaGrethpN6n72NM6y077fvS7aNhprV1YlrP/z+97834al9Wrkyfh0MAAAAAAC6E69hOXp9/Zba2DZV9Y1yxtML4h536hOfxlqmNejUwO+nO0fbw6WpZXtYWb65rOsxFuVmm8vrtjbvt6V0+vZls6PDVEry4tuVbaE0KxrTCQh/870Rsctzl2w070GKjvBA7G5tDXW1cjMdt1X81LPb+8ya/8ZNDo89USqWJdtFoqFw/cYFsn7WMbHQsmbFK/ZG8t1b0WX5RjV+51FhGZHRke88B/f4qVn9tvtAEq63pDLSa71MdE6dJrDUtSwXLFggjzzySEcfihnMU1JSEncCAAAAAKC9Khvbm9+wnJWb44t8vCIubZl2Bp2POdag/GZzjaytiIaT+TnhWEv4OkcQ2hohR/Y2ZVTfuNuG9s4PvJ6mPkYrGtMJCJd82xxuamWlvvaWVlgeOWFg7PKEgcXyr5N3My3mU8f2T2s/c2r2kr9UHBYNLWO3hmRaw5LY5HDfykqvCsxQSM6sec++IbZ2pIaWG2b/vHnTuuhAILOtIxg156FwbB+hAIGl7vu71893HEf8tPasooFURvYAnSKw1DUlZ82aJa+++qpsu+22sdsHDhxo1qHctCn6xbfplHC9z97GPTXcvp5qGw0iCwqiZdsAAAAAAHSWysb2Di39huW4Azi/PM5vKriuOWm3lb+zvFwKmgLLDY4KywO336bFx20/X3VDY2xdzAOa9je0rEDO+P6IwPvRikZtaQ+iICcsr3610fNYWrpeqMrNCsu8c6eYsFK/AxoG+3EO+CnKbY53rq84SX678Xz5rG64RMLR1ufeu//ehJY6MXwHiS7H5xVOxkLLpuu/rvlApjYsTVg7svzdqz3ja2cwmms1mPO7Kp+P7SOc0zxJ3Y/fvpsPVY8xQ7336LQ6NLDUL5mGlU899ZS88sorZjCOkw7ZycnJkZdffjl22+LFi81gnr333ttc1/P58+fL+vXNk7N04riGkePGjYtt49yHvY29DwAAAAAAOoJXZaO9nmJ7GlCc1+bPcel/PpfVm2vM5fVb6zzXgkyXHWvV1Edka210HcXe+dG284ZGS176Mj5U9JMTDpmQUIPDQ8fHLynnZfKQ3nGvIRPt7XYVqi3Vepr2ACINT0vz49/D2TV7yaEbbpKj62eZ1ueCYfvH7vtd5LPoBa+p4aGQZFuNskNko9xd/5pcUPuO59qRDeVf+IaGdjC6qOIucz61cXnsvvUvnBS3Dqbn60qybxWpWhfXmo7uKdzRbeA63fvhhx+W4uJis9aknux1JXXgzSmnnCLnnHOOqb7UITy/+MUvTNC41157mW2mTp1qgskTTzxRPvnkE5k9e7ZccsklZt/a2q1OPfVUWbp0qVxwwQXy+eefy1133SWPPfaYnH322R358gEAAAAAPZxXZaO9nmJ70Uq+5eWJ8x0yTSstn1u0LmENy+9cg3z8llfce3j8fAtlOcK7zTXRwLIkPyd225Jvq1Ielz7XuIHNrcpP/2IPOW/KqLgKRrecrLAMKsnLfGCZ3RzTpFpP0z68vOwszzVBnd+jUFZ0HVH1w9A6Me+Q19RwyzLrTWrQOL0oHGsDb9ogtnZkdpkOTQq4YKcV/VxUQ/mXKcPGYPuOtqaj++rQwPLuu+82Q22mTJkigwYNip0effTR2DY333yzzJgxQ4466ijZd999TXv3k08+Gbs/KyvLtJPruQaZJ5xwgpx00kly5ZVXxrbRys3nn3/eVFVOnDhRbrzxRrn33nvNpHAAAAAAADqK35qJup5ie1d5BnHQmPj27Z9ObB6wE4T9PHalpZrzxYaEIG78gMS1Dr/+Lnmo+uGq6HJypQXRwLI+EpHhZQWBB9w4XT9jnJyepJ381SXfyuRte0umbIoFltGWeZVqPU27QFJDTq/w1J40bi5nN99vNdbKdo0bvNeydEwFb9i6qmmq9oSEqdplZuBNS9qym9fB9BNs39HWdHRf0TrpDhJddyC5/Px8ufPOO83Jz/Dhw+Xf//530v1oKPrxxx+36DgBAAAAAGgLOlTl0zVbEm8f06/D16/08v6K+BkTo7cpkqyQSNMylSnZm+l6nX52H1oqT/58dxly5Ytxt69PMVncXivTDkO1Jfzk3YbKJS98nrBtjh60JTJuQLHMnDrGtIO7q05veWNZ0uf7dHXi59ZSm6obElrCdT1NXc801XuZlx2Wn04cLH92rHfpDmJD2c0VlpGacjkz/L6cVvTj5rUr7cc5poJHar4zU7X15Ka3aZi58cX/J5Ha+O9EasnDRt13yeRzpOLDm5LsI9qaju6rUwzdAQAAAACgJ5qzuHkeg9Nj81a32+RxreQLWmFpt13b/vjyV4HDyqAq6xrlmQVrE27X5wlynG8u+zbWEr7bUO8qSA0z6yOWXOoRVgatOl3lmKD++wNGS2ts9qiw1PU0tTXdyeuYtMLSnpBuV6juPLAkNmnc3RIukTqZ1vCV/Krmw7hBOxpWWo6p4KkG5GiwmDd4nxa82tRhY/XyF5M+3m5NR/dFYAkAAAAAQAfxW6dw+abqFk8KtyePa+WmVjLquV6/YNYiz+21kq8zzVzWSspb3nBOpm5mDyVKZkPTMJz6xogJP5Ptx2+4UaqqUz2Evo5hQY9/sjqt4UFaFelkrzXpXMPSbk1/4uTdZOKgEnPfzoNK5LrpO7r2lSX5Oc1B5yl7DpOPz90vLoitXvlqwjFcWPuWmeitA3aiE703yl2Vs5qngoeSR0a6DmX1slmBX3M6YWN08I43Z2s6uq8ObQkHAAAAAKAn0+pGr5ZwpWGaVtmly2+69A1zl8hew8sS9qnXNRQ7+Z8fm4BP133U0LC63r9tuy3p9G2vQTy2YaUFsm5LtD28tiHiGyyu2lwjx/7jQ9/9JBtupJ/L/DVbfPett6+paG5R1+E+6YS+etxOC9ZuSWgJd34+zs/sk9Wb5cLnP4sLP51BZ6EjvLRVfHSb70TvaVubW8mdIvXJW97L371a0pVdPEz67PfnlGGjDt6p37jAtZZlyISVOvUc3R8VlgAAAAAAdBCtbvST7qRwuw3cLwBV7opC+zEnPPSRRCLRcOiOIybEBtdkkrt6MJkkA7qlND9Hqq6dLg8ev6tnSKit3nZLuH3Zi3MoTbpVp30KcuLaszNVoepsCQ9ananvq7PCsjA3cR/1m71DyWSyCge0uArS9zFblgd6t5oH7yROKEfPQGAJAAAAAEAH0cq5vKxwWmGaF233ttvAk3GGoHbr+Pym1vHqpqo/nbZdlJv5hsxpY4MPEmpMMqTXfg12ZWhLgg2/6eA2e9/ZPsnpd9X1bdJGX+BRYemW6/q+BKmwzCnzfp3JFI46LGUVZPqSTwh3D/XxmlCOnoHAEgAAAACADrRtaX5aYZqbBo/a7p2KRm/OENQeLOMO3h766BspclTp5WaFpDjPO8Dcbdv4oTYlednSO99722cWrjP3B7Ft7wLv1+AKck2rdNCJQU2yPIbS+NEqzaD02LSd3l5vUs/TPLS4SsmggaVWZTqrLr0qLAuGH+w+2tTHMnjvAFWQktEJ4e7QUtu/R5yxxZwTVvYsrGEJAAAAAEAHcgZMGiNpmDbTZ3q1M6TUwNFvaI8Xjd6cIehn67Z6VgkuL6+WyY4gsjA3W/Yf1Vee8pjcHXJNwDlml8Hy0Iff+B5DRW38lHE/uoakHkeQIFfb15Oteem2bWmBGUqTyrnPLgy8T/vYbjp0fNznVnbJfxImq7e2bd67wtLREu4KPXU4TsWHN7n2Ykkot7dYdZubXkCWhHKKxKqraH5NWXkpA8WSXc+Rio+c+45G4NnFw6VhywqPODz1hHBAUWEJAAAAAEAHqnEMtxnSOz9hwrObu5VbT0FZjn34re84qm9hQoWle91E29otNXHXN26tk6p678ncqWSFQ7G6vxe/2Og5bMerKnL7bYrSep7VFfHH7GeFKzD1osdrpnf7VGzuMiS+AjWVf326JuV0ePdnYQJLRyu5O9CMDsdxV1SGRBqbhwaV7vkHKXK1gKcKLFX+4D0Thupo6/bQU76U/jMeaX6u2DnrUCIYAksAAAAAADpQTUNzwJdsSEyqVu4gVYD20B2/SeLq/Cmj4gJLndo9e/F6z9Bs5ab48G/lptQhn59jJg02x+h37Dpsxx0Iarj33opNaT2PPkWqUDCoYWUFZgCQX8i844DitPa3ta7RhNHJji/XHVjmxK9hGXa9idHhOO5viyWWI7AM5xZLKLdXWoGlVm6un3WM71Ad1qFEaxBYAgAAAADQSSos6wJUS2obeEsGvmjL8qdrKkwYlqyV/OiJgxNarMurGwKtgPjBqqYW44Cc+3n4o2/EL6/VY/eamn7uMwskXfWNVspQUA1zrS3qJdVKkN+0MMB1T3NP2hKeFZZ/f9YcKM+c/Xnca4sOx/E60ubb6sq/knBOUVqBpV/lpnOoDutQoqUILAEAAAAA6EDOlu66xkig9R3THeZi0zxQw7oBxXm++9A1EN9fmbxqMZ02dDe7ANCs++i8PcVjvKamr3BVeLr9eq9hCetCWq5qUz83HraTpLJ2S3OVopuGhs8uWict4RXOOlvnncPLtar1p//4MHZ91aaauEA2OhzHvGrXnpo/w63z/yr1FbrmZPDA0q9yM+hQHSAZAksAAAAAADpLS3hj6trJmVPHBg76fANDy7+lfNDls1sVSLrlhENmYrau8fjEybuZ9R7tdR9vOWx8bDu/40l3arrTC59vkEaPsk2/ik0nnUCux+s37dsvRLUla7tPJdl+VbYjsXzh8/Vxx+cOZN2t2ZKV71kZWfvNG/G3ZOUmPwbPyk2G6iAzCCwBAAAAAOggGqY5Q0qtsLQ0TUsRpP3hwNGx69v1LZTTvz8i8HPq7tdtrTVhnNcwnQ1pTNxORYOzcQOKY2s86rHrZXvdR21nTsVvoI29hmQyWn2o64KG0gwbbfbxPn7ybrHHBQ1RdQp7SyXbr1ZO1jm+M3rRShHIOluzQ2brxMrIxsr4KfDVq+IDTLfEyk2G6iBzCCwBAAAAAOggtY7qSltDisE7Glj93werYtcnDi6RO976OvBz2mGdhnHbFCWvomutZJWM+jp+8+T8pI8/fKeBSaem33jo+OTP7zhPJ2z0q7Z0Vof6hajO1m0vzlvHDYgPTbcpykm533OfXZjyeJMFstHKSC/x37vvXjtbvn3jIt/nYKgO2lJ2m+4dAAAAAIBuTEM3bf3VITa6tqS2a2u4FdQTnyYOftHBOzk+lYf6fLo+Yfxt8ZVxybjDuoqa+GE6mZYsOAsy7XznQSWBgkRtf9ZgtLYh4rk/bUvXSk/dZmy/XjJz6pikoaDfc6Xz2Xq1oruN6ddLFjkqMf9y9MSkx6Wf//Ly5IN8UgWyWhnpnu7tp+LDmyR/0J4mnPSit/vdB7QGFZYAAAAAALSAHR7OX7PFrPmo50GmTzsf//NH5nne7scO+VrKWRkYiViyta5lgeWxkwa7rg9KOzgLMu28ICcr5bE428wnDCr2bP8eN7A4rhU93bCyJXYc0MtjhUeRorzm11RakBN3f1Fu8tebbF1MrfwMUv1pAsZw/PMm45z6DbQXAksAAAAAAFrAXSEYdPq0+/Fu1736VdJ1EVPX7XnT57rUUVlYWddoAkWv7VROVvSShmBTx/SL2+aNZd/FXT9vyui48E0fmio4CzLtPD8nvdjCHkjUmvbvTPE8FhEZXKJDb6JmuaaI98pL3girIa+fh47fNXAgm9Nnh8Djmpj6jY5AYAkAAAAAQAt4VQgGmT6d7PFqycaqtNdF9NzWtak+l7MCdEutd3Wlbnf+lFFSe90MidxwiAnBtu9XFLfN6s01cdeLcrOl2FE5eM5+o1IGZ16BnpuGpeloyVqTbcXrWPR9dYaOGyvr4h7TKzd5YOkX8o4oK0jrNSYOzPGXUzY28H6BTCGwBAAAAACgBQYU5yXcFnT6dLLwSQfhTLpxrhRe9Lw5d7aI+62LaO8nKxSSiYOiId34gYnt0eonD3xg9vsvn9ZzfQ1zFm+Iu+25hfHrZLqPoldeVlzYVpKf3aJA7+idB6XdEu613/Zu/w56LLMXr0+6faqWcDvkdfvJxPgW/VTcA3Mkq7nq063UhJtA+yKwBAAAAAB0Oxry+YV+mdq/1/CTdNqP/cKnbypqfNfF1HURvdjt2Ff+cGwspPOr4NTbdL+/e3qB5768qkS/qahN+lpe/XJjXDtzSX5OiwK9yduWxt2fn51+YNmZJWvpDtISru/XeVNGxd2mofSf5y5J+zuuoeWQEz6QEWdskZDfQgPhbCkafXha+wUygcASAAAAANAjhuFcMGtRxkJMv/Un02nN1fDp1L2Hm8va6Z3taPeOWxdTmtfF1JDTS019ozm/6+2vY68r2RqRydbBdFeJ6v5STbw+6ZF5Ut10DKo4RfAWtMIw3TUsO7sBvXJbVWGp5riqNNNdO9VLdpmG7IkjgnL7jGvxPoHW6F6/fAAAAABAj+c1DEfdMHdJiyd6u/lVL67dUpuy0tN529MLoq3WJ+y6rWeLuX38i9ZtiYWcl3lUcFY3RGJrS9qvK7ZGZJqvzV0lmmwytZN9jO7L6fhsffzjPly1WboVr4U6Hb5/x5spv5NeVZrprJ0abE3L6C+IdnB0FAJLAAAAAEC34hcmSismerv5VS86Q0e/Sk/nbXbA+W1VneRmBfsr+p7Dy3zvc74ue43IHQKuqWlzD6nRyeRBOIswNRzWitZ06Pt199vL42676sUvMt7O35HWuQJttwUBgnSv7146a6cGWdNSz/vPeIx2cHQYAksAAAAAQLeSrBU6U1VpfutP6rqWeRfMkvB5z5nhNuZ57OdzPrfrcbMXb5DahuaWarf6iBULAOsbo9WUQV6Xhpav/OZ7EpS2pVutmEzupKFlOmGjV5u9sx2+J3w3gwTpXtPV01k7NcialnrO2pXoSASWAAAAAIBuxS9M9NLSqjQNAoeVFviGiyroMaiGiCWrUwy2sQPAuhSBpbva7sUUk6ndx+Gu8Eu1fmUy6YSNXpWxer01rc6djTtsbEmQ7jVd3V0VC3R1BJYAAAAAgG41+VsDnT/9aIdA+2xNVdq6rckDxragAWB9Y/IA0V1tpxOkvfgVTror/PwmkweRTtjo2ercilC5M3KHjTkeH0KQ9m73dHXCSnQ3BJYAAAAAgE7Pbz1Iv9ByrxHN6zzmZnknc3rzFXMWt3hq+HZ9CqW9aQCYrMJSX6m72u6LjYlDWpRf4aS7ws+eTO5sQbafK5l0w0avyli9PnVMP+lO7LDxweN3jVXjOmWivRvo6ggsAQAAAACdvpLSa/J3srX+NNS0NWoC5EELFT9txdTw/7f38DRfWTRM/M330n+cMwBMVmG506DihGq7gb1y03seV4WfVwvy+VNGJW1ttj+rdII3fZ5z9huZsB+tEO1Og3eSrdmpRpQVUDGJHi+7x78DAAAAAIBOV0lpB152kKits1Yaa/3V1DcHltv2LjDDcJKx921P1w5iz2Hx07r1GL0q5py0onPvEX0kXc4A0J4s7uUyj4CwyvFe+O6/aXCL3wAXfU/c74tOK9f3Sz+Dsf16ydSx/WTO4g2x6zOnjkk7eHvpi41Jp573hGn2yT5foKcgsAQAAACAdmJXCmpQoev1aQtsdwthWsuvklInVetAGHfAM6A4z3M/1fXNE7dnjBsgd771daDnX7RuS+DPcaFr25F9C+VLnxDKpgGsVnWma6eBxXL5tLEmALztjaX+G7pKHvVYN1TWJd33NoW5MqR3ftpBo1eIed0MaZXP1m3N6DT3zkz/DNDvg5Xm+pVAT0BLOAAAAAB0wjUYeyrPSdFWdFK1VxColZNe76GzJVxDuAO33ybQ82swmswFsxaZz01DR/dSknrsednJ/5ptAliP21OtB1nveP11SVrC3S3yGqymomtiXjp1TIcPcNHP0atCtbsN3vGbGO5X3Qr0RASWAAAAANAO0l2DsbvQgE/Xogyf95w51+upqs7c9H0aN7BYivOyPO/zeg9rHBWWZz69QF79Kr7V2I8Go8kCtRt8Jm7bUnSEG16bpHrY5+u3RlvjL5glN73mfwzuSkQNUVOpqG3oFOG5X7ia7lqYXYXX2qDugUlAT0VLOAAAAAB0YOVgJlpdO2uruYaTzoBPqx7t69fPGOf5GD12Dc/c79OCNRVmSI4EfA8f/2R12kGi0mDUT5BqxfokE7yTcYbZqQLVZGscuisRvdqO/XT0OpF+4aquDdpdQzyvtnoAVFgCAAAAQLvQ4Mjd9ht0vTqvqdldodX8jjeXpXW70vDmgNHx7dv6viXpgvZ8D99Y9p20RLJKPq/1Fd0C5qKej0vVFu4llOL43W3HrV2/syN+I8lCZADdEy3hAAAAANAO7ODIXR04dUy/pI9LFUhmotU8WSDaGs51JIPcbsvJCqUVAnqFjDqgJx2DS/JTtuOmWt+ytfJz0vsrum69TVFu7HpeVjjhvXK2Hac6+rZ+famwpiMAG4ElAAAAALQDDY5+teewuNs0Hvrz3CVJA0K7DdkZSCo7kNSqv9a0mrdlhaauy5fO7fbxzF68IfBzjCgr8AwZXZlnSqd+b3jKtuNk61tmQnV9eu3keTnhuAngtY0Rz89Ov3s6TOfxk3fr0NeXCms6ArARWAIAAABAO3lu0bq460GqIf3akLV9NxNTldtyGNDp+4z0vP1Mn9vt40nHjYeO97x958Elae1nc3VDym12HND6SdU7DczctOvahohn1eS5zyxMGgjqmpBeOkPrtR2udvTEcgAdi8ASAAAAADIk1VqTXsNSUlVD+rXp6u2ZmKrclsOAdLDODo6p31r1eMGUUXKtz8Ad+3iCOm6Xwb6B1m5DS9M61s019YFalltr3IDUrdlBaVbtVRO5fFO1b4WsBoL/PHGyuWyva2mfd8dJ3AC6JgJLAAAAAMgAu7X606bW6k891pr0k6wa0quC0m7fTRbuWQHDVa3SkxYOA5IAz7Fqc03s+vdG9EkaVtqDV4L658erTTis08jdz+ueEp7K5urUgaWGff17Na8ZmY7Dxg8w5499srrFg3ncspOsOZmsQpbWawCdXXZHHwAAAAAAdAfnPrPA5/aFJiBKFi76VbZp8Oa3rqC271qWZYJRv8BKn9dvvxqmOlvBvYYBjbz6RVmxKRo4DisrMO3Xfvv0ew735G4NF+csXm/eDw0ntWrRuU+97n5cMhoO3zB3Sayi0+t5g/jX/DXm9Wpau25LrTm2qWP7y+Pzvol7D751rBmZjndXlEum/WBkH3l1ybee96WqkNX3POhnCQDtjcASAAAAAALSMEwrJe2wTQMtO3zzm3y9YlO1OdftvcJFv6ExqaoyNeTUsNEvnHMGVu7j3lzT4BtWqvOnjDLDgJyWl1eb59I1EIMEXe61MW0aLtq32wN+3PvUmTz225mbFZK6xtQ1iXe8ucwEln7Pa/O7T3Ph5U3BpNLPyv156XvQUuu2JA86Jw4qkalj+8lj81bHvjMakP504uCEz8J25M6DZOl3VQnHlakKWQDokS3hr7/+uhxyyCEyePBgCYVC8vTTT8fdr/9aOHPmTBk0aJAUFBTIQQcdJF9++WXcNt99950cf/zxUlJSIqWlpXLKKafI1q3x/5L06aefyg9+8APJz8+XoUOHyvXXX98urw8AAABA96GVge6Wbw3f7OnaLV3/UMMzrzUvlV9Vpg5NSTaMxDl0x2sKuAZcySLA2YvX+94XdBiP19qYNvfE8+P+8aF5D7TCUY/V+XYGCSuV/Rkke159X4b0zpfORMPFiYNLzICZ62aMk2WXHCSNNxxiTssuPsjcVprvXWuUmxWODR1yrkepFbKsRwmgK+vQwLKyslImTpwod955p+f9Gizedtttcs8998h7770nRUVFMm3aNKmpaf5XLw0rFy5cKC+++KLMmjXLhKC//vWvY/dXVFTI1KlTZfjw4fLhhx/Kn//8Z7n88svlr3/9a7u8RgAAAAAdP/gm2TCcoPu0247dUsVpw8sKzLlWEO49PHEQzIry6liQ6FzzUmk1ZCjJNOcgQ3e8poCnkqx9PegwHr9j91unU98DZ4VjuvK1LDPF8+4ypCRuTc3OIEi4qAU+XnKywqxHCaBbCllaxtgJ6B/ATz31lBx++OHmuh6WVl6ee+65ct5555nbNm/eLAMGDJD7779fjj32WPnss89k3Lhx8v7778tuu+1mtnnhhRfkxz/+saxatco8/u6775aLL75Y1q5dK7m50cWRL7roIlPN+fnnnwc6Ng09e/fubZ5fKzkBAAAAdE7utRmdQZ27FXh4Gmsyasjpt1ZkKv86ebdYNeS0v74jL36xMXaf+5g0l9p5YLTazvl6vPapj/vJAx94BpBagVl7/QxzWUPaIBWgTv2KcmWDz1qNphrwnOjxJdPStSRbyp4+nux5dUaN/g24U/wluOnz13b4ZNWyKv/CWZ6Vpr/7wUi5+bCd2vAIASBz0snXOu2U8GXLlpmQUdvAbfqi9txzT3nnnXfMdT3XNnA7rFS6fTgcNhWZ9jb77rtvLKxUWqW5ePFiKS/3XvS4trbWvInOEwAAAIDOL1k1oTvusddkTFZtaVdmtjSsdK9Pubm6Ie5+9zFpmOasYLSnOTtNHFQs7ywvN8fuF7zZFZhqQHFe2sftF1aqoK3Geux3HNH2YVpWqDmsdL5nXgO0I20QVoaaAuKW2HlwScqwUr+Dfm3xt76xLGFCOgB0B502sNSwUmlFpZNet+/T8/79+8fdn52dLX369Inbxmsfzudw+9Of/mTCUfuk614CAAAA6PySrV/oRSsa/dZkdK792FL2+oK2TTX1KY/HPSzlkHHxf5+pdUzF9qMTvu0W+NUZbIFOtXam294j+khbO3T8wFhYaTts/EATTqYrndjR7tLWcPSfJ072DVO1VV3PWxr+Jhu8pPS7kO4SBwDQ2TEl3MPvf/97Oeecc2LXtcKS0BIAAABoe+5p1jqoJkjLtk0fowFj0KzKXdHolGradCon7DokLtzT17bk2+jakF77NbdZIpuq603QaL/+g7bfJm67r1NMqtZ2br+p0q0RclVuBlGRIqBtzbHY79/TC9aacNY5sX27voUt2m9udtgEwqloQeWEgSUyc+qY2GeswaWG3/p9GtuvV9x96m/vLZdfPf5p3H6umLPYvI5k3/Fk64na9HnT+Z0AQGfXaSssBw4caM7XrVsXd7tet+/T8/Xr46fXNTQ0mMnhzm289uF8Dre8vDzTS+88AQAAAGhbXtOsvVq2kw3Q0YAv3YDRXdFoP0c6waeXkX2L4vanr6WxKQvz2q/laFW3p5DrY37//Gdx26Vaj7K82r+duzWcg3yC2lLbaM5H9S2MDcVpYfe0kZMVMlWew0oL4o7LPbF90bpgg4HcgoSVqqwgRy51BZIaGOrao1XXTjfn7krUYycNSdiP33c83eFFQQchAUBX0WkDy5EjR5pA8eWXX46rdNS1Kffee29zXc83bdpkpn/bXnnlFYlEImatS3sbnRxeX9/8L3s6UXzs2LFSVlbWrq8JAAAAQHrrT7pbtlOFmhoanbffdmmvM+lkP0dr1zqsro+Gdc7X1hJ3v7M8re3TnK8TmFZ6akVgkEnrdqh81P3vm+uFuVmm6lAFbdXOzQrHhZtHTBgotdfNMMOEehdke76fqXbdiqw0zrdV9SmDRjd9D9y8vuNuQUJ4r9AdALqyDm0J37p1q3z11Vdxg3bmzZtn1qAcNmyYnHXWWXL11VfL9ttvbwLMSy+91Ez+tieJ77jjjvLDH/5QfvWrX8k999xjQsnTTz/dTBDX7dTPfvYzueKKK+SUU06RCy+8UBYsWCC33nqr3HzzzR32ugEAAAAEW3/S3bKdLNS0W2Ifmbc6rbf3mYXrTPBkP761reC2qrrGFq+t2Rm99GXzdHO7+vPQ8QPknWXfyYaqaIGIrtWoQWO1KzVtyTqgDZGIDOmdLys3Rdfg3H1oaavfT235/uHYfuYzby339y719qG0lyVwDhHS55q/pkIiPmuWAkB30qEVlh988IHssssu5qR03Ui9PHPmTHP9ggsukDPOOEN+/etfy+67724CzhdeeEHy8/Nj+3jooYdkhx12kAMPPFB+/OMfyz777CN//etfY/fr0Jw5c+aYMHTy5Mly7rnnmv3rPgEAAAB0Hl7TrN1DaFKFmho8rmrBkBlnhVumwsUqR4VlkLZeaafqwEx6duG6WFipdJi1O6xsKf1cnR9ESV5Oq9/PuoZIRsJK+/jSbcX2mibuNWjJzW4332mQ9xqicxZvSOs4AKCz69AKyylTpohl/ivk/y9QV155pTn50WrMhx9+OOnz7LzzzvLGG2+06lgBAAAAtB0NGnXtRjf964I98TrZ8BE78Ek1UdmPM/DMlBpHYKltvVqR2BpdvULTj2Z4Xm3ietNKR/hckp/d6vfTaoeQ3Y/Xd0vDSv2OB10b1O83wBqWALqbTruGJQAAAICew2+Nx5K8bDPx+tOmNSv9Bs7YLbFBJiq7hZoCT3vtylRDbVpSYakVcn84cHRG9tvdJKlhiXPpC5/Hr1U6ZZR0tKBVnvZ3q96VzOrwoH+dvFvCgB4/XpWlQSo0AaCrIbAEAAAA0OH82rArahsCPd5uidVAJx32WpUaeJ7w0EeSSV9/VxU3zdy5piXSr3rUClznoJvH533TqrfRZ0nJtKzdUtviQF6vl+bnBA4rnQN47GNPt0ITALoKAksAAAAAHa61azzaLbFTx/ZP63E5WSE5bPwAU8WZqcrK4rxo67JWhdqVoXp+yxvLOvV6lF3Fuc8uNOcrmobxBOEM+NT5U0bJzgNLPNeUTEfQykbPtVdb0MptD+DRY8/PDpvzdCo0AaCr6NA1LAEAAAAgE2s82i3dN8xdktbj6hqtjA1hsW1JURValJtlTuu31mX0eXuKFR5rnSYzvLRASgtyTDg4tl8vmTl1jAn4rpsRvf+CWYvS/t7YglY2aiCvk9Itj6UI0qWhZdDJ5ADQVVFhCQAAAKDD2ZVj2S2seNtUXS8/aeVQm/ZSWdeY8bBy4uAS8/6VOgbTdHfDygoCbbdua62ZsF117XRz7q5GvH7GuBath3nBlFGBKxtjrdyO2/Q6rdwA4I3AEgAAAECnCS2toBNYPNY37K5TtIPQ4Evfv32269uix7e0MbpXbpa0lcEl3hO4hzcFlTceOj5jk7w1tNTAd+KgaKu1VmXq8+hlvU1byO379FzbsK+dMS7tQH7b3vmx2246ZDyt3ADgo+f88xsAAACADqUt2zp8RNfz0xZZrTpztrb+65PV0phm6pibFTJt3T2ZrsOolX76/s5a1LL29p0HlZiW6dqGSFrB79YMDhLS4lrnEO2wz1QcO6i0Q8Cf//PjpMehYba+N6naqFO1Wtst5C2l+/5o1Sb548tfmet/fW+5DOtTQHs3AHigwhIAAABAAg14nBOu7cnMLX2MXtY1Kuc3DaHRc+fEZz3/yT8+TPuT6OlhpWZ64wYWm8vnPrOgxfu5dOoY0zI9YVB0Xx1hp4Elcde/2RwdquOsdHQPmNEQ8KAx/VK+R1fN+UI6mn7H7bBSLV6/Ne43AABoRmAJAAAAII4dLjonXKcKVlI9RisrtV7Ojhft83OfWRh3f1twT4jWqeAtqWK0W5E7C3092kFvr4OYztRsNzvQ06rXZFoyVVvfN22pTuXTNRVx182ajyGR0vwc3/Un9fv19IK1Sfer71G607jbgvs7bnWiMBUAOhtawgEAAADE8avU03DRbpl1t3evqaj1fMxx//hQssIh31bj5Zui7bq6n0zXSuogmksPHmMCIeeE6CvmLE57X4+cONmEZSOvecm0GHckrTZUzonXrWUHevr59ivKlQ2ViUOBRpQVSEl+dsK062SyQiLLLj7IXN5zeFnss1AabKeSKmw899lo4J2MhoItmcadaV7f8c4SpgJAZ0NgCQAAAPRgXutK+lXqrdhUHVdNadNqSj/1EcucktEQS587nSAsCK08PMJjXcLjH/oorf1oUGeHgrp+ovO1t4ZWHq7bUmuCx6lj+8mcxRsCrSP50PG7eoaUOjW7JWGqO9C7++idPV+jvnZ974J+RlpNuNOgEs81InXZgKCShY0rUrxedxVqR/L6jneWMBUAOhtawgEAAIAe6oJZizzbuFMFUhpwZpKGdBqUZrrC0koSHCVjt+3aLeTOadT2oBfnxOh0W8W1glHXYtTKQ7vV+boZ48x5qnUkDx8/wLeiMujU7FSBntdrtNeOTPXexe23KTT2EnQ/qqVhox77zgMT173sKPZ33LlEQWcJUwGgs6HCEgAAABmd9Iyu8zneMHdJWo+xgzn97DPJrjArycuWitqGjO33hIc+kgeP3zXh+6nfWa8KQg0Sf7770Filo1/LtXuatF1xagdQfoIGZ37Hp2Hlk7/Yw/dxdtDobIFfsG6LNHpUuGpmlpcdDvwaUx1buu3qU8f2T1qZ61Xd6mVYab4s96gI1sctbWpF7yy8Pp9MtfQDQHcTsqxk/0mFqqiokN69e8vmzZulpCR+ch0AAEBPEgtm7IERTef6l3BCy/atjLzjzWWmKlIDotP3GSnXzxiX1j50ineQwMirhTno+oNBFWSHpTqD+3Pz+n7qdzmTwZG9Px0c4/UXrHQDtEwdn37OXm3IOw8qkY/P2U9aIuf858RvOHvQUDbV988Of1Ptz708QbrHAQDonPkagWWG31AAAIDuzDP8kKbw49yWhR89lQYtOtzGXi9S1x/Ult5Uwa+GlV6VkedNGZVWaKlrCGYydGwPWmG47LvqWIjnFw6KO5wb2H7fT3e1ZdDgrSsdj993RyeI114/o1X7sGkbetCQNtPhMwCg4/M1WsIBAAAQ2GfrtiZOuRWRRevSq9Tr6e3wXqGjDkvRYClVteqtry/1vF33N2fx+kDHpK/Dq024M9IKUq8Qyq+yTjp4CnNna/tti+PJxHdHfz9+FZY63T2d6k+/9nUAQNdFYAkAANCD14H0Og7ld2xZ4VDKic/d9b11t8NrpWmQgNFdUem13p6TBkvu9RHt11Ocm5X0/beH5iSrtgwa9HWWsFKH0HjR9zKIjpjC3NkCtEwfz44DenlWWo8b6D8sKJ21MBlCAwCgJTwAWsIBAEB7rwPZ0nbhdAK8ZMGV37HlXTDLNzDrDOtY+r2mdNulvfarA1zcLayp2o3t914rU4MGvVkhkfEDi83nNaBXrgk47c8hHX6fx8hrXjLVnF1BshbjrPOeS90O3vS+sZ5h52wzj/45t1BWbKqO/Tl306HjaecGgG6KNSw78A0FAABoyTqQanhpgSy75KCkQWK6oaC79dgZQGqQFmToijOUSxZ2TewE61iOvPpF3wrGlgaqqSoS/aoA3cF0OlryGK99TBhUnFBhGiTo6yyStQYHfR3nTxkl17UirIY31o0EAKSLNSwBAAA6Ma2c8wpalm+qjlXkBW0XTkb35V4n0X5erWpatzU67TnoGoC6v2SVeZ+sqTAVmNo2rmvcadtouu3YrW3ntitSvRz3jw/lnydOTju0PPfZhUnvL87zXmVJX0dLg8dMBIqWo0Vc9SvKlbuP3lm6kmStwVqNl6pSVMP2OYs3yHXB5sCgC7e9AwC6l3BHHwAAAEBPo0FcskBSwzo/6QwPSRZ8ajjqF7R5GVCcFwvgktG2Z22b1nM7LNMQMgi7IlEfp/uwH69Vos5ttEJVJwzredB928fmdzx++00V0qoNlXVmO/c+FnoMKOpIepz6+tP53Fsr1FR5q63C2pav1ahB6Hap2ot1iYTONnAHAABkBmtYBkBLOAAAaC3nmpRWijX7tDLRr1V7eFmB9M7PDlSBqMGZe83FltLqvI2VdS0K4PoV5sq6K6el3G7AZbNNqOb3vgwuyYtbz9HZ3q6CtrjbrfdB1hQNuk9dd7KxM6WTnYRf6Kjv+U8e+MD3+5Rs2E4663Ga5QwGpTdxGgAAdHy+RoUlAABAG9MKQQ3ENGyzAlQBTh3b3/d+DWfcFYjahu1VbZiskjNdGiS2NI/bUFUnh9/336Tb6LH7hZX2+2KvTWkfhx0uanu7XZkZhN167275du5XpbPP7hBWZvovBhdMGeVbIakhe3bYv1436FRvu8pSg0k3exAME6cBAOh6qLAMgApLAAB6ntaspeh8rD3lOSjNXbQi7NKpY5JWoCWrhNxS22COWYNP9xqWHSnZpG4NXIOGg17SXSvSbk1O9/PpruyKxtZMENfPIC87LGP76dqlY1JOek72maczbdo5/GVArzxzIOu21AY+DgAA0D6YEt6BbygAAOg6rdn24A6t0nKGkX5ToZ1DS/zCzFQTpYPaeVBxqwK8zkpb2jVMsgPVOYvXm/extiHSqdZ7bC/6nUpWWdoeTEjeNI071h7fVJ2YDm3br70++HQbv9+KVmZey1RvAAC6HQLLDnxDAQBA55UsTNT1Cu3QMUi1n9d6h/r41lSooWexA77wec919KHEVTQ6KxbTWQN1YlPomQ7nc1ERCQBA91aRRr7WfiMCAQAAOriFO9nUbA1N7O2TTem2udc7PO4fH0pWOJSxITfo/vT7ooaX5re4Lf3w8QNk2XfVsmBtRavW0XS2TevvwP4tBBncZIf2LVkr0vlcAAAANgJLAADQ7Son7WE0umaiuuPNZSlDF63wsmngmW47tg6F0RMQVGPT9+XGw3ZKaxmBUNNSBjcdOj4WNLZmDVD/0Tcip+8z0nMdVDsopTISAAC0BQJLAADQoQNqMsGvcjKdgTPFedkm9NHXkCzAQddZm7GzGzew2Jzrb0XD9SDfV/1uNt5wSMLtQaqCk60r6scekmSH/jqc58x9RrLGJAAAaFMElgAA9CBtESz6VTc614Rs6fCboBaua66ObCkN1wjYMkNDLR1OlInhQ92Zs4Vag8G9hpe1aO3IVFXBqcJj/d0lo8fmN90dAACgLYQsK935fz0PQ3cAoPtU4vVkF8xa5FnBpZVdrQkjBlw22zMM0aqtZRcf5BtU+q3Zp4NItLVaQy9tR/U7tlT7QXD2GoSZYk959vvOdVU6OXvnQSVy8Jh+rX5dqSZhj7z6Rc/v9oiyAlnq87vym7i9x/Ay3/A43cneAAAALcWU8AwjsASAzPD7C3VrA7PuKhPhrr2Pz9ZtTbq+YqpqyGT7T1ZFt/OgYnPcqqXhYkletvz92Enm+IK+HgSnAVhJfnba6x/mZIXk7B9sJ5Ek7cLaYj9/zZaMhqEd3Z5uT9O2p1t/sqYi8GM1HBw3QH8TY+KG3KTz23JO805n4vbh9/1Xnl24Lu3gFAAAIFMILDOMwBJAd9SSdtzWBmjJhkK0NDDL9DFmgvsYpo7tL4/P+ybpe+1+zIg+hZ7hQqpw17mfAb1y0woI7UEeP5k4WOYsXu/7Hjqfo74x0qrJxGg9/dy2aUUgpwGYfoTptG/7Vfm5BZkw7ReGTt+hvzzt8RtIV6oKw6A0jH3o+F0TwsK8C2YFDs/T/XMuWQDZElrxylqUAACgoxBYduAbCgBdQbKqOL+/UPs95tDxA+TpX+wR6HmThRfDSwtk2SUHtSp09KsgShYSuP8Cry3Iuo5cOs/vDH/Tye60ZXrdltq0g0Wlxzp1bD/5+ruqlCFnJtjvYaqKyu4sHBLpbEWdzmo7Z7il06fdIZodSJfm53gGYOHznmvR8yaTzuRqr0q/llQx6uvMyw4nvL6R17wky8urpaX8XnM6VaQTB5XIx+fu1+JjAAAA6MoILDvwDQWAriBZiOD3F+pkjwna0p0qvNBQTAUJRjVovPX1pbFQpiA7LNU+Yai2YWaFQwnho1/A6VzTzz5PN8hF93L4+AHy/GfrM9aGPtwRHA7olSfLN6UO0rLCIpGmr7gGjzcdOj5pa7B+L3XNRV2t3D5PFjRmnfecb+hmT01P9bxuQdaw1D9zUlUOphM2ThxcIh+fs1/Gqj3tlvJ1V0wL9F6n+oeGqmunt+gYAAAAelK+xpRwAF2SV1VcZ14DsSUVg8mq9vQvzzqBN519iKPdN1louGjdFt9Q0I8GEnYoocdWmJslqzfXmJBQK70Gl+SZxGRVilBG/9KvVWxeNFjU49HP2SsE8QsrlR6/nuzp1bomYkVtQ9JjsVzndiip4efv9t0u9n3TzxXdl36f7zl6ZxOmpVMtmIo79Iv+VhfKik3VvqHhYycGq2pU+meDhuzptBPrnw9eoWDQ9m8vurRAMvrnd5CKQ61EbsnkbSf9s7el62nqdyDoe+0XQOsfbWP792rBswMAAPQ8TAkPgApLoHPxq4pr7eCWtlj7UPf5m8c/kQ1V9bHbUlXsaRh382tLAq0LaL9mr+dpafuqXQHU3ab7oufIVDWku1IvWTWtXcVb2xBJGYgFGXKS6bULg2jJkJdUUlU1+lVDtrTtOlm46ld1qqH0xqb1PzW0/alZw3VDq95795+f9p/7rXkvAQAAujpawjvwDQXQtlIFBrXXz/B9nDPQs6vk7LUKF67dEhcQeoWKXoGmclYw6uALDSxSVe+prJBkZFiJtkpnet1C+/UDXY1zgrNXm649pVkriVMFml7hkrMS0t0i7ReI2WuVtlfw2BqZDkpTVaUGDfD8Ps/Y0g0BWt7bOwjuiNAZAACgMyOw7MA3FB0/xbgj24kzVaHXmv3Y75lzgMfwAFN/09l3OpOO7VAv2etJ9RgdSFJVHzEVMEFDNA0l1I4DepkJzS2tFAyLSMtWPAOQDg3wc7LC5s9l/f2WFuTId1V1aYX67srFZIFRqiAt2ZqFyRBSBf9HpiCVpsneWx061dpKSAAAALQfAssOfEO7s1QhWqr7vdbC07+gFuVmy5amajRnAJbsLznu9q10Qky/duLdhvaWDVtq44I+51p1qcI6v/ZZXSuvrjESC902NB2331qEqdqdkwWSD/x3RVxLcCpe05299m+/Dr+qwUxVCgLoufxag4MONdI28CcDTqsPsl9adzMnWVUqAAAAeo6KNPI11rDM8BvaHXithddVEJwBQHDu9f7sCrYg7dJOWs+886ASU/H257lLElqizTYpJignCwjdgVdxXrZUNzRKfaNlquTP3GdkWpV67v06B6ToPwARpgEAAACZR2Dp484775Q///nPsnbtWpk4caLcfvvtssceqasxuntgGXQKMACg5VIFdu0pyHp/0X+8+lQ2VNX5DnLy2o9XS7Q+xDlBuaq+Ma5KnoAQAAAA6P4qqLBM9Oijj8pJJ50k99xzj+y5555yyy23yOOPPy6LFy+W/v37Z+wN7WqYAgwgHbqEweCSfFOBpxOR6xsjZt3BuoZI2mt96uPP3Xc72WN4WUKVW3cKE90DV/Rp3EFgEOdPGZWwXp/uK1W7dCiDk48VazQCAAAAaAkCSw8aUu6+++5yxx13mOuRSESGDh0qZ5xxhlx00UUZe0O7msKLnjcDDgB0Tl5DRIKs55fO9G931Vwy6bTtJpuY7rfeoHsglh5vtcefURp26hqzXhV87tv82ps1fC3MzUoIE9Npidb3LisUMtuGml6ze5+pJhYH+TyTDSdxtnHr+9IYscwUbAaQAAAAAOhMCCxd6urqpLCwUJ544gk5/PDDY7effPLJsmnTJnnmmWfitq+trTUn5xuq4WZ3DCzD5z3X0YcA9Hh2YGhPRtYhVMnCLq9199wDku47dlJCiOc3UTdIaNaadf3cIWQ66w36HVu6A1FaUhXYXkEgk48BAAAA9AQVtITHW716tQwZMkTefvtt2XvvvWO3X3DBBfLaa6/Je++9F7f95ZdfLldccUXCG9sdA0sqLNFV2cGX3U6sVX1WwGrB0X0LZXl5dayCTgO+ssIcWb05Ohk9VWVdTlZIhpcWyFffVqU8TjtY8wveklXOtXUY2FWm+NKCDAAAAABdH4FlKwPLnlRh2VFrWLoDo1RywyGpa4OhQBpUVdY3SKOr47QgOyx1kUjC7ensV3m1wtqtqCP6FMj7Kze36Wv2qorTz/ymuUsCrTeox/m9EWUya9E6abSSvz6vVuNUod/ZP9guFq75DepwrvXnfkyQyjhlt+sGDeGChnfOYw4ySITgDQAAAADQU1VQYdm6lvDWvKHdaUq4tkAesmN/+fibirg16dSGplDGDq+qGxqlvtHyrPAKUgHmnkbrFUx57Uer6zQwWrC2Ii5Q06DtnqN37hTVYcmkCrCCvnctqYxzf+6ZeM/cAZ6mhEHX8gMAAAAAAN0XgaXP0J099thDbr/99tjQnWHDhsnpp5/eo4fuAAAAAAAAAG0tnXwt2tfZA5xzzjmmonK33XYzweUtt9wilZWV8otf/KKjDw0AAAAAAABATwssjznmGNmwYYPMnDlT1q5dK5MmTZIXXnhBBgwY0NGHBgAAAAAAAKBJyLKszE8y6WZoCQcAAAAAAADaJ18Lt+J5AAAAAAAAACCjCCwBAAAAAAAAdBoElgAAAAAAAAA6DQJLAAAAAAAAAJ0GgSUAAAAAAACATiO7ow+gK7AHqes0IwAAAAAAAADpsXM1O2dLhsAygC1btpjzoUOHpvlRAAAAAAAAAHDmbL1795ZkQlaQWLOHi0Qisnr1aikuLpZQKCTdNeXWQHblypVSUlLS0YcD9Ej8DgF+g0BPxn8HAX6DQE/X3f9baFmWCSsHDx4s4XDyVSqpsAxA38Rtt91WegL9QXTHHwXQlfA7BPgNAj0Z/x0E+A0CPV1JN85mUlVW2hi6AwAAAAAAAKDTILAEAAAAAAAA0GkQWMLIy8uTyy67zJwD6Bj8DoGOxW8Q4DcI9GT8dxDoePwOmzF0BwAAAAAAAECnQYUlAAAAAAAAgE6DwBIAAAAAAABAp0FgCQAAAAAAAKDTILAEAAAAAAAA0GkQWAIAAAAAAADoNAgsu4k//elPsvvuu0txcbH0799fDj/8cFm8eHHcNjU1NXLaaadJ3759pVevXnLUUUfJunXrYvd/8sknctxxx8nQoUOloKBAdtxxR7n11lsTnmvu3Lmy6667Sl5enowePVruv//+dnmNQGfXXr/DNWvWyM9+9jMZM2aMhMNhOeuss9rtNQKdWXv9Bp988kk5+OCDpV+/flJSUiJ77723zJ49u91eJ9CZtdfv8M0335Tvf//7Zh+6zQ477CA333xzu71OoLNqz78X2t566y3Jzs6WSZMmtelrA7qK9vodzp07V0KhUMJp7dq10h0QWHYTr732mvmyv/vuu/Liiy9KfX29TJ06VSorK2PbnH322fLcc8/J448/brZfvXq1HHnkkbH7P/zwQ/NjevDBB2XhwoVy8cUXy+9//3u54447YtssW7ZMpk+fLvvvv7/MmzfPBCW//OUv+Ysa0I6/w9raWhOUXHLJJTJx4kTee6Cd/1v4+uuvm8Dy3//+t9le/5t4yCGHyMcff8xngR6vvX6HRUVFcvrpp5vf42effWb+m6inv/71rz3+M0DP1l6/QdumTZvkpJNOkgMPPLDdXiPQ2bX373Dx4sWmqMU+6eO6BQvd0vr16y39eF977TVzfdOmTVZOTo71+OOPx7b57LPPzDbvvPOO735++9vfWvvvv3/s+gUXXGCNHz8+bptjjjnGmjZtWpu8DqAra6vfodN+++1n/e53v2uDowe6vvb4DdrGjRtnXXHFFRk8eqB7aM/f4RFHHGGdcMIJGTx6oOtr69+g/l3wkksusS677DJr4sSJbfQqgK6trX6Hr776qnlMeXm51R1RYdlNbd682Zz36dMnls5rqn/QQQfFttHWmWHDhsk777yTdD/2PpRu69yHmjZtWtJ9AD1VW/0OAXSu32AkEpEtW7bwOwU68HeoFc5vv/227LfffnwOQDv9Bu+77z5ZunSpXHbZZbznQAf+t3DSpEkyaNAg0wGkSzR0F9kdfQDIPP2Lk7Zq67o+O+20k7lN1zDIzc2V0tLSuG0HDBjgu76B/p++Rx99VJ5//vnYbbqtPsa9j4qKCqmurjZrKwBo298hgI79b6HbDTfcIFu3bpWf/vSnfDRAO/8Ot912W9mwYYM0NDTI5ZdfbpYqAtD2v8Evv/xSLrroInnjjTfM+pUA2v93OGjQILnnnntkt912M8uG3XvvvTJlyhR57733zNyRro4/WbohXSthwYIFZjHyltLHH3bYYeZfy3StBQD8DoGupL3+W/jwww/LFVdcIc8880z3WS8I6EK/Qw1L9B8MdJ0wDU90IKQOKQDQdr/BxsZGMwBS//unQyABdMx/C8eOHWtOtu9973uyZMkSM4TuH//4R5f/WAgsuxldfHzWrFlmAXL9F2fbwIEDpa6uziyK7EzxdQqV3ue0aNEis2jyr3/9a7N4uZNu65xcZe9Dp6RSXQm0z+8QQMf+t9D2yCOPmGouXSzdvVwK0NO11+9w5MiR5nzChAlmH1plSWAJtO1vUJdB+eCDD8xSDPo8dhWZZVmm2nLOnDlywAEH8DGgx+uIvxfusccerQpHOxPWsOwm9D8O+mN46qmn5JVXXon9nzfb5MmTJScnR15++eW4SVIrVqyQvffeO3abTp/Saacnn3yyXHPNNQnPo9s696F06pVzH0BP1V6/QwAd/xv85z//Kb/4xS/M+fTp0/lIgE7w30INTLQlDujJ2uM3qMUq8+fPl3nz5sVOp556qqn00st77rlnO7xSoPPqyP8Wzps3z7SKdwsdPfUHmfGb3/zG6t27tzV37lxrzZo1sVNVVVVsm1NPPdUaNmyY9corr1gffPCBtffee5uTbf78+Va/fv3MdEXnPnSilW3p0qVWYWGhdf7555spVnfeeaeVlZVlvfDCC3yU6PHa63eoPv74Y3OaPHmy9bOf/cxcXrhwYY//DNCztddv8KGHHrKys7PNfwOd2+jER6Cna6/f4R133GE9++yz1hdffGFO9957r1VcXGxdfPHF7f6agZ76/0edmBIOtP/v8Oabb7aefvpp68svvzTb/+53v7PC4bD10ksvdYuPg8Cym9Ds2et03333xbaprq62fvvb31plZWUmdDziiCPMF975HxmvfQwfPjzuuV599VVr0qRJVm5urrXddtvFPQfQk7Xn7zDINkBP016/wf32289zm5NPPrndXzPQU3+Ht912mzV+/Hjz+JKSEmuXXXax7rrrLquxsbHdXzPQU///qBOBJdD+v8PrrrvOGjVqlJWfn2/16dPHmjJliglAu4uQ/k9HV3kCAAAAAAAAgGINSwAAAAAAAACdBoElAAAAAAAAgE6DwBIAAAAAAABAp0FgCQAAAAAAAKDTILAEAAAAAAAA0GkQWAIAAAAAAADoNAgsAQAAAAAAAHQaBJYAAAAAAAAAOg0CSwAAAAAAAACdBoElAAAAAAAAgE6DwBIAAAAAAACAdBb/H9I/G9WAq18IAAAAAElFTkSuQmCC", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "plot_series(\n", - " y_train, y_test, y_pred, labels=[\"Treino\", \"Teste\", \"Previsão com ML + Diferença + Normalização\"]\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], - "source": [ - "from tsbook.forecasting.global_reduction import GlobalReductionForecaster\n", - "from typing import Optional\n", - "\n", - "\n", - "model = GlobalReductionForecaster(\n", - " regressor,\n", - " window_length=30,\n", - " steps_ahead=12,\n", - " normalization_strategy=\"divide_mean\",\n", - ")\n", - "\n", - "model.fit(y_train, X=X_train)\n", - "y_pred = model.predict(fh=y_test.index, X=X_test)" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(
, )" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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pVZWvvjii0wIBwAAAAAAnSiwrG7TQ+loAhEWFExJh8P06NHDBGtaTWhXXV0tK1askBEjRkh+fn7LvVMAgBbBn+MAAAAAWsKaf0yUuu+XmN8H/fhdyRvQONy4KypLkq85sYYlAAAAAAAAkGlUWDYZgSUAAAAAAACQaaxh2WQElgAAAAAAAECGReyBZT1rWKaDwBIAAAAAAADINFrCO2Zgec0110ggEIj72XnnneMGIZxzzjnSu3dv6d69u5x44omyYcOGuH2sWrVKjjrqKCksLJR+/frJpZdeKvX19XHbzJs3z0yzzsvLk1GjRslDDz3Uaq8RAAAAAAAAXRAVlh23wnLcuHGybt262M9//vOf2H0XXnihvPTSS/L000/LW2+9JWvXrpUTTjghdn8oFDJhZW1trbz77rvy8MMPmzBy5syZsW10grduc/DBB8uCBQvkggsukLPOOkvmzJnT6q8VAAAAAAAAXbAlPFTTpsfS0WS3+QFkZ8uAAQMSbtcR53/729/ksccek0MOOcTc9uCDD8ouu+wi8+fPl3333Vfmzp0rS5Yskddee0369+8vEydOlOuvv15++9vfmurN3NxceeCBB2TEiBEya9Yssw99vIait99+u0ybNs31mGpqasyPfew6AAAAAAAA4BuBZcetsPz6669l0KBBsuOOO8qpp55qWrzVxx9/LHV1dXLYYYfFttV28aFDh8p7771nruvl+PHjTVhp0RBSA8bFixfHtrHvw9rG2oebG2+8UXr06BH7GTJkSMZfNwAAAAAAADqxSKjxV4budJzAcp999jEt3K+88orcf//9pn37gAMOkPLyclm/fr2pkOzZs2fcYzSc1PuUXtrDSut+675k22ioWVVV5XpcV1xxhanwtH5Wr16d0dcNAAAAAACATo6hOx0zsPzhD38oJ598suy2226m6vFf//qXbN26VZ566qm2PCwznKe4uDjup7NxDjty/mhLfXP2/fzzz2f0eAEAAAAAADrqGpal710nax6ZJBXfPNemx9RRtHlLuJ1WU44ePVq++eYbs66lDtPRANNOp4Rba17qpXNquHU91TYaQhYUFEhXZR90dMcdd5jzYb/tkksuaetDBAAAAAAAaHEaImqY+O3dRRkNFSP1ts7eSEjqNi+SjbNPIbTsaIHl9u3bZdmyZTJw4ECZNGmS5OTkyOuvvx67f+nSpWaNy8mTJ5vrerlw4ULZuHFjbJtXX33VhG9jx46NbWPfh7WNtY+u8AVxo0Gu9aPrdGpVpP22J554wgwoys/PN2uH3nfffbHHapB87rnnmvdJ7x82bJhZ91MNHz7cXB5//PFmn9Z19cILL8gee+xhHqNrll577bVSX1/fYq8RAAAAAAAgGc1eNETUMFEneWcyVIzUVzpv0b5U2Tr/D7wp7XlKuFbxHX300SbwWrt2rVx99dWSlZUlP/nJT0yIduaZZ8pFF10kvXr1MiHkeeedZ4JGnRCupk6daoLJ008/XW655RazXuWVV14p55xzjmnrVmeffbbcc889ctlll8n//M//yBtvvGFazl9++eUWe12RSMTlQ5lc5bKXZNMrZ5gPrn6ArS9I3yP+TwpHHu17P4HsQhMUNsejjz4qM2fONOdt9913l08//VR+8YtfSLdu3WTGjBly1113yYsvvmjOow5B0jU+rXU+P/zwQ+nXr5+Z6H7EEUeY91O98847csYZZ5jH6jqlGkz/8pe/NPfp+w4AAAAAANDaSuffEMtinKFit1HHN2/ntpZw241SV7q0efvtAto0sFyzZo0JJ7///nvp27ev7L///jJ//nzzu7r99tslGAzKiSeeKDU1NWadS3uln4Zhs2fPll/96lcmyLQCteuuuy62zYgRI0w4eeGFF8qdd94pgwcPlr/+9a9mXy1Fw8qV95Y09dFxl9EQ079h55RKIKebNIcGiLNmzZITTjghdg6XLFkif/rTn8z51SrXnXbaybxfGo5q4Gyx3jtt77fa8pVWU15++eXm8UorLK+//noTJBNYAgAAAACAtlBf+pUti8l0qKiNzc7QMiA5JWMysO/OLRDRckAkpRPFteJTJ4Y7B/BUV1eb6eYa6mmrswrXVTQjsGweDSyDaQaWOqn9ggsuMOuFVlRUSPfu3c36nhoWW7R1W8+Brv/5ySefyOGHHy69e/c2VZTTp0831a4WDTGfe+45Oe644+KCTG35tyouVSgUMudPn7OwsLDZrx0AmsLtz3EAAAAAXYMuyaddrvGhZUBy+4yXHU77qFn7XnFXd5Fwbdx+9Xn6TX9Kuo1qzEy6irIk+Vq7qrDsrLQtW4PDdKx9Yn+p+35Jwhckp/c4GfTjd9J67ubQUFH95S9/kX322SfuPits1HUo9S/3//73v+W1116TH/3oR3LYYYfJM888k3S/WmVpVW3aERAAAAAAAIC2ULLvlWZJvngR6bnvlc3edyAQbEx5AkHJ7b2r2W9XDCvTRWDZArTCMN227JLJMxu+INa6CdFLvT3disnm6N+/vwwaNEiWL18up556qud2moSfcsop5uekk04ylZZbtmwx643qsCStnrTTkFOHJo0aNaoVXgUAAAAAAEBquk5lv+lPxkLLQE6R9J32t4yEipFIYzbSfeefSt9pf+ct8YnAsp19QXRRV10nQdczaKvUXSshzz//fFOmq0Gkrh/60UcfSWlpqRmCdNttt5kJ4TqQR9vGn376abNepa5bqXQyuE5m32+//czwo5KSEjPER1vHdUiPBpz6uM8++0wWLVokN9ygC9wCAAAAAAC0Pvtwnfwd9s9cFmMbumMPL5EagWU7+4I0ewJVBpx11llmTck//vGPcumll5phRuPHjzfrXKqioiIzlf3rr782beJ77bWX/Otf/4qteakDezTY1LbyHXbYQb799lsz5EgHJOlApJtvvtlUYe68887muQAAAAAAANqFgHa8Zoh9SrjrxHB4YehOCwzdAQB0HPw5DgAAAGDFHbnmJBTueLT0P+afzT4hOuP62zvzYte7jT5Z+h35aJc+0WVpDN1pHAMNAAAAAAAAdGXB6MDhZnNUVNISnh4CSwAAAAAAAEAFMhSVOVvAaQlPC4ElAAAAAAAAoHllpqIyAstmIbAEAAAAAAAAMtgSHhFnhSVTwtNBYAkAAAAAAACoQIbWsAyHEobwwD8CSwAAAAAAAHRZ9jAxkKnAkpbwZiGwBAAAAAAAQNcVrs/40J2IM7B0VFwiOQJLAAAAAAAAdFmRcG3mp4Q71rBMWNMSSRFYAgAAAAAAoMuKhOpaviWcCsu0EFii1T300EPSs2dP39s/++yzZvurrrpKXn31VTnnnHNa9PjQNIFAQJ5//vnY9S+//FL23Xdfyc/Pl4kTJ3JaO5EpU6bIBRdcELs+fPhwueOOO1rt+fkzAQAAAEBGxVVYBjK0z1DyABNJEVh2YT/72c9MyKQ/ubm5MmrUKLnuuuukvt62dkMLOOWUU+Srr75KK5z4xz/+IWvXrpVf/epXMmPGDOlMwY+e/5tuuinhvqOOOsrcd80113gGRa35GcnJyZH+/fvL4YcfLn//+98lHI7/w3bdunXywx/+MHb96quvlm7dusnSpUvl9ddfb7Vj7ii+/fZbc1779esn5eXlcfdpwGt/39u7Dz/8UH75y1+22vN15j8TAAAAALS+SKgxsIxEMrPWpLMFnJbw9BBYdnFHHHGECZq+/vprufjii01I8sc//tF129pa2784NENBQYEJafx65JFH5Oijj5a//e1v8s0338jee+8tncmQIUNM1andd999Z0K+gQMHZvS5NPB0Ppffz4gGbP/+97/l4IMPlt/85jcyffr0uHB7wIABkpeXF7u+bNky2X///WXYsGHSu3fvJh1vpj5zrWHevHmm0jBdGlbeeuutGT2WUCiUECi3pL59+0phYWGrPV9n/zMBAAAAQBuuYZmp1m1awpuFwLIdefbbhbL787Ok8P8uN5d6vaVpwKRBk4ZKWql02GGHyYsvvhirrjvuuOPkD3/4gwwaNEjGjBljbl+9erX86Ec/Mm3avXr1kmOPPdaEWWru3LmmBXjr1q1xz6MB1yGHHOLaEv7ZZ5+ZEKyoqEiKi4tl0qRJ8tFHH5n7vv/+e/nJT34iO+ywgwlExo8fL48//njcvmtqauT88883Iag+t4ZkWvGVjD7mt7/9rQkL9RxodamGH5a33nrLhCB6n4aGl19+eVw4p8HfeeedZ6odS0pKTOXhX/7yF6moqJCf//zn5rXoPjXgS0WDv82bN8t///vf2G0PP/ywTJ06Na1gt6U/I/oe7LHHHvK73/1OXnjhBfPa7OGnvSVcf//4449Nxa69SjTZZ6epnzn74zT40/dLA1JdOqCurs7Xe64B35lnnikjRowwgbo+75133tkKZ1fM5+i2226TjRs3em5TWloqZ5xxhvms6fdAK1n1Hxks1ndKv7tjx441r2/VqlUmQL3hhhvMY7t3726+57rNpk2bzDnU23bbbbfY983vd87J3hKux2JV5dp/rM+Afje1SrdPnz7So0cPOeigg+STTz6J25/++fH//t//M98r/U7vuuuuMnv27Bb9MwEAAABA12Vfw1IiLRRY0hKeFgLLFhCJRKSiriatn8eXfSInv/mwLCxdJzWhenOp1/X2dPajz90cGtbYq9q0yk9benXtSA0MNACaNm2aCeTeeecdE7Jp6KFVePq4Qw891AQn//znP2P70DDoySeflFNPPdX1OfX2wYMHm0BBQy4NB7X9WFVXV5sA8+WXX5ZFixaZttPTTz9dPvjgg9jjL7vsMvN8GvJp8KFBlB7jli1bPF+nBjgactx1113yxRdfyJ/+9CfzOqzqxiOPPFL22msvE6bef//9JtjS4MdOn09DFz0WDZ008D355JPlBz/4gTkODRz1WCsrK5Oec23H13Pw4IMPxm7T0Od//ud/pL3S8HnChAmmNdeNVmSOGzfOVO3q75dccknKz05TP3OWN99801R16qW+N3oO7YFqsvdcqxH1M/j000/LkiVLZObMmSaYfeqpp6SlafhmLcfgRQNZDRU1bHzvvffM91w/o/ZAVj9nN998s/z1r3+VxYsXx8Lu22+/Xfbbbz/59NNPzTID+pnUc3HaaaeZz+nIkSPNdevPDj/fuVRLPuh7bv3oOc/OzjbHYFWUagv3f/7zH5k/f77stNNO5rVYbfH6Xmggq++zVlLq+6FLJmRlZbXonwkAAAAAurBwC7SEs4Zls2Q37+FwU1lfK8WP/L5JJyfiuDzt7cfSenzZaX+Qbjl56T9vJGKCojlz5pjwzaJrEGoAoqGa0gBBAwW9TaumlAZtGlJqS6yGdD/+8Y/lscceMxVrSverFVMnnnii63NrJdill14qO++8s7muAYZFq6g07LLosekxapCkFZBa0aiBogZT1vqJWumoYZeGjLpfJ10/Ux+v22hFqdpxxx1j9993332mCu+ee+4xr1GPS9fK0+o8DbKCwWjOr4HdlVdeaX6/4oorTKiiAeYvfvELc5tuq8f2+eefm+EzyWg4ecABB5iqPg1tt23bZiov2/M6hnpe9LW50YpMDak0ENTf/X52mvqZU1p9qO+ZBlt6bBrO6WdP349U77kG5Ndee23sulZaajCoj9HKzpZkrWGqLc4XXnihCRDttJJSg0oN8DQMV48++qj5jGpFq4bkSsNL/ezq59JOw0CtVrR/JjWMtx6nn+vJkyfLhg0bYpW0yb5zfv7RQ3+UBsha6fq///u/pqpSWZXWlj//+c/mvdSqZv3Mv/baayZ81FB59OjRCe9VS/yZAAAAAKBri6uwzNg0b8calpmq3OwiCCy7OK1g01BJww4NhX7605/GhWTabmkFR0orDnXNOK12s9OqJw0nlFYLakCnIZ+29Wq4ouGR12Twiy66SM466ywzREPDJA1SrNBGqzM17NAwQisftaJO2z2t9fL0OfXYreotK3zS4EIDDzcLFiwwoZa2orrRx2mAY4VjSve/fft2WbNmjQwdOtTcpq20Ft2ftiHr+bJoO6tK1upr0ZBJg9pnnnnGVAhqxZgGfs2l505/LFVVVaaq7dxzz43dphVs1mtKN+S2n6NU/Hx2mvqZU1rRaVXhKW0NX7hwoa/3XN17771mmJAG6Hqe9LOWarq5VaFpfVb1s2m/TasYH3jgAUlFq/+0bfmqq64yYb/z86ifhX322Sd2m37WtG3d/hnXc2b/TFrst1mfSa/PqQaWqb5zflmhu3737SGhBqMa9GvYrM+pz6fVoXrerfdKq12tsNKpJf5MAAAAANC1xQ/dydAgYlrCm4XAsgUUZueaSsd0/GD23bJ46/pYZaUKSEB2Lekv/z3qvLSeOx26dqRWI2nYoeGiMyTTajc7De20HVNDSLfBG0qrtzRwfOKJJ0yb9HPPPZd00IsGpBqUaounrouo06X1sccff7wZAKRVh7o+noYsejy6bmRzhrFY1V/NZbWtW6xJ2vbryu/wE62y1NBMA0S/7bepnH322XEVghoma6XrCSecELtN3/em0PBHKxH98vPZaepnzuv9sM59qvdcP29atTdr1iwTVms4qp+9999/P+njNFyz6LZarahBnEXXZPVLqyz1uZtaAaiv0S1AdvtMJvucZuI7p6Gitobr69cKSjttB9d1KPU5dE1NXW9TX7e1/1TvVUv8mQAAAACga4uEW6DCksCyWQgsW4AGAOm2ZV+9+zSzZqWGlBGJxC719qa0ePulf9nX9d380qEruh6lro+XLIzRYEwDJq2U0hZqrbJKRqup9EdbYnVNP2351cBS22B1OIhWqlmhirb36mARpcGohq26nYYfSqurdD1MDTHcaMih+9EWVKs92G6XXXYx69/ZKwh1/xpi6etpKRraamim1ZbW62suHVCjP84J7em8527eeOMNU72o71emPzuZelw677nVbv3rX/86dpu9etOL/Txq9a0G/k09t1oBqEGyruHq/DzqwCcNRK2WcA38dJ3PTH1O7FJ95/zQz4V+PnTdTR1649y/tq5rq7o1UEmHTtkrQvVc6nO6VVm2xJ8JAAAAALq26tVvxn6vWv2GVHzznHQbdXyz9ulsAaclPD0M3WknThg+Xp4+eIaM7zVA8rOyzeUzh8yQ44c1tm62BxpE6jqNGhjoAJQVK1aYijKdyKshg307HXah055POukkU0XlRltvtT1Z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HSdLly6N26a6ulrOOecc6d27t3Tv3l1OPPFE2bBhQ+z+zz77TH7yk5/IkCFDpKCgQHbZZRe58847E55r3rx5sscee0heXp6MGjVKHnrooVZ5jUB711rfw3Xr1slPf/pTGT16tASDQbngggta7TUC7VlrfQefffZZOfzww6Vv375SXFwskydPljlz5rTa6wTas9b6Hv7nP/+R/fbbz+xDt9l5553l9ttvb7XXCbRXrfn3Qst///tfyc7OlokTJ7boawM6itb6Hs6bN08CgUDCz/r166UzILDsJN566y3zYZ8/f768+uqrUldXJ1OnTpWKiorYNhdeeKG89NJL8vTTT5vt165dKyeccELs/o8//th8mR555BFZvHix/P73v5crrrhC7rnnntg2K1askKOOOkoOPvhgWbBggQlKzjrrLP6iBrTi97CmpsYEJVdeeaVMmDCBcw+08n8L3377bRNY/utf/zLb638Tjz76aPn00095L9Dltdb3sFu3bnLuueea7+MXX3xh/puoP3/+85+7/HuArq21voOWrVu3yhlnnCGHHnpoq71GoL1r7e/h0qVLTVGL9aOP6xQi6JQ2btwY0bf3rbfeMte3bt0aycnJiTz99NOxbb744guzzXvvvee5n1//+teRgw8+OHb9sssui4wbNy5um1NOOSUybdq0FnkdQEfWUt9Du4MOOijym9/8pgWOHuj4WuM7aBk7dmzk2muvzeDRA51Da34Pjz/++Mhpp52WwaMHOr6W/g7q3wWvvPLKyNVXXx2ZMGFCC70KoGNrqe/hm2++aR5TWloa6YyosOyktm3bZi579eoVS+c11T/ssMNi22jrzNChQ+W9995Luh9rH0q3te9DTZs2Lek+gK6qpb6HANrXdzAcDkt5eTnfU6ANv4da4fzuu+/KQQcdxPsAtNJ38MEHH5Tly5fL1VdfzTkH2vC/hRMnTpSBAweaDiBdoqGzyG7rA0Dm6V+ctFVb1/XZddddzW26hkFubq707Nkzbtv+/ft7rm+g/6fvySeflJdffjl2m26rj3Huo6ysTKqqqszaCgBa9nsIoG3/W+h06623yvbt2+VHP/oRbw3Qyt/DwYMHy6ZNm6S+vl6uueYas1QRgJb/Dn799ddy+eWXyzvvvGPWrwTQ+t/DgQMHygMPPCB77rmnWTbsr3/9q0yZMkXef/99M3eko+NPlk5I10pYtGiRWYy8qfTxxx57rPnXMl1rAQDfQ6Ajaa3/Fj722GNy7bXXygsvvNB51gsCOtD3UMMS/QcDXSdMwxMdCKlDCgC03HcwFAqZAZD63z8dAgmgbf5bOGbMGPNj+cEPfiDLli0zQ+j+8Y9/dPi3hcCyk9HFx2fPnm0WINd/cbYMGDBAamtrzaLI9hRfp1DpfXZLliwxiyb/8pe/NIuX2+m29slV1j50SirVlUDrfA8BtO1/Cy1PPPGEqebSxdKdy6UAXV1rfQ9HjBhhLsePH2/2oVWWBJZAy34HdRmUjz76yCzFoM9jVZFFIhFTbTl37lw55JBDeBvQ5bXF3wv33nvvZoWj7QlrWHYS+h8H/TI899xz8sYbb8T+z5tl0qRJkpOTI6+//nrcJKlVq1bJ5MmTY7fp9Cmddjpjxgz5wx/+kPA8uq19H0qnXtn3AXRVrfU9BND238HHH39cfv7zn5vLo446ircEaAf/LdTARFvigK6sNb6DWqyycOFCWbBgQezn7LPPNpVe+vs+++zTCq8UaL/a8r+FCxYsMK3inUJbT/1BZvzqV7+K9OjRIzJv3rzIunXrYj+VlZWxbc4+++zI0KFDI2+88Ubko48+ikyePNn8WBYuXBjp27evma5o34dOtLIsX748UlhYGLn00kvNFKt77703kpWVFXnllVd4K9Hltdb3UH366afmZ9KkSZGf/vSn5vfFixd3+fcAXVtrfQcfffTRSHZ2tvlvoH0bnfgIdHWt9T285557Ii+++GLkq6++Mj9//etfI0VFRZHf//73rf6aga76/0ftmBIOtP738Pbbb488//zzka+//tps/5vf/CYSDAYjr732Wqd4OwgsOwnNnt1+Hnzwwdg2VVVVkV//+teRkpISEzoef/zx5gNv/4+M2z6GDRsW91xvvvlmZOLEiZHc3NzIjjvuGPccQFfWmt9DP9sAXU1rfQcPOugg121mzJjR6q8Z6Krfw7vuuisybtw48/ji4uLI7rvvHrnvvvsioVCo1V8z0FX//6gdgSXQ+t/Dm2++OTJy5MhIfn5+pFevXpEpU6aYALSzCOj/tHWVJwAAAAAAAAAo1rAEAAAAAAAA0G4QWAIAAAAAAABoNwgsAQAAAAAAALQbBJYAAAAAAAAA2g0CSwAAAAAAAADtBoElAAAAAAAAgHaDwBIAAAAAAABAu0FgCQAAAAAAAKDdILAEAAAAAAAA0G4QWAIAAAAAAABoNwgsAQAAAAAAAEh78f8Bp84fOOYVIKQAAAAASUVORK5CYII=", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "plot_series(\n", - " y_train,\n", - " y_test,\n", - " y_pred,\n", - " labels=[\"Treino\", \"Teste\", \"Previsão com ML + Diferença + Normalização\"],\n", - ")" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "tsbook-py3.11", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.11" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/src/tsbook/forecasting/__pycache__/global_reduction.cpython-311.pyc b/src/tsbook/forecasting/__pycache__/global_reduction.cpython-311.pyc deleted file mode 100644 index bf3859d..0000000 Binary files a/src/tsbook/forecasting/__pycache__/global_reduction.cpython-311.pyc and /dev/null differ diff --git a/src/tsbook/forecasting/__pycache__/reduction.cpython-311.pyc b/src/tsbook/forecasting/__pycache__/reduction.cpython-311.pyc deleted file mode 100644 index 3dc2452..0000000 Binary files a/src/tsbook/forecasting/__pycache__/reduction.cpython-311.pyc and /dev/null differ diff --git a/src/tsbook/forecasting/global_reduction.py b/src/tsbook/forecasting/global_reduction.py deleted file mode 100644 index f0c1a9c..0000000 --- a/src/tsbook/forecasting/global_reduction.py +++ /dev/null @@ -1,1431 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# copyright: (c) 2025, authored for the requesting user -# license: BSD-3-Clause-compatible grant for this file by the author -""" -Global hybrid reduction forecaster for sktime: -K-step direct heads + recursive continuation, with per-row normalization. - -Now supports BOTH: -- pooled multi-series/hierarchical data (y/X with MultiIndex where last level is time) -- a single time series (y/X with a single time index) - -Training (global): -- Builds K supervised datasets (one per step_ahead = 1..K). -- Each row uses a lag window of y (length L = window_length) and optional - *concurrent* exogenous X at the target timestamp. -- A row-wise normalization *strategy* can be supplied to map each (lags, target) - into a normalized space before model fitting. The same strategy is applied - at prediction time (per step), with inverse-transform to the original scale. - -Prediction (for requested horizon H): -- For steps 1..min(K, H): use the corresponding direct model h on the **observed** - lag window (no predicted values fed back yet). -- For steps K+1..H: continue recursively with the trained 1-step model, rolling - the window forward with its own predictions. -- Accepts either: - * An absolute MultiIndex fh (matching y’s id+time), or a simple time Index - if the training data was a single series. - * A relative FH/array of positive ints (applied to every series id). - -Normalization strategy API (efficient & flexible): -- Pass either: - 1) a **strategy**: a callable taking a `lags` vector and returning - `(transform, inverse)` functions; or - 2) a **factory**: a zero-arg callable that returns such a strategy. - 3) a **string** shortcut: one of {"divide_mean", "subtract_mean", - "normalize", "minmax"}. -- `transform(lags, target) -> (lags_n, target_n)`; `inverse(y_n) -> y`. - -Includes `mean_window_normalizer()` factory: divides by the lag-window mean. - -Notes ------ -- Univariate target only (one column series per id). -- If X is used in fit, you must pass **future X rows** at all required timestamps - for prediction (for each id, and each requested timestamp). -- This is a from-scratch implementation; not copied from sktime or other libs. -""" - -from __future__ import annotations - -from functools import partial -from typing import Callable, Dict, Iterable, List, Optional, Sequence, Tuple, Union - -import numpy as np -import pandas as pd -from pandas.api.types import is_integer_dtype -from pandas.tseries.frequencies import to_offset -from sklearn.base import clone, RegressorMixin -from sktime.forecasting.base import BaseForecaster, ForecastingHorizon - - -__all__ = [ - "GlobalReductionForecaster", - "make_reduction", - "mean_window_normalizer", - "subtract_mean_normalizer", - "zscore_normalizer", - "minmax_normalizer", -] - - -# --------------------------------------------------------------------------- -# Normalization strategy helpers -# --------------------------------------------------------------------------- - - -def _mean_window_transform( - lags_in: np.ndarray, target: Optional[float], m: float -) -> Tuple[np.ndarray, Optional[float]]: - lags_arr = np.asarray(lags_in, dtype=float) - lags_n = lags_arr / m - tgt_n = None if target is None else float(target) / m - return lags_n, tgt_n - - -def _mean_window_inverse(y_n: float, m: float) -> float: - return float(y_n) * m - - -class MeanWindowNormalizer: - """Callable strategy that scales by the mean of each lag window.""" - - def __call__(self, lags: np.ndarray) -> Tuple[Callable, Callable]: - lags_arr = np.asarray(lags, dtype=float) - m = float(np.nanmean(lags_arr)) if lags_arr.size else 1.0 - if not np.isfinite(m) or abs(m) < 1e-12: - m = 1.0 - - transform = partial(_mean_window_transform, m=m) - inverse = partial(_mean_window_inverse, m=m) - return transform, inverse - - -def mean_window_normalizer() -> Callable[[np.ndarray], Tuple[Callable, Callable]]: - """Factory for a simple per-row normalizer: divide by window mean.""" - - return MeanWindowNormalizer() - - -def _subtract_mean_transform( - lags_in: np.ndarray, target: Optional[float], m: float -) -> Tuple[np.ndarray, Optional[float]]: - lags_arr = np.asarray(lags_in, dtype=float) - lags_n = lags_arr - m - tgt_n = None if target is None else float(target) - m - return lags_n, tgt_n - - -def _subtract_mean_inverse(y_n: float, m: float) -> float: - return float(y_n) + m - - -class SubtractMeanNormalizer: - """Center lag windows by subtracting the mean (per row).""" - - def __call__(self, lags: np.ndarray) -> Tuple[Callable, Callable]: - lags_arr = np.asarray(lags, dtype=float) - m = float(np.nanmean(lags_arr)) if lags_arr.size else 0.0 - if not np.isfinite(m): - m = 0.0 - - transform = partial(_subtract_mean_transform, m=m) - inverse = partial(_subtract_mean_inverse, m=m) - return transform, inverse - - -def subtract_mean_normalizer() -> Callable[[np.ndarray], Tuple[Callable, Callable]]: - """Factory for per-row mean subtraction.""" - - return SubtractMeanNormalizer() - - -def _zscore_transform( - lags_in: np.ndarray, target: Optional[float], m: float, s: float -) -> Tuple[np.ndarray, Optional[float]]: - lags_arr = np.asarray(lags_in, dtype=float) - lags_n = (lags_arr - m) / s - if target is None: - tgt_n = None - else: - tgt_n = (float(target) - m) / s - return lags_n, tgt_n - - -def _zscore_inverse(y_n: float, m: float, s: float) -> float: - return float(y_n) * s + m - - -class ZScoreNormalizer: - """Standardize lag windows using per-row mean and std.""" - - def __call__(self, lags: np.ndarray) -> Tuple[Callable, Callable]: - lags_arr = np.asarray(lags, dtype=float) - m = float(np.nanmean(lags_arr)) if lags_arr.size else 0.0 - if not np.isfinite(m): - m = 0.0 - s = float(np.nanstd(lags_arr, ddof=0)) if lags_arr.size else 1.0 - if not np.isfinite(s) or abs(s) < 1e-12: - s = 1.0 - - transform = partial(_zscore_transform, m=m, s=s) - inverse = partial(_zscore_inverse, m=m, s=s) - return transform, inverse - - -def zscore_normalizer() -> Callable[[np.ndarray], Tuple[Callable, Callable]]: - """Factory for per-row z-score standardization.""" - - return ZScoreNormalizer() - - -def _minmax_transform( - lags_in: np.ndarray, target: Optional[float], lo: float, hi: float, scale: float -) -> Tuple[np.ndarray, Optional[float]]: - lags_arr = np.asarray(lags_in, dtype=float) - lags_n = (lags_arr - lo) / scale - if target is None: - tgt_n = None - else: - tgt_n = (float(target) - lo) / scale - return lags_n, tgt_n - - -def _minmax_inverse(y_n: float, lo: float, scale: float) -> float: - return float(y_n) * scale + lo - - -class MinMaxNormalizer: - """Scale lag windows to [0, 1] range per row.""" - - def __call__(self, lags: np.ndarray) -> Tuple[Callable, Callable]: - lags_arr = np.asarray(lags, dtype=float) - if lags_arr.size: - lo = float(np.nanmin(lags_arr)) - hi = float(np.nanmax(lags_arr)) - else: - lo = 0.0 - hi = 1.0 - if not np.isfinite(lo): - lo = 0.0 - if not np.isfinite(hi): - hi = lo + 1.0 - - scale = hi - lo - if not np.isfinite(scale) or abs(scale) < 1e-12: - scale = 1.0 - - transform = partial(_minmax_transform, lo=lo, hi=hi, scale=scale) - inverse = partial(_minmax_inverse, lo=lo, scale=scale) - return transform, inverse - - -def minmax_normalizer() -> Callable[[np.ndarray], Tuple[Callable, Callable]]: - """Factory for per-row min-max scaling.""" - - return MinMaxNormalizer() - - -_NORMALIZATION_STRATEGY_REGISTRY = { - "divide_mean": mean_window_normalizer, - "subtract_mean": subtract_mean_normalizer, - "normalize": zscore_normalizer, - "minmax": minmax_normalizer, -} - - -def _resolve_normalization_strategy(ns): - """Accept either a factory (zero-arg) or a strategy (lags->(transform, inverse)).""" - if ns is None: - return None - if isinstance(ns, str): - key = ns.lower() - if key not in _NORMALIZATION_STRATEGY_REGISTRY: - options = sorted(_NORMALIZATION_STRATEGY_REGISTRY) - raise ValueError( - "Unknown normalization_strategy string. " - f"Expected one of {options}, got '{ns}'." - ) - ns = _NORMALIZATION_STRATEGY_REGISTRY[key] - try: - import inspect - - sig = inspect.signature(ns) - required = [ - p - for p in sig.parameters.values() - if p.kind in (p.POSITIONAL_ONLY, p.POSITIONAL_OR_KEYWORD) - and p.default is p.empty - ] - if len(required) == 0: - # zero-arg factory -> call it once to get the strategy - return ns() - except Exception: - # if introspection fails, just treat as already-a-strategy - pass - return ns - - -# --------------------------------------------------------------------------- -# utils (pure-python; no sktime private imports) -# --------------------------------------------------------------------------- - - -def _check_regressor(estimator: RegressorMixin) -> None: - if not hasattr(estimator, "fit") or not hasattr(estimator, "predict"): - raise TypeError("estimator must implement scikit-learn's fit/predict.") - - -def _as_positive_int_fh( - arr_like: Union[Iterable[int], np.ndarray, List[int]], -) -> np.ndarray: - """Return strictly positive integer steps, sorted and unique.""" - arr = np.asarray(list(arr_like)).reshape(-1) - if arr.size == 0: - raise ValueError("fh must contain at least one step ahead.") - if not np.issubdtype(arr.dtype, np.integer): - if np.issubdtype(arr.dtype, np.floating) and np.all(np.mod(arr, 1) == 0): - arr = arr.astype(int) - else: - raise ValueError("fh must be an iterable of integers.") - if np.any(arr < 1): - raise ValueError("All steps in fh must be >= 1 (strictly out-of-sample).") - return np.unique(np.sort(arr)) - - -def _infer_freq_from_index(idx: pd.Index): - """Best-effort frequency inference (DatetimeIndex/PeriodIndex).""" - if isinstance(idx, (pd.DatetimeIndex, pd.PeriodIndex)): - if idx.freq is not None: - return idx.freq - try: - return pd.infer_freq(idx) - except Exception: - return None - return None - - -def _future_index_like(idx: pd.Index, horizon: int) -> Tuple[pd.Index, bool]: - """ - Build a future index of length `horizon` "like" `idx`. - Returns (index, absolute?), where absolute indicates absolute time (True) or - simple 1..H relative steps (False). - """ - if horizon < 1: - raise ValueError("horizon must be >= 1.") - - if isinstance(idx, pd.DatetimeIndex): - raw_freq = idx.freq or _infer_freq_from_index(idx) - offset = None - if raw_freq is not None: - try: - offset = to_offset(raw_freq) - except (TypeError, ValueError): - offset = None - if offset is None and len(idx) >= 2: - step = idx[-1] - idx[-2] - try: - offset = to_offset(step) - except (TypeError, ValueError): - offset = None - if offset is not None: - start = idx[-1] + offset - return ( - pd.date_range(start=start, periods=horizon, freq=offset, tz=idx.tz), - True, - ) - return pd.RangeIndex(1, horizon + 1), False - - if isinstance(idx, pd.PeriodIndex): - freq = idx.freq - if freq is not None: - start = idx[-1] + 1 - return pd.period_range(start=start, periods=horizon, freq=freq), True - return pd.RangeIndex(1, horizon + 1), False - - if isinstance(idx, (pd.RangeIndex, pd.Index)) and is_integer_dtype(idx.dtype): - start = idx[-1] + 1 - return pd.RangeIndex(start, start + horizon), True - - return pd.RangeIndex(1, horizon + 1), False - - -def _select_future_rows( - X_future: pd.DataFrame, idx: Union[pd.Index, pd.MultiIndex], allow_fill: bool = True -) -> pd.DataFrame: - """Select rows of X_future at index `idx`, optionally imputing missing rows.""" - if not isinstance(idx, (pd.Index, pd.MultiIndex)): - idx = pd.Index(idx) - - if not allow_fill: - missing = idx.difference(X_future.index) - if len(missing) > 0: - sample = list(missing[:3]) - raise ValueError( - "Missing required rows in X for forecast timestamps. " - f"Examples: {sample} (total missing: {len(missing)})." - ) - return X_future.loc[idx] - - X_aligned = X_future.reindex(idx) - if X_aligned.isnull().values.any(): - X_aligned = X_aligned.ffill().bfill() - - if X_aligned.isnull().values.any(): - missing_rows = X_aligned.index[X_aligned.isnull().any(axis=1)] - sample = list(missing_rows[:3]) - raise ValueError( - "Missing required rows in X for forecast timestamps even after fill. " - f"Examples: {sample} (total missing: {len(missing_rows)})." - ) - - return X_aligned - - -def _flatten_multiindex_to_time( - y_or_X: Union[pd.Series, pd.DataFrame], ids -) -> Union[pd.Series, pd.DataFrame]: - """Return object with *time-only* index for a specific ids tuple.""" - if isinstance(ids, tuple): - key = ids - else: - key = (ids,) - return y_or_X.xs(key, level=list(range(y_or_X.index.nlevels - 1))) - - -def _iter_series_groups(y: pd.Series): - """Yield (ids_tuple, y_single_series_with_time_index).""" - nlvls = y.index.nlevels - id_lvls = list(range(nlvls - 1)) - # keep order stable - group_level = id_lvls if len(id_lvls) != 1 else id_lvls[0] - for ids, y_g in y.groupby(level=group_level, sort=False): - if not isinstance(ids, tuple): - ids = (ids,) - y_flat = y_g.droplevel(id_lvls) - yield ids, y_flat - - -def _build_supervised_table_single( - y: pd.Series, - X: Optional[pd.DataFrame], - window_length: int, - steps_ahead: int, - x_mode: str, -) -> Tuple[pd.DataFrame, pd.Series]: - """Turn (y, X) into (Xt, yt) for one series and one horizon.""" - if window_length < 1: - raise ValueError("window_length must be >= 1.") - if not isinstance(steps_ahead, int) or steps_ahead < 1: - raise ValueError("steps_ahead must be a positive integer.") - if x_mode not in ("none", "concurrent", "auto"): - raise ValueError("x_mode must be one of {'none', 'concurrent', 'auto'}.") - - use_X = (X is not None) and (x_mode in ("concurrent", "auto")) - - values = y.to_numpy() - n = len(values) - - # anchors t valid from window_length-1 .. n - steps_ahead - 1 - max_anchor = n - steps_ahead - 1 - if max_anchor < (window_length - 1): - raise ValueError( - "Not enough observations: need at least window_length + steps_ahead. " - f"Got len(y)={n}, window_length={window_length}, steps_ahead={steps_ahead}." - ) - - rows = [] - targets = [] - t_index = [] - - for t in range(window_length - 1, max_anchor + 1): - # lags y[t], y[t-1], ..., y[t-window_length+1] (newest first) - lag_block = values[t : t - window_length : -1] - if lag_block.shape[0] != window_length: - lag_block = np.asarray( - [values[t - i] for i in range(window_length)], dtype=float - ) - - row = {f"y_lag_{i+1}": lag_block[i] for i in range(window_length)} - target_time = y.index[t + steps_ahead] - y_target = values[t + steps_ahead] - - if use_X: - if target_time not in X.index: - # Feature placeholder; user should ensure X completeness - for c in X.columns: - row[f"X_{c}"] = np.nan - else: - xrow = X.loc[target_time] - if isinstance(xrow, pd.DataFrame): - xrow = xrow.iloc[0] - for c in X.columns: - row[f"X_{c}"] = xrow[c] - - rows.append(row) - targets.append(y_target) - t_index.append(target_time) - - Xt = pd.DataFrame(rows, index=pd.Index(t_index, name=y.index.name)) - yt = pd.Series(targets, index=Xt.index, name=y.name) - return Xt, yt - - -def _build_supervised_table_global( - y: pd.Series, - X: Optional[pd.DataFrame], - window_length: int, - steps_ahead: int, - x_mode: str, -) -> Tuple[pd.DataFrame, pd.Series]: - """Supervised table across all ids for one horizon, stacked with MultiIndex index.""" - nlvls = y.index.nlevels - id_lvls = list(range(nlvls - 1)) - id_names = list(y.index.names[:-1]) - time_name = y.index.names[-1] - - Xt_list = [] - yt_list = [] - idx_list = [] - - # iterate ids - for ids, y_flat in _iter_series_groups(y): - X_flat = None - if X is not None: - X_flat = _flatten_multiindex_to_time(X, ids) - if not X_flat.index.is_monotonic_increasing: - X_flat = X_flat.sort_index() - if not y_flat.index.is_monotonic_increasing: - y_flat = y_flat.sort_index() - - Xt_g, yt_g = _build_supervised_table_single( - y=y_flat, - X=X_flat, - window_length=window_length, - steps_ahead=steps_ahead, - x_mode=x_mode, - ) - - # attach ids to index -> MultiIndex (ids..., time) - if len(ids) == 1: - new_index = pd.MultiIndex.from_arrays( - [[ids[0]] * len(Xt_g), Xt_g.index], - names=id_names + [time_name], - ) - else: - arrays = [[ids[j]] * len(Xt_g) for j in range(len(ids))] - arrays.append(list(Xt_g.index)) - new_index = pd.MultiIndex.from_arrays(arrays, names=id_names + [time_name]) - - Xt_g.index = new_index - yt_g.index = new_index - - Xt_list.append(Xt_g) - yt_list.append(yt_g) - - Xt_all = pd.concat(Xt_list, axis=0) - yt_all = pd.concat(yt_list, axis=0) - - return Xt_all.sort_index(), yt_all.sort_index() - - -def _normalize_supervised_rowwise( - Xt: pd.DataFrame, - yt: pd.Series, - L: int, - normalization_strategy: Optional[Callable[[np.ndarray], Tuple[Callable, Callable]]], -) -> Tuple[pd.DataFrame, pd.Series]: - """Apply per-row normalization to (y-lags, target).""" - if normalization_strategy is None: - return Xt, yt - - Xt_out = Xt.copy() - yt_out = yt.astype(float, copy=True) - lag_cols = [f"y_lag_{i+1}" for i in range(L)] - lag_idx = [Xt_out.columns.get_loc(col) for col in lag_cols] - - for i in range(len(Xt_out)): - lags = Xt_out.iloc[i, lag_idx].to_numpy(dtype=float) - target_value = yt_out.iloc[i] - transform, _ = normalization_strategy(lags) # per-window - lags_n, tgt_n = transform(lags, target_value) - Xt_out.iloc[i, lag_idx] = lags_n - if tgt_n is not None: - yt_out.iloc[i] = float(tgt_n) - - return Xt_out, yt_out - - -def _make_group_future_multiindex( - ids: Tuple, future_time_index: pd.Index, id_names: List[str], time_name: str -) -> pd.MultiIndex: - """Build a MultiIndex combining ids (tuple) and per-group future time index.""" - arrays = [[ids[j]] * len(future_time_index) for j in range(len(ids))] - arrays.append(list(future_time_index)) - return pd.MultiIndex.from_arrays(arrays, names=id_names + [time_name]) - - -def _steps_and_full_future_for_group( - train_time_index: pd.Index, - req_times: Optional[pd.Index] = None, - rel_steps: Optional[np.ndarray] = None, -) -> Tuple[pd.Index, Optional[np.ndarray]]: - """Return full future index for the group, and (if req_times) positions for them.""" - if req_times is not None: - last_t = train_time_index[-1] - max_t = pd.Index(req_times).max() - - if isinstance(train_time_index, pd.DatetimeIndex): - raw_freq = train_time_index.freq or _infer_freq_from_index(train_time_index) - offset = None - if raw_freq is not None: - try: - offset = to_offset(raw_freq) - except (TypeError, ValueError): - offset = None - if offset is None: - inferred = pd.infer_freq(train_time_index) - try: - offset = to_offset(inferred) - except (TypeError, ValueError): - offset = None - if offset is None: - if len(train_time_index) >= 2: - step = train_time_index[-1] - train_time_index[-2] - try: - offset = to_offset(step) - except (TypeError, ValueError): - offset = None - if offset is None: - return pd.Index([]), np.array([], dtype=int) - rng = pd.date_range( - start=last_t + offset, - end=max_t, - freq=offset, - tz=train_time_index.tz, - ) - H = len(rng) - full_future = pd.date_range( - start=last_t + offset, - periods=H if H > 0 else 0, - freq=offset, - tz=train_time_index.tz, - ) - elif isinstance(train_time_index, pd.PeriodIndex): - freq = train_time_index.freq - rng = pd.period_range(start=last_t + 1, end=max_t, freq=freq) - H = len(rng) - full_future = pd.period_range( - start=last_t + 1, periods=H if H > 0 else 0, freq=freq - ) - elif is_integer_dtype(train_time_index.dtype): - H = int(max_t - last_t) - full_future = pd.RangeIndex(last_t + 1, last_t + 1 + max(H, 0)) - else: - req_times_sorted = pd.Index(req_times).sort_values() - H = len(req_times_sorted) - full_future = pd.RangeIndex(1, H + 1) - - if H == 0: - return pd.Index([]), np.array([], dtype=int) - - if isinstance(full_future, pd.RangeIndex) and not np.issubdtype( - train_time_index.dtype, np.integer - ): - req_sorted = pd.Index(req_times).sort_values() - pos_map = {req_sorted[i]: i + 1 for i in range(len(req_sorted))} - steps = np.asarray([pos_map[t] for t in req_times], dtype=int) - else: - pos = pd.Index(full_future).get_indexer(pd.Index(req_times)) - if np.any(pos < 0): - bad = list(pd.Index(req_times)[pos < 0][:3]) - raise ValueError( - "Requested times are not aligned with training frequency for a group. " - f"Examples: {bad}" - ) - steps = pos.astype(int) + 1 # 1-based - - return full_future, steps - - # relative steps path - if rel_steps is None or len(rel_steps) == 0: - raise ValueError("Either req_times or rel_steps must be provided.") - H = int(np.max(rel_steps)) - full_future, _ = _future_index_like(train_time_index, H) - return pd.Index(full_future), None - - -def _union_indices(indices: List[pd.Index]) -> pd.Index: - """Safe union for both Index and MultiIndex without relying on union_many.""" - if not indices: - return pd.Index([]) - u = indices[0] - for ix in indices[1:]: - u = u.union(ix) - return u - - -# --------------------------------------------------------------------------- -# The global forecaster -# --------------------------------------------------------------------------- - - -class GlobalReductionForecaster(BaseForecaster): - """Global hybrid reduction forecaster: K-step direct + recursive continuation. - - Trains **steps_ahead = K** separate direct models for horizons 1..K on pooled - (possibly hierarchical) data, using a lag window from ``y`` (and optional - *concurrent* exogenous ``X`` at each target timestamp). For each series id, - requested predictions beyond K steps are produced recursively using the - 1-step model. - - This class works with **either** a single time series (simple time index) **or** - MultiIndex/Hierarchical data (id levels + time). - """ - - _tags = { - # accept single series AND hierarchical / multiindex series - "y_inner_mtype": ["pd.Series", "pd-multiindex", "pd_multiindex_hier"], - "X_inner_mtype": ["pd.DataFrame", "pd-multiindex", "pd_multiindex_hier"], - # univariate target - "scitype:y": "univariate", - # exogenous supported - "capability:exogenous": True, - # does not require fh in fit - "requires-fh-in-fit": False, - # enforce same index between X and y - "X-y-must-have-same-index": True, - # index type unrestricted - "enforce_index_type": None, - # missing values: we don't guarantee generic handling (y can be imputed) - "capability:missing_values": False, - # strictly oos steps - "capability:insample": False, - # no probabilistic output in this implementation - "capability:pred_int": False, - # soft dependency on scikit-learn - "python_dependencies": "scikit-learn", - } - - def __init__( - self, - estimator: RegressorMixin, - window_length: int = 10, - steps_ahead: int = 1, - normalization_strategy: Optional[ - Union[str, Callable[[np.ndarray], Tuple[Callable, Callable]]] - ] = None, - x_mode: str = "auto", - impute_missing: Optional[str] = "bfill", - ): - # hyper-params - self.estimator = estimator - self.window_length = int(window_length) - self.steps_ahead = int(steps_ahead) - self.normalization_strategy = normalization_strategy - self.x_mode = x_mode - self.impute_missing = impute_missing - - super().__init__() - - if self.steps_ahead < 1: - raise ValueError("steps_ahead must be a positive integer.") - - # learned attributes - self._dir_estimators_: Optional[List[RegressorMixin]] = None - self._estimator_: Optional[RegressorMixin] = None # 1-step model shortcut - self._x_used_: bool = False - self._x_columns_: Optional[List[str]] = None - - # per-group rolling state - self._last_windows_: Optional[Dict[Tuple, np.ndarray]] = ( - None # ids -> window (old..new) - ) - self._train_time_index_: Optional[Dict[Tuple, pd.Index]] = ( - None # ids -> time index - ) - self._ids_: Optional[List[Tuple]] = None # list of ids tuples in fit order - - # index naming - self._id_names_: Optional[List[str]] = None - self._time_name_: Optional[str] = None - self._was_single_series_: bool = False - self._single_id_value_: str = "__singleton__" - self._single_id_name_: str = "id" - - # for update/refit bookkeeping - self._y_train_: Optional[pd.Series] = None - self._X_train_: Optional[pd.DataFrame] = None - self._norm_strategy_: Optional[ - Callable[[np.ndarray], Tuple[Callable, Callable]] - ] = None - self._y_name_: Optional[str] = None - self._y_is_dataframe_: bool = False - self._y_column_name_: Optional[str] = None - - # -------------------- fit -------------------- - def _fit( - self, y: pd.Series, X: Optional[pd.DataFrame], fh: Optional[ForecastingHorizon] - ): - """Fit the global forecaster to (possibly hierarchical or single) training data.""" - _check_regressor(self.estimator) - - self._y_is_dataframe_ = isinstance(y, pd.DataFrame) - if isinstance(y, pd.DataFrame): - if y.shape[1] != 1: - raise ValueError( - "GlobalReductionForecaster supports univariate targets only." - ) - col_name = y.columns[0] - y = y.iloc[:, 0].copy() - y.name = col_name - self._y_column_name_ = col_name - else: - self._y_column_name_ = y.name - - # detect single series and coerce to MultiIndex internally - if isinstance(y.index, pd.MultiIndex) and y.index.nlevels >= 2: - self._was_single_series_ = False - y_mi = y.copy() - if X is not None: - if isinstance(X.index, pd.MultiIndex): - X_mi = X.copy() - else: - raise TypeError( - "X must have a MultiIndex to match y's MultiIndex in fit." - ) - else: - X_mi = None - else: - # single series -> wrap to MultiIndex with one id level - self._was_single_series_ = True - time_name = y.index.name if y.index.name is not None else "time" - id_name = self._single_id_name_ - id_val = self._single_id_value_ - y_mi = y.copy() - y_mi.index = pd.MultiIndex.from_arrays( - [[id_val] * len(y_mi), y_mi.index], names=[id_name, time_name] - ) - if X is not None: - if isinstance(X.index, pd.MultiIndex): - raise TypeError( - "For single-series fit, X should have a simple time index." - ) - X_mi = X.copy() - X_mi.index = pd.MultiIndex.from_arrays( - [[id_val] * len(X_mi), X_mi.index], names=[id_name, time_name] - ) - else: - X_mi = None - - y = y_mi - X = X_mi - - # store names - self._id_names_ = list(y.index.names[:-1]) - self._time_name_ = y.index.names[-1] - self._y_name_ = y.name - - # basic imputation on y - if self.impute_missing == "ffill": - y = y.ffill() - elif self.impute_missing == "bfill": - y = y.bfill() - elif self.impute_missing is not None: - raise ValueError("impute_missing must be 'ffill', 'bfill', or None.") - - # resolve x_mode - x_mode = self.x_mode - if x_mode == "auto": - x_mode = "concurrent" if X is not None else "none" - - # resolve normalization strategy (allow factory or strategy) - self._norm_strategy_ = _resolve_normalization_strategy( - self.normalization_strategy - ) - - # fit K direct heads using pooled data - dir_estimators: List[RegressorMixin] = [] - for h in range(1, self.steps_ahead + 1): - Xt_h_all, yt_h_all = _build_supervised_table_global( - y=y, - X=X, - window_length=self.window_length, - steps_ahead=h, - x_mode=x_mode, - ) - # remember X columns (consistency check at predict) - if self._x_columns_ is None and X is not None and X.shape[1] > 0: - self._x_columns_ = list(X.columns) - - # row-wise normalization on lags & target - Xt_h_all_n, yt_h_all_n = _normalize_supervised_rowwise( - Xt_h_all, yt_h_all, self.window_length, self._norm_strategy_ - ) - - est_h = clone(self.estimator) - est_h.fit(Xt_h_all_n.values, yt_h_all_n.values) - dir_estimators.append(est_h) - - # learned state - self._dir_estimators_ = dir_estimators - self._estimator_ = dir_estimators[0] - self._x_used_ = (x_mode == "concurrent") and (X is not None) - - # store per-group last window and time indices - last_windows = {} - time_idx_map = {} - ids_list = [] - for ids, y_flat in _iter_series_groups(y): - ids_list.append(ids) - if not y_flat.index.is_monotonic_increasing: - y_flat = y_flat.sort_index() - time_idx_map[ids] = y_flat.index - last = y_flat.iloc[-self.window_length :].to_numpy() - if len(last) != self.window_length: - raise ValueError( - f"Group {ids}: not enough observations for last window. " - f"Need window_length={self.window_length}, got {len(y_flat)}." - ) - last_windows[ids] = last.astype(float).reshape(-1) - - self._last_windows_ = last_windows - self._train_time_index_ = time_idx_map - self._ids_ = ids_list - - # store training for potential refit in update - self._y_train_ = y.copy() - self._X_train_ = X.copy() if X is not None else None - - return self - - # -------------------- predict -------------------- - def _predict( - self, - fh: Union[ForecastingHorizon, Sequence, pd.Index, pd.MultiIndex], - X: Optional[pd.DataFrame] = None, - ) -> pd.Series: - """Forecast pooled multi-series or a single series at future horizon.""" - if ( - self._estimator_ is None - or self._last_windows_ is None - or self._dir_estimators_ is None - or self._train_time_index_ is None - or self._ids_ is None - ): - raise RuntimeError("Call fit(...) before predict(...).") - - # determine fh mode - mode_abs_multi = isinstance(fh, pd.MultiIndex) - mode_abs_single = isinstance(fh, pd.Index) and not isinstance(fh, pd.MultiIndex) - mode_rel = False - - req_steps_all: Optional[np.ndarray] = None - if not (mode_abs_multi or mode_abs_single): - # FH object or array-like of relative ints - if isinstance(fh, ForecastingHorizon): - rel = fh.to_relative(self.cutoff) - req_steps_all = _as_positive_int_fh(np.asarray(rel, dtype=int)) - else: - # try array-like relative ints - arr = np.asarray(fh) - req_steps_all = _as_positive_int_fh(arr) - mode_rel = True - - if mode_abs_single and not self._was_single_series_: - raise TypeError( - "Absolute fh as a simple Index is only valid when the model was fit " - "on a single series. For multi-series, pass a MultiIndex fh." - ) - - # prepare exogenous usage - if self._x_used_: - if X is None: - raise ValueError( - "This model was fit with exogenous variables. " - "Provide X with rows for all required forecast timestamps." - ) - if self._x_columns_ is not None: - missing = [c for c in self._x_columns_ if c not in X.columns] - if missing: - raise ValueError( - f"X is missing columns seen in training: {missing}" - ) - # shape validation - if self._was_single_series_: - if isinstance(X.index, pd.MultiIndex): - raise TypeError( - "For single-series prediction, X should have a simple time index." - ) - else: - if not isinstance(X.index, pd.MultiIndex): - raise TypeError( - "For multi-series prediction, X must have a MultiIndex index." - ) - # order X columns to match training - if self._x_columns_ is not None: - X = X[self._x_columns_] - - out_series: List[pd.Series] = [] - - K = self.steps_ahead - - for ids in self._ids_: - time_idx_train = self._train_time_index_[ids] - - # determine requested times/steps for this ids - if mode_abs_multi: - try: - req_times = fh.xs(ids, level=list(range(fh.nlevels - 1))) - req_times = pd.Index(req_times) - except Exception: - req_times = pd.Index([]) - full_future, steps_for_req = _steps_and_full_future_for_group( - time_idx_train, req_times=req_times - ) - if len(full_future) == 0 and len(req_times) == 0: - continue - H = len(full_future) - pos_req = steps_for_req # 1-based - elif mode_abs_single: - # single series: use the provided absolute time Index for the lone ids - req_times = pd.Index(fh) - full_future, steps_for_req = _steps_and_full_future_for_group( - time_idx_train, req_times=req_times - ) - if len(full_future) == 0 and len(req_times) == 0: - continue - H = len(full_future) - pos_req = steps_for_req - else: - # relative steps (common to all ids) - assert req_steps_all is not None - H = int(np.max(req_steps_all)) - full_future, _ = _steps_and_full_future_for_group( - time_idx_train, rel_steps=req_steps_all - ) - pos_req = req_steps_all - - # prepare exogenous block for this group's full future horizon - X_block = None - if self._x_used_: - if self._was_single_series_: - X_needed = _select_future_rows(X, full_future) - X_block = X_needed.to_numpy() - else: - group_future_index = _make_group_future_multiindex( - ids, full_future, self._id_names_, self._time_name_ - ) - X_needed = _select_future_rows(X, group_future_index) - X_block = X_needed.to_numpy() - - # predictions for steps 1..H - preds = np.zeros(H, dtype=float) - - # direct part - last_obs = self._last_windows_[ids].copy() # chronological old..new - for i in range(1, min(K, H) + 1): - y_feats = last_obs[::-1] # newest first to match training - if self._norm_strategy_ is not None: - transform, inv = self._norm_strategy_(y_feats) - y_feats_n, _ = transform(y_feats, None) - else: - y_feats_n = y_feats - inv = lambda v: float(v) - - if X_block is not None: - row = np.concatenate([y_feats_n, X_block[i - 1]]) - else: - row = y_feats_n - - yhat_n = float( - np.asarray( - self._dir_estimators_[i - 1].predict(row.reshape(1, -1)) - ).ravel()[0] - ) - yhat = inv(yhat_n) - preds[i - 1] = yhat - - # rolling state after K direct preds - last_roll = self._last_windows_[ids].copy() - for i in range(1, min(K, H) + 1): - last_roll = np.roll(last_roll, -1) - last_roll[-1] = preds[i - 1] - - # recursive part - for i in range(K + 1, H + 1): - y_feats = last_roll[::-1] - if self._norm_strategy_ is not None: - transform, inv = self._norm_strategy_(y_feats) - y_feats_n, _ = transform(y_feats, None) - else: - y_feats_n = y_feats - inv = lambda v: float(v) - - if X_block is not None: - row = np.concatenate([y_feats_n, X_block[i - 1]]) - else: - row = y_feats_n - - yhat_n = float( - np.asarray(self._estimator_.predict(row.reshape(1, -1))).ravel()[0] - ) - yhat = inv(yhat_n) - preds[i - 1] = yhat - - # roll forward with *original-scale* prediction - last_roll = np.roll(last_roll, -1) - last_roll[-1] = yhat - - # subset to requested steps for this ids and append to output - steps = np.asarray(pos_req, dtype=int) - sel = preds[steps - 1] - - if mode_abs_multi: - idx = _make_group_future_multiindex( - ids, full_future[steps - 1], self._id_names_, self._time_name_ - ) - elif mode_abs_single or (mode_rel and self._was_single_series_): - idx = pd.Index(full_future[steps - 1], name=self._time_name_) - else: - idx = _make_group_future_multiindex( - ids, full_future[steps - 1], self._id_names_, self._time_name_ - ) - - out_series.append(pd.Series(sel, index=idx, name=self._y_name_)) - - if len(out_series) == 0: - # No requested rows (e.g., fh had no times beyond training) -> empty series - return pd.Series([], dtype=float, name=self._y_name_) - - # Assemble output - y_pred = pd.concat(out_series) - - # preserve the order of the provided absolute fh if given - if mode_abs_multi: - y_pred = y_pred.reindex(fh) - elif mode_abs_single: - y_pred = y_pred.reindex(pd.Index(fh)) - else: - y_pred = y_pred.sort_index() - - if self._y_is_dataframe_: - col_name = self._y_column_name_ or self._y_name_ or "y" - return y_pred.to_frame(name=col_name) - - return y_pred - - # -------------------- update -------------------- - def _update( - self, y: pd.Series, X: Optional[pd.DataFrame] = None, update_params: bool = True - ): - """Update rolling windows; refit on appended data if `update_params=True`.""" - if ( - self._estimator_ is None - or self._last_windows_ is None - or self._dir_estimators_ is None - or self._train_time_index_ is None - or self._ids_ is None - ): - raise RuntimeError("Call fit(...) before update(...).") - - if y is None or len(y) == 0: - return self - - if isinstance(y, pd.DataFrame): - if y.shape[1] != 1: - raise ValueError( - "GlobalReductionForecaster update expects a single target column." - ) - col_name = y.columns[0] - y = y.iloc[:, 0].copy() - y.name = col_name - - # Coerce y (and X) to the internal MultiIndex shape if needed - if self._was_single_series_: - time_name = self._time_name_ or "time" - id_name = self._id_names_[0] if self._id_names_ else self._single_id_name_ - id_val = self._single_id_value_ - if not isinstance(y.index, pd.MultiIndex): - y = y.copy() - y.index = pd.MultiIndex.from_arrays( - [[id_val] * len(y), y.index], names=[id_name, time_name] - ) - if X is not None and not isinstance(X.index, pd.MultiIndex): - X = X.copy() - X.index = pd.MultiIndex.from_arrays( - [[id_val] * len(X), X.index], names=[id_name, time_name] - ) - - # roll last windows for groups present in y - for ids, y_flat in _iter_series_groups(y): - new_vals = y_flat.to_numpy(dtype=float).reshape(-1) - if len(new_vals) == 0: - continue - if len(new_vals) >= self.window_length: - self._last_windows_[ids] = new_vals[-self.window_length :] - else: - rolled = np.roll(self._last_windows_[ids], -len(new_vals)) - rolled[-len(new_vals) :] = new_vals - self._last_windows_[ids] = rolled - # update stored time index for the group - existing_index = self._train_time_index_[ids] - new_index = y_flat.index - if isinstance(existing_index, pd.DatetimeIndex): - combined = existing_index.append(pd.DatetimeIndex(new_index)) - elif isinstance(existing_index, pd.PeriodIndex): - combined = existing_index.append( - pd.PeriodIndex(new_index, freq=existing_index.freq) - ) - else: - combined = existing_index.append(pd.Index(new_index)) - - if not combined.is_monotonic_increasing: - combined = combined.sort_values() - - self._train_time_index_[ids] = combined - - if not update_params: - return self - - # Refit from scratch on concatenated data (simple & robust) - y_full = pd.concat([self._y_train_, y]).sort_index() - X_full = None - if self._X_train_ is not None or X is not None: - if (self._X_train_ is not None) and (X is None): - raise ValueError( - "This model was originally fit with X; update requires matching X." - ) - X_full = pd.concat([self._X_train_, X]).sort_index() - - _check_regressor(self.estimator) - - # impute like in fit - if self.impute_missing == "ffill": - y_imp = y_full.ffill() - elif self.impute_missing == "bfill": - y_imp = y_full.bfill() - else: - y_imp = y_full.copy() - - x_mode = self.x_mode - if x_mode == "auto": - x_mode = "concurrent" if X_full is not None else "none" - - dir_estimators: List[RegressorMixin] = [] - for h in range(1, self.steps_ahead + 1): - Xt_h_all, yt_h_all = _build_supervised_table_global( - y=y_imp, - X=X_full, - window_length=self.window_length, - steps_ahead=h, - x_mode=x_mode, - ) - Xt_h_all_n, yt_h_all_n = _normalize_supervised_rowwise( - Xt_h_all, yt_h_all, self.window_length, self._norm_strategy_ - ) - est_h = clone(self.estimator) - est_h.fit(Xt_h_all_n.values, yt_h_all_n.values) - dir_estimators.append(est_h) - - # update learned state - self._dir_estimators_ = dir_estimators - self._estimator_ = dir_estimators[0] - self._x_used_ = (x_mode == "concurrent") and (X_full is not None) - self._x_columns_ = ( - list(X_full.columns) if (X_full is not None) else self._x_columns_ - ) - - # refresh per-group windows and time index maps from y_imp - last_windows = {} - time_idx_map = {} - ids_list = [] - for ids, y_flat in _iter_series_groups(y_imp): - ids_list.append(ids) - if not y_flat.index.is_monotonic_increasing: - y_flat = y_flat.sort_index() - time_idx_map[ids] = y_flat.index - last = y_flat.iloc[-self.window_length :].to_numpy() - last_windows[ids] = last.astype(float).reshape(-1) - self._last_windows_ = last_windows - self._train_time_index_ = time_idx_map - self._ids_ = ids_list - - # store full data for potential next update - self._y_train_ = y_full.copy() - self._X_train_ = X_full.copy() if X_full is not None else None - - return self - - # -------------------- fitted params -------------------- - def _get_fitted_params(self): - """Expose fitted parameters and learned state.""" - return { - "x_used": self._x_used_, - "x_columns": self._x_columns_, - "direct_estimators": self._dir_estimators_, - "one_step_estimator": self._estimator_, - "last_windows": { - k: v.copy() for k, v in (self._last_windows_ or {}).items() - }, - "id_names": self._id_names_, - "time_name": self._time_name_, - "was_single_series": self._was_single_series_, - "y_was_dataframe": self._y_is_dataframe_, - } - - # -------------------- test params -------------------- - @classmethod - def get_test_params(cls, parameter_set: str = "default"): - """Return parameter settings for the estimator tests.""" - from sklearn.linear_model import LinearRegression, Ridge - - if parameter_set == "fast": - return { - "estimator": LinearRegression(), - "window_length": 4, - "steps_ahead": 2, - "normalization_strategy": mean_window_normalizer, # factory form - } - - return [ - { - "estimator": LinearRegression(), - "window_length": 5, - "steps_ahead": 1, - "normalization_strategy": mean_window_normalizer(), # strategy form - }, - { - "estimator": Ridge(alpha=0.1), - "window_length": 3, - "steps_ahead": 3, - "normalization_strategy": None, - }, - ] - - -# --------------------------------------------------------------------------- -# Convenience factory -# --------------------------------------------------------------------------- - - -def make_reduction( - estimator: RegressorMixin, - strategy: str = "recursive", - window_length: int = 10, - steps_ahead: Optional[int] = None, - normalization_strategy: Optional[ - Union[str, Callable[[np.ndarray], Tuple[Callable, Callable]]] - ] = None, - x_mode: str = "auto", - impute_missing: Optional[str] = "bfill", -) -> GlobalReductionForecaster: - """ - Construct a GlobalReductionForecaster. - - In this unified design: - - If ``strategy='recursive'`` and ``steps_ahead is None``, you'll get K=1. - - If ``strategy='direct'`` and you pass ``steps_ahead=K``, you'll get K direct heads - for 1..K and recursive continuation beyond K. - - Any other combination behaves the same as setting K=max(1, steps_ahead). - """ - strategy = (strategy or "recursive").lower() - if strategy not in ("recursive", "direct"): - raise ValueError("strategy must be 'recursive' or 'direct'.") - - if steps_ahead is None: - K = 1 - else: - K = int(steps_ahead) - if K < 1: - raise ValueError("steps_ahead must be a positive integer.") - - return GlobalReductionForecaster( - estimator=estimator, - window_length=window_length, - steps_ahead=K, - normalization_strategy=normalization_strategy, - x_mode=x_mode, - impute_missing=impute_missing, - ) - - -# --------------------------------------------------------------------------- -# Minimal smoke test (optional) -# --------------------------------------------------------------------------- - -if __name__ == "__main__": - from sklearn.linear_model import LinearRegression - - rng = np.random.default_rng(0) - - # -------- Single series example -------- - n = 50 - t = pd.date_range("2024-01-01", periods=n, freq="D") - y_single = pd.Series( - np.sin(np.linspace(0, 4, n)) + 0.1 * rng.standard_normal(n), - index=t, - name="y", - ) - X_single = pd.DataFrame({"cos": np.cos(np.linspace(0, 4, n))}, index=t) - - f_single = make_reduction( - LinearRegression(), - strategy="direct", - window_length=7, - steps_ahead=3, - normalization_strategy=mean_window_normalizer, # factory OR mean_window_normalizer() - ) - f_single.fit(y_single, X=X_single) - - H = 5 - future_times = pd.date_range(t[-1] + pd.Timedelta(days=1), periods=H, freq="D") - Xf_single = pd.DataFrame( - {"cos": np.cos(np.linspace(4, 4 + 0.05 * H, H))}, index=future_times - ) - print("Single-series forecast:") - print(f_single.predict(fh=future_times, X=Xf_single)) - - # -------- Multi-series example -------- - ids = ["A", "B"] - ys = [] - Xs = [] - for i, s in enumerate(ids): - y = pd.Series( - np.sin(np.linspace(0, 4, n)) + 0.1 * rng.standard_normal(n) + i, - index=t, - name="y", - ) - y.index = pd.MultiIndex.from_product([[s], y.index], names=["id", "time"]) - ys.append(y) - - X = pd.DataFrame({"cos": np.cos(np.linspace(0, 4, n))}, index=t) - X.index = pd.MultiIndex.from_product([[s], X.index], names=["id", "time"]) - Xs.append(X) - - y_all = pd.concat(ys) - X_all = pd.concat(Xs) - - f_multi = make_reduction( - LinearRegression(), - strategy="direct", - window_length=7, - steps_ahead=3, - normalization_strategy=mean_window_normalizer, - ) - f_multi.fit(y_all, X=X_all) - - future_times = pd.date_range(t[-1] + pd.Timedelta(days=1), periods=H, freq="D") - fh_abs = pd.MultiIndex.from_product([ids, future_times], names=["id", "time"]) - Xf = [] - for s in ids: - Xs_f = pd.DataFrame( - {"cos": np.cos(np.linspace(4, 4 + 0.05 * H, H))}, index=future_times - ) - Xs_f.index = pd.MultiIndex.from_product([[s], Xs_f.index], names=["id", "time"]) - Xf.append(Xs_f) - Xf = pd.concat(Xf) - - print("\nMulti-series forecast:") - print(f_multi.predict(fh=fh_abs, X=Xf)) diff --git a/src/tsbook/forecasting/reduction.py b/src/tsbook/forecasting/reduction.py index 2eb0279..8b9762c 100644 --- a/src/tsbook/forecasting/reduction.py +++ b/src/tsbook/forecasting/reduction.py @@ -3,50 +3,59 @@ # copyright: (c) 2025, authored for the requesting user # license: BSD-3-Clause-compatible grant for this file by the author """ -Hybrid reduction forecaster for sktime: K-step direct + recursive continuation. - -This forecaster inherits from ``sktime.forecasting.base.BaseForecaster`` and can -train *K* inner direct models to forecast 1..K steps ahead from a shared lagged -feature window of ``y`` (and optional *concurrent* exogenous ``X``). When asked -to predict beyond K steps, it *recursively* rolls a 1-step model forward using -its own past predictions. Setting ``steps_ahead=1`` recovers pure recursive -reduction. In that sense, **DirectReduction is a subcase of RecursiveReduction -with steps_ahead=1**; larger ``steps_ahead`` simply adds direct heads for the -first K steps before recursion continues them. - -New: per-window normalization via a lightweight strategy callback ------------------------------------------------------------------ -Pass ``normalization_strategy`` as a **callable** that receives the y-lag window -vector (1D ndarray, ordered as features are fed to the model: [y_t, y_{t-1}, ...]) -and returns a pair of functions ``(transform, inverse_transform)``. These functions -must each accept and return a 1D ndarray. The same transform is applied to the -lag window **and** the scalar target for that row; predictions are immediately -inverse-transformed back to the original scale. Exogenous ``X`` is never normalized. - -Example strategy (provided below): :func:`meanvar_window_normalizer`, which centers -and scales by the window's mean and standard deviation. - -Highlights ----------- -- Works with any scikit-learn style regressor (fit/predict). -- Univariate ``y``; optional *concurrent* exogenous ``X`` (values at the *target* - timestamps). If used in ``fit``, you must provide future ``X`` rows in ``predict``. -- Handles arbitrary forecasting horizons (not necessarily consecutive). Internally - computes predictions for steps 1..H where H=max requested step, then subselects. +Global hybrid reduction forecaster for sktime: +K-step direct heads + recursive continuation, with per-row normalization. + +Now supports BOTH: +- pooled multi-series/hierarchical data (y/X with MultiIndex where last level is time) +- a single time series (y/X with a single time index) + +Training (global): +- Builds K supervised datasets (one per step_ahead = 1..K). +- Each row uses a lag window of y (length L = window_length) and optional + *concurrent* exogenous X at the target timestamp. +- A row-wise normalization *strategy* can be supplied to map each (lags, target) + into a normalized space before model fitting. The same strategy is applied + at prediction time (per step), with inverse-transform to the original scale. + +Prediction (for requested horizon H): +- For steps 1..min(K, H): use the corresponding direct model h on the **observed** + lag window (no predicted values fed back yet). +- For steps K+1..H: continue recursively with the trained 1-step model, rolling + the window forward with its own predictions. +- Accepts either: + * An absolute MultiIndex fh (matching y’s id+time), or a simple time Index + if the training data was a single series. + * A relative FH/array of positive ints (applied to every series id). + +Normalization strategy API (efficient & flexible): +- Pass either: + 1) a **strategy**: a callable taking a `lags` vector and returning + `(transform, inverse)` functions; or + 2) a **factory**: a zero-arg callable that returns such a strategy. + 3) a **string** shortcut: one of {"divide_mean", "subtract_mean", + "normalize", "minmax"}. +- `transform(lags, target) -> (lags_n, target_n)`; `inverse(y_n) -> y`. + +Includes `mean_window_normalizer()` factory: divides by the lag-window mean. Notes ----- -- This is a brand-new implementation authored from scratch and not copied from - sktime or other libraries. It follows the sktime extension template. -- Scope is intentionally minimal: single series (no panel/global), point forecasts. +- Univariate target only (one column series per id). +- If X is used in fit, you must pass **future X rows** at all required timestamps + for prediction (for each id, and each requested timestamp). +- This is a from-scratch implementation; not copied from sktime or other libs. """ from __future__ import annotations -from typing import Callable, Iterable, List, Optional, Sequence, Tuple, Union +from functools import partial +from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Tuple, Union import numpy as np import pandas as pd +from pandas.api.types import is_integer_dtype +from pandas.tseries.frequencies import to_offset from sklearn.base import clone, RegressorMixin from sktime.forecasting.base import BaseForecaster, ForecastingHorizon @@ -54,34 +63,213 @@ __all__ = [ "ReductionForecaster", "make_reduction", - "meanvar_window_normalizer", + "mean_window_normalizer", + "subtract_mean_normalizer", + "zscore_normalizer", + "minmax_normalizer", ] # --------------------------------------------------------------------------- -# utils (pure-python; no sktime private imports) +# Normalization strategy helpers # --------------------------------------------------------------------------- -# Type alias for normalization strategy: -# given a 1D window vector (lags), return (transform, inverse_transform) pair -# both functions operate on 1D ndarrays and must be shape-preserving. -NormStrategy = Optional[ - Callable[ - [np.ndarray], - Tuple[Callable[[np.ndarray], np.ndarray], Callable[[np.ndarray], np.ndarray]], - ] -] +def _mean_window_transform( + lags_in: np.ndarray, target: Optional[float], m: float +) -> Tuple[np.ndarray, Optional[float]]: + lags_arr = np.asarray(lags_in, dtype=float) + lags_n = lags_arr / m + tgt_n = None if target is None else float(target) / m + return lags_n, tgt_n + + +def _mean_window_inverse(y_n: float, m: float) -> float: + return float(y_n) * m + + +class MeanWindowNormalizer: + """Callable strategy that scales by the mean of each lag window.""" + + def __call__(self, lags: np.ndarray) -> Tuple[Callable, Callable]: + lags_arr = np.asarray(lags, dtype=float) + m = float(np.nanmean(lags_arr)) if lags_arr.size else 1.0 + if not np.isfinite(m) or abs(m) < 1e-12: + m = 1.0 + + transform = partial(_mean_window_transform, m=m) + inverse = partial(_mean_window_inverse, m=m) + return transform, inverse + + +def mean_window_normalizer() -> Callable[[np.ndarray], Tuple[Callable, Callable]]: + """Factory for a simple per-row normalizer: divide by window mean.""" + + return MeanWindowNormalizer() + + +def _subtract_mean_transform( + lags_in: np.ndarray, target: Optional[float], m: float +) -> Tuple[np.ndarray, Optional[float]]: + lags_arr = np.asarray(lags_in, dtype=float) + lags_n = lags_arr - m + tgt_n = None if target is None else float(target) - m + return lags_n, tgt_n + + +def _subtract_mean_inverse(y_n: float, m: float) -> float: + return float(y_n) + m + + +class SubtractMeanNormalizer: + """Center lag windows by subtracting the mean (per row).""" + + def __call__(self, lags: np.ndarray) -> Tuple[Callable, Callable]: + lags_arr = np.asarray(lags, dtype=float) + m = float(np.nanmean(lags_arr)) if lags_arr.size else 0.0 + if not np.isfinite(m): + m = 0.0 + + transform = partial(_subtract_mean_transform, m=m) + inverse = partial(_subtract_mean_inverse, m=m) + return transform, inverse + + +def subtract_mean_normalizer() -> Callable[[np.ndarray], Tuple[Callable, Callable]]: + """Factory for per-row mean subtraction.""" + + return SubtractMeanNormalizer() + + +def _zscore_transform( + lags_in: np.ndarray, target: Optional[float], m: float, s: float +) -> Tuple[np.ndarray, Optional[float]]: + lags_arr = np.asarray(lags_in, dtype=float) + lags_n = (lags_arr - m) / s + if target is None: + tgt_n = None + else: + tgt_n = (float(target) - m) / s + return lags_n, tgt_n + + +def _zscore_inverse(y_n: float, m: float, s: float) -> float: + return float(y_n) * s + m + + +class ZScoreNormalizer: + """Standardize lag windows using per-row mean and std.""" + + def __call__(self, lags: np.ndarray) -> Tuple[Callable, Callable]: + lags_arr = np.asarray(lags, dtype=float) + m = float(np.nanmean(lags_arr)) if lags_arr.size else 0.0 + if not np.isfinite(m): + m = 0.0 + s = float(np.nanstd(lags_arr, ddof=0)) if lags_arr.size else 1.0 + if not np.isfinite(s) or abs(s) < 1e-12: + s = 1.0 + + transform = partial(_zscore_transform, m=m, s=s) + inverse = partial(_zscore_inverse, m=m, s=s) + return transform, inverse + + +def zscore_normalizer() -> Callable[[np.ndarray], Tuple[Callable, Callable]]: + """Factory for per-row z-score standardization.""" + + return ZScoreNormalizer() + + +def _minmax_transform( + lags_in: np.ndarray, target: Optional[float], lo: float, hi: float, scale: float +) -> Tuple[np.ndarray, Optional[float]]: + lags_arr = np.asarray(lags_in, dtype=float) + lags_n = (lags_arr - lo) / scale + if target is None: + tgt_n = None + else: + tgt_n = (float(target) - lo) / scale + return lags_n, tgt_n + + +def _minmax_inverse(y_n: float, lo: float, scale: float) -> float: + return float(y_n) * scale + lo -def _ensure_series(y: Union[pd.Series, pd.DataFrame]) -> pd.Series: - """Coerce y to a univariate Series (first column if DataFrame).""" - if isinstance(y, pd.Series): - return y - if isinstance(y, pd.DataFrame): - if y.shape[1] == 0: - raise ValueError("y DataFrame has no columns.") - return y.iloc[:, 0] - raise TypeError("y must be a pandas Series or DataFrame.") + +class MinMaxNormalizer: + """Scale lag windows to [0, 1] range per row.""" + + def __call__(self, lags: np.ndarray) -> Tuple[Callable, Callable]: + lags_arr = np.asarray(lags, dtype=float) + if lags_arr.size: + lo = float(np.nanmin(lags_arr)) + hi = float(np.nanmax(lags_arr)) + else: + lo = 0.0 + hi = 1.0 + if not np.isfinite(lo): + lo = 0.0 + if not np.isfinite(hi): + hi = lo + 1.0 + + scale = hi - lo + if not np.isfinite(scale) or abs(scale) < 1e-12: + scale = 1.0 + + transform = partial(_minmax_transform, lo=lo, hi=hi, scale=scale) + inverse = partial(_minmax_inverse, lo=lo, scale=scale) + return transform, inverse + + +def minmax_normalizer() -> Callable[[np.ndarray], Tuple[Callable, Callable]]: + """Factory for per-row min-max scaling.""" + + return MinMaxNormalizer() + + +_NORMALIZATION_STRATEGY_REGISTRY = { + "divide_mean": mean_window_normalizer, + "subtract_mean": subtract_mean_normalizer, + "normalize": zscore_normalizer, + "minmax": minmax_normalizer, +} + + +def _resolve_normalization_strategy(ns): + """Accept either a factory (zero-arg) or a strategy (lags->(transform, inverse)).""" + if ns is None: + return None + if isinstance(ns, str): + key = ns.lower() + if key not in _NORMALIZATION_STRATEGY_REGISTRY: + options = sorted(_NORMALIZATION_STRATEGY_REGISTRY) + raise ValueError( + "Unknown normalization_strategy string. " + f"Expected one of {options}, got '{ns}'." + ) + ns = _NORMALIZATION_STRATEGY_REGISTRY[key] + try: + import inspect + + sig = inspect.signature(ns) + required = [ + p + for p in sig.parameters.values() + if p.kind in (p.POSITIONAL_ONLY, p.POSITIONAL_OR_KEYWORD) + and p.default is p.empty + ] + if len(required) == 0: + # zero-arg factory -> call it once to get the strategy + return ns() + except Exception: + # if introspection fails, just treat as already-a-strategy + pass + return ns + + +# --------------------------------------------------------------------------- +# utils (pure-python; no sktime private imports) +# --------------------------------------------------------------------------- def _check_regressor(estimator: RegressorMixin) -> None: @@ -106,6 +294,19 @@ def _as_positive_int_fh( return np.unique(np.sort(arr)) +def _as_int_fh(arr_like: Union[Iterable[int], np.ndarray, List[int]]) -> np.ndarray: + """Return integer steps (can include non-positive values).""" + arr = np.asarray(list(arr_like)).reshape(-1) + if arr.size == 0: + raise ValueError("fh must contain at least one step.") + if not np.issubdtype(arr.dtype, np.integer): + if np.issubdtype(arr.dtype, np.floating) and np.all(np.mod(arr, 1) == 0): + arr = arr.astype(int) + else: + raise ValueError("fh must be an iterable of integers.") + return arr.astype(int) + + def _infer_freq_from_index(idx: pd.Index): """Best-effort frequency inference (DatetimeIndex/PeriodIndex).""" if isinstance(idx, (pd.DatetimeIndex, pd.PeriodIndex)): @@ -128,11 +329,23 @@ def _future_index_like(idx: pd.Index, horizon: int) -> Tuple[pd.Index, bool]: raise ValueError("horizon must be >= 1.") if isinstance(idx, pd.DatetimeIndex): - freq = idx.freq or _infer_freq_from_index(idx) - if freq is not None: - start = idx[-1] + freq + raw_freq = idx.freq or _infer_freq_from_index(idx) + offset = None + if raw_freq is not None: + try: + offset = to_offset(raw_freq) + except (TypeError, ValueError): + offset = None + if offset is None and len(idx) >= 2: + step = idx[-1] - idx[-2] + try: + offset = to_offset(step) + except (TypeError, ValueError): + offset = None + if offset is not None: + start = idx[-1] + offset return ( - pd.date_range(start=start, periods=horizon, freq=freq, tz=idx.tz), + pd.date_range(start=start, periods=horizon, freq=offset, tz=idx.tz), True, ) return pd.RangeIndex(1, horizon + 1), False @@ -144,35 +357,77 @@ def _future_index_like(idx: pd.Index, horizon: int) -> Tuple[pd.Index, bool]: return pd.period_range(start=start, periods=horizon, freq=freq), True return pd.RangeIndex(1, horizon + 1), False - if isinstance( - idx, (pd.RangeIndex, pd.Int64Index, pd.UInt64Index, pd.Index) - ) and np.issubdtype(idx.dtype, np.integer): + if isinstance(idx, (pd.RangeIndex, pd.Index)) and is_integer_dtype(idx.dtype): start = idx[-1] + 1 return pd.RangeIndex(start, start + horizon), True return pd.RangeIndex(1, horizon + 1), False -def _build_supervised_table( +def _select_future_rows( + X_future: pd.DataFrame, idx: Union[pd.Index, pd.MultiIndex], allow_fill: bool = True +) -> pd.DataFrame: + """Select rows of X_future at index `idx`, optionally imputing missing rows.""" + if not isinstance(idx, (pd.Index, pd.MultiIndex)): + idx = pd.Index(idx) + + if not allow_fill: + missing = idx.difference(X_future.index) + if len(missing) > 0: + sample = list(missing[:3]) + raise ValueError( + "Missing required rows in X for forecast timestamps. " + f"Examples: {sample} (total missing: {len(missing)})." + ) + return X_future.loc[idx] + + X_aligned = X_future.reindex(idx) + if X_aligned.isnull().values.any(): + X_aligned = X_aligned.ffill().bfill() + + if X_aligned.isnull().values.any(): + missing_rows = X_aligned.index[X_aligned.isnull().any(axis=1)] + sample = list(missing_rows[:3]) + raise ValueError( + "Missing required rows in X for forecast timestamps even after fill. " + f"Examples: {sample} (total missing: {len(missing_rows)})." + ) + + return X_aligned + + +def _flatten_multiindex_to_time( + y_or_X: Union[pd.Series, pd.DataFrame], ids +) -> Union[pd.Series, pd.DataFrame]: + """Return object with *time-only* index for a specific ids tuple.""" + if isinstance(ids, tuple): + key = ids + else: + key = (ids,) + return y_or_X.xs(key, level=list(range(y_or_X.index.nlevels - 1))) + + +def _iter_series_groups(y: pd.Series): + """Yield (ids_tuple, y_single_series_with_time_index).""" + nlvls = y.index.nlevels + id_lvls = list(range(nlvls - 1)) + # keep order stable + group_level = id_lvls if len(id_lvls) != 1 else id_lvls[0] + for ids, y_g in y.groupby(level=group_level, sort=False): + if not isinstance(ids, tuple): + ids = (ids,) + y_flat = y_g.droplevel(id_lvls) + yield ids, y_flat + + +def _build_supervised_table_single( y: pd.Series, X: Optional[pd.DataFrame], window_length: int, steps_ahead: int, x_mode: str, - normalization_strategy: NormStrategy = None, ) -> Tuple[pd.DataFrame, pd.Series]: - """ - Turn (y, X) into (Xt, yt) for supervised learning for a single horizon. - - - Features: y lags [t, t-1, ..., t - window_length + 1] (most recent first) - - Target: y[t + steps_ahead] - - Exogenous concurrent: X at time t + steps_ahead (if used) - - Normalization: if `normalization_strategy` is provided, for each row: - * obtain (transform, inverse_transform) = normalization_strategy(y_lag_vector) - * replace lag features by transform(y_lag_vector) - * replace scalar target by transform([target])[0] - Note: X is *not* normalized. - """ + """Turn (y, X) into (Xt, yt) for one series and one horizon.""" if window_length < 1: raise ValueError("window_length must be >= 1.") if not isinstance(steps_ahead, int) or steps_ahead < 1: @@ -181,13 +436,6 @@ def _build_supervised_table( raise ValueError("x_mode must be one of {'none', 'concurrent', 'auto'}.") use_X = (X is not None) and (x_mode in ("concurrent", "auto")) - if use_X: - if not isinstance(X, pd.DataFrame): - raise TypeError("X must be a pandas DataFrame when provided.") - if not X.index.is_monotonic_increasing: - X = X.sort_index() - if not y.index.is_monotonic_increasing: - y = y.sort_index() values = y.to_numpy() n = len(values) @@ -204,43 +452,28 @@ def _build_supervised_table( targets = [] t_index = [] - x_cols = [] - if use_X: - x_cols = list(X.columns) - for t in range(window_length - 1, max_anchor + 1): - # y lags vector in the same order as features will be fed: [y_t, y_{t-1}, ...] + # lags y[t], y[t-1], ..., y[t-window_length+1] (newest first) lag_block = values[t : t - window_length : -1] if lag_block.shape[0] != window_length: - # safety for very early slices (rarely hit) lag_block = np.asarray( [values[t - i] for i in range(window_length)], dtype=float ) - y_feats = lag_block.astype(float).copy() - # target scalar at t + h + row = {f"y_lag_{i+1}": lag_block[i] for i in range(window_length)} target_time = y.index[t + steps_ahead] - y_target = float(values[t + steps_ahead]) - - # apply per-row normalization if requested - if normalization_strategy is not None: - tr, _inv = normalization_strategy(y_feats.copy()) - y_feats = np.asarray(tr(y_feats), dtype=float) - y_target = float(np.asarray(tr(np.array([y_target], dtype=float)))[0]) + y_target = values[t + steps_ahead] - # build row dict - row = {f"y_lag_{i+1}": y_feats[i] for i in range(window_length)} - - # append X (concurrent at target_time) without normalization if use_X: if target_time not in X.index: - for c in x_cols: + # Feature placeholder; user should ensure X completeness + for c in X.columns: row[f"X_{c}"] = np.nan else: xrow = X.loc[target_time] if isinstance(xrow, pd.DataFrame): xrow = xrow.iloc[0] - for c in x_cols: + for c in X.columns: row[f"X_{c}"] = xrow[c] rows.append(row) @@ -252,167 +485,223 @@ def _build_supervised_table( return Xt, yt -def _select_future_rows(X_future: pd.DataFrame, idx: pd.Index) -> pd.DataFrame: - """Select rows of X_future at index `idx`, raising error if any missing.""" - idx = pd.Index(idx) - missing = idx.difference(X_future.index) - if len(missing) > 0: - sample = list(missing[:3]) - raise ValueError( - "Missing required rows in X for forecast timestamps. " - f"Examples: {sample} (total missing: {len(missing)})." +def _build_supervised_table_global( + y: pd.Series, + X: Optional[pd.DataFrame], + window_length: int, + steps_ahead: int, + x_mode: str, +) -> Tuple[pd.DataFrame, pd.Series]: + """Supervised table across all ids for one horizon, stacked with MultiIndex index.""" + nlvls = y.index.nlevels + id_lvls = list(range(nlvls - 1)) + id_names = list(y.index.names[:-1]) + time_name = y.index.names[-1] + + Xt_list = [] + yt_list = [] + idx_list = [] + + # iterate ids + for ids, y_flat in _iter_series_groups(y): + X_flat = None + if X is not None: + X_flat = _flatten_multiindex_to_time(X, ids) + if not X_flat.index.is_monotonic_increasing: + X_flat = X_flat.sort_index() + if not y_flat.index.is_monotonic_increasing: + y_flat = y_flat.sort_index() + + Xt_g, yt_g = _build_supervised_table_single( + y=y_flat, + X=X_flat, + window_length=window_length, + steps_ahead=steps_ahead, + x_mode=x_mode, ) - return X_future.loc[idx] + # attach ids to index -> MultiIndex (ids..., time) + if len(ids) == 1: + new_index = pd.MultiIndex.from_arrays( + [[ids[0]] * len(Xt_g), Xt_g.index], + names=id_names + [time_name], + ) + else: + arrays = [[ids[j]] * len(Xt_g) for j in range(len(ids))] + arrays.append(list(Xt_g.index)) + new_index = pd.MultiIndex.from_arrays(arrays, names=id_names + [time_name]) -def _fh_to_absolute_index( - fh_like: Union[ForecastingHorizon, Sequence, pd.Index], - cutoff, - y_index: pd.Index, - steps: Optional[Sequence[int]] = None, - H: Optional[int] = None, -) -> pd.Index: - """ - Robustly coerce a forecasting horizon (absolute or relative) to a pandas Index. + Xt_g.index = new_index + yt_g.index = new_index - Tries multiple sktime FH APIs across versions, then falls back to constructing - a "future index like y_index". - """ - # If it's already a pandas Index, return it - if isinstance(fh_like, pd.Index): - return fh_like + Xt_list.append(Xt_g) + yt_list.append(yt_g) - # If it's not an FH, try to coerce directly - if not isinstance(fh_like, ForecastingHorizon): - try: - return pd.Index(fh_like) - except Exception: - pass # fall through to robust fallback + Xt_all = pd.concat(Xt_list, axis=0) + yt_all = pd.concat(yt_list, axis=0) - # From here treat as ForecastingHorizon - fh_obj = ( - fh_like - if isinstance(fh_like, ForecastingHorizon) - else ForecastingHorizon(fh_like) - ) + return Xt_all.sort_index(), yt_all.sort_index() - # 1) Preferred: to_absolute_index(cutoff) - m = getattr(fh_obj, "to_absolute_index", None) - if callable(m): - try: - return m(cutoff) - except Exception: - pass - # 2) Some versions: to_pandas_index() if already absolute - m = getattr(fh_obj, "to_pandas_index", None) - if callable(m): - try: - idx = m() - if isinstance(idx, pd.Index): - return idx - except Exception: - pass +def _normalize_supervised_rowwise( + Xt: pd.DataFrame, + yt: pd.Series, + L: int, + normalization_strategy: Optional[Callable[[np.ndarray], Tuple[Callable, Callable]]], +) -> Tuple[pd.DataFrame, pd.Series]: + """Apply per-row normalization to (y-lags, target).""" + if normalization_strategy is None: + return Xt, yt + + Xt_out = Xt.copy() + yt_out = yt.astype(float, copy=True) + lag_cols = [f"y_lag_{i+1}" for i in range(L)] + lag_idx = [Xt_out.columns.get_loc(col) for col in lag_cols] + + for i in range(len(Xt_out)): + lags = Xt_out.iloc[i, lag_idx].to_numpy(dtype=float) + target_value = yt_out.iloc[i] + transform, _ = normalization_strategy(lags) # per-window + lags_n, tgt_n = transform(lags, target_value) + Xt_out.iloc[i, lag_idx] = lags_n + if tgt_n is not None: + yt_out.iloc[i] = float(tgt_n) + + return Xt_out, yt_out + + +def _make_group_future_multiindex( + ids: Tuple, future_time_index: pd.Index, id_names: List[str], time_name: str +) -> pd.MultiIndex: + """Build a MultiIndex combining ids (tuple) and per-group future time index.""" + arrays = [[ids[j]] * len(future_time_index) for j in range(len(ids))] + arrays.append(list(future_time_index)) + return pd.MultiIndex.from_arrays(arrays, names=id_names + [time_name]) + + +def _steps_and_full_future_for_group( + train_time_index: pd.Index, + req_times: Optional[pd.Index] = None, + rel_steps: Optional[np.ndarray] = None, +) -> Tuple[pd.Index, Optional[np.ndarray]]: + """Return full future index for the group, and (if req_times) positions for them.""" + if req_times is not None: + last_t = train_time_index[-1] + max_t = pd.Index(req_times).max() + + if isinstance(train_time_index, pd.DatetimeIndex): + raw_freq = train_time_index.freq or _infer_freq_from_index(train_time_index) + offset = None + if raw_freq is not None: + try: + offset = to_offset(raw_freq) + except (TypeError, ValueError): + offset = None + if offset is None: + inferred = pd.infer_freq(train_time_index) + try: + offset = to_offset(inferred) + except (TypeError, ValueError): + offset = None + if offset is None: + if len(train_time_index) >= 2: + step = train_time_index[-1] - train_time_index[-2] + try: + offset = to_offset(step) + except (TypeError, ValueError): + offset = None + if offset is None: + return pd.Index([]), np.array([], dtype=int) + rng = pd.date_range( + start=last_t + offset, + end=max_t, + freq=offset, + tz=train_time_index.tz, + ) + H = len(rng) + full_future = pd.date_range( + start=last_t + offset, + periods=H if H > 0 else 0, + freq=offset, + tz=train_time_index.tz, + ) + elif isinstance(train_time_index, pd.PeriodIndex): + freq = train_time_index.freq + rng = pd.period_range(start=last_t + 1, end=max_t, freq=freq) + H = len(rng) + full_future = pd.period_range( + start=last_t + 1, periods=H if H > 0 else 0, freq=freq + ) + elif is_integer_dtype(train_time_index.dtype): + H = int(max_t - last_t) + full_future = pd.RangeIndex(last_t + 1, last_t + 1 + max(H, 0)) + else: + req_times_sorted = pd.Index(req_times).sort_values() + H = len(req_times_sorted) + full_future = pd.RangeIndex(1, H + 1) - # 3) Try going to absolute first, then 1) and 2) - try: - abs_fh = fh_obj.to_absolute(cutoff) - m = getattr(abs_fh, "to_absolute_index", None) - if callable(m): - try: - return m(cutoff) - except Exception: - pass - m = getattr(abs_fh, "to_pandas_index", None) - if callable(m): - try: - idx = m() - if isinstance(idx, pd.Index): - return idx - except Exception: - pass - # If abs_fh itself is index-like - if not isinstance(abs_fh, ForecastingHorizon): - try: - return pd.Index(abs_fh) - except Exception: - pass - except Exception: - pass + if H == 0: + return pd.Index([]), np.array([], dtype=int) - # 4) Last resort: synthesize using y_index's cadence - if steps is not None: - steps = np.asarray(steps, dtype=int).reshape(-1) - H_ = int(np.max(steps)) - else: - H_ = int(H if H is not None else len(fh_obj)) - steps = np.arange(1, H_ + 1, dtype=int) + if isinstance(full_future, pd.RangeIndex) and not np.issubdtype( + train_time_index.dtype, np.integer + ): + req_sorted = pd.Index(req_times).sort_values() + pos_map = {req_sorted[i]: i + 1 for i in range(len(req_sorted))} + steps = np.asarray([pos_map[t] for t in req_times], dtype=int) + else: + pos = pd.Index(full_future).get_indexer(pd.Index(req_times)) + if np.any(pos < 0): + bad = list(pd.Index(req_times)[pos < 0][:3]) + raise ValueError( + "Requested times are not aligned with training frequency for a group. " + f"Examples: {bad}" + ) + steps = pos.astype(int) + 1 # 1-based + + return full_future, steps + + # relative steps path + if rel_steps is None or len(rel_steps) == 0: + raise ValueError("Either req_times or rel_steps must be provided.") + H = int(np.max(rel_steps)) + full_future, _ = _future_index_like(train_time_index, H) + return pd.Index(full_future), None - full_future, _ = _future_index_like(y_index, H_) - return pd.Index([full_future[h - 1] for h in steps]) + +def _union_indices(indices: List[pd.Index]) -> pd.Index: + """Safe union for both Index and MultiIndex without relying on union_many.""" + if not indices: + return pd.Index([]) + u = indices[0] + for ix in indices[1:]: + u = u.union(ix) + return u # --------------------------------------------------------------------------- -# The forecaster +# The global forecaster # --------------------------------------------------------------------------- class ReductionForecaster(BaseForecaster): - """Hybrid reduction forecaster: K-step direct + recursive continuation. - - Trains **steps_ahead = K** separate direct models for horizons 1..K using a - lag window from ``y`` (and optional *concurrent* exogenous ``X`` at each - target timestamp). For a requested forecast horizon H: - - - For steps 1..min(K, H): use the corresponding direct model h to predict y[h] - from the *same observed* lag window (no predicted values fed back here). - - If H > K: continue with **recursive** one-step predictions, starting from the - observed lag window rolled forward by the K *direct* predictions, and use the - trained 1-step model repeatedly. - - Normalization strategy - ---------------------- - The optional ``normalization_strategy`` is a callable that receives the - **current y-lag window** (1D ndarray in feature order: [y_t, y_{t-1}, ...]) and - returns a pair of functions ``(transform, inverse_transform)``, both operating on - 1D ndarrays. In training, for each supervised row, we fit this per-window - normalizer on the y-lag vector, transform the lags **and** the scalar target, - train models in the normalized space, and in prediction we inverse-transform each - predicted scalar immediately back to the original y-scale. - - Parameters - ---------- - estimator : sklearn-style regressor - Any object with `fit(X, y)` and `predict(X)` methods. - window_length : int, default=10 - Number of past observations to use as lags. - steps_ahead : int, default=1 - Number of direct heads (K). K=1 recovers pure recursive reduction. - x_mode : {"auto","none","concurrent"}, default="auto" - - "none": ignore X even if provided - - "concurrent": use X at the **target** timestamps in `fit`, and the - future timestamps in `predict` - - "auto": behaves like "concurrent" if X is provided else "none" - impute_missing : {"ffill","bfill",None}, default="bfill" - Optional imputation applied to `y` **before** windowing. - If None, NaNs are left as-is (your estimator must handle them). - normalization_strategy : callable or None, default=None - Function ``f(window_1d) -> (transform, inverse_transform)``. If None, no - normalization is applied. - - Notes - ----- - - Univariate series only (single variable). - - If X is used in fit, you must pass future X at *all* required forecast - timestamps to `predict`. + """Global hybrid reduction forecaster: K-step direct + recursive continuation. + + Trains **steps_ahead = K** separate direct models for horizons 1..K on pooled + (possibly hierarchical) data, using a lag window from ``y`` (and optional + *concurrent* exogenous ``X`` at each target timestamp). For each series id, + requested predictions beyond K steps are produced recursively using the + 1-step model. + + This class works with **either** a single time series (simple time index) **or** + MultiIndex/Hierarchical data (id levels + time). """ - # -------------------- sktime estimator tags -------------------- _tags = { - # inner mtypes - "y_inner_mtype": "pd.Series", - "X_inner_mtype": "pd.DataFrame", - # univariate only + # accept single series AND hierarchical / multiindex series + "y_inner_mtype": ["pd.Series", "pd-multiindex", "pd_multiindex_hier"], + "X_inner_mtype": ["pd.DataFrame", "pd-multiindex", "pd_multiindex_hier"], + # univariate target "scitype:y": "univariate", # exogenous supported "capability:exogenous": True, @@ -422,66 +711,140 @@ class ReductionForecaster(BaseForecaster): "X-y-must-have-same-index": True, # index type unrestricted "enforce_index_type": None, - # we don't guarantee missing handling in general (y can be imputed) + # missing values: we don't guarantee generic handling (y can be imputed) "capability:missing_values": False, - # only strictly out-of-sample fh supported (positive relative steps) - "capability:insample": False, - # no probabilistic output + # supports both out-of-sample and in-sample forecasts + "capability:insample": True, + # no probabilistic output in this implementation "capability:pred_int": False, - # soft dependency on sklearn (sktime tag uses the import name) + # soft dependency on scikit-learn "python_dependencies": "scikit-learn", } - # -------------------- constructor signature -------------------- def __init__( self, estimator: RegressorMixin, window_length: int = 10, steps_ahead: int = 1, + normalization_strategy: Optional[ + Union[str, Callable[[np.ndarray], Tuple[Callable, Callable]]] + ] = None, x_mode: str = "auto", impute_missing: Optional[str] = "bfill", - normalization_strategy: NormStrategy = None, ): - # components / hyper-params + # hyper-params self.estimator = estimator self.window_length = int(window_length) self.steps_ahead = int(steps_ahead) + self.normalization_strategy = normalization_strategy self.x_mode = x_mode self.impute_missing = impute_missing - self.normalization_strategy = normalization_strategy super().__init__() if self.steps_ahead < 1: raise ValueError("steps_ahead must be a positive integer.") - if (self.normalization_strategy is not None) and ( - not callable(self.normalization_strategy) - ): - raise TypeError("normalization_strategy must be a callable or None.") - # learned attributes (set in _fit) + # learned attributes self._dir_estimators_: Optional[List[RegressorMixin]] = None - self._estimator_: Optional[RegressorMixin] = None # alias: 1-step model + self._estimator_: Optional[RegressorMixin] = None # 1-step model shortcut self._x_used_: bool = False self._x_columns_: Optional[List[str]] = None - self._last_window_: Optional[np.ndarray] = None - self._y_train_index_: Optional[pd.Index] = None - self._y_name_: Optional[str] = None + + # per-group rolling state + self._last_windows_: Optional[Dict[Tuple, np.ndarray]] = ( + None # ids -> window (old..new) + ) + self._train_time_index_: Optional[Dict[Tuple, pd.Index]] = ( + None # ids -> time index + ) + self._ids_: Optional[List[Tuple]] = None # list of ids tuples in fit order + + # index naming + self._id_names_: Optional[List[str]] = None + self._time_name_: Optional[str] = None + self._was_single_series_: bool = False + self._single_id_value_: str = "__singleton__" + self._single_id_name_: str = "id" + + # for update/refit bookkeeping self._y_train_: Optional[pd.Series] = None self._X_train_: Optional[pd.DataFrame] = None + self._norm_strategy_: Optional[ + Callable[[np.ndarray], Tuple[Callable, Callable]] + ] = None + self._y_name_: Optional[str] = None + self._y_is_dataframe_: bool = False + self._y_column_name_: Optional[str] = None - # -------------------- fit logic -------------------- + # -------------------- fit -------------------- def _fit( self, y: pd.Series, X: Optional[pd.DataFrame], fh: Optional[ForecastingHorizon] ): - """Fit forecaster to training data (private core, called by BaseForecaster).""" + """Fit the global forecaster to (possibly hierarchical or single) training data.""" _check_regressor(self.estimator) - y = _ensure_series(y).copy() + self._y_is_dataframe_ = isinstance(y, pd.DataFrame) + if isinstance(y, pd.DataFrame): + if y.shape[1] != 1: + raise ValueError( + "ReductionForecaster supports univariate targets only." + ) + col_name = y.columns[0] + y = y.iloc[:, 0].copy() + y.name = col_name + self._y_column_name_ = col_name + else: + self._y_column_name_ = y.name + + # detect single series and coerce to MultiIndex internally + if isinstance(y.index, pd.MultiIndex) and y.index.nlevels >= 2: + self._was_single_series_ = False + y_mi = y.copy() + if X is not None: + if isinstance(X.index, pd.MultiIndex): + X_mi = X.copy() + else: + raise TypeError( + "X must have a MultiIndex to match y's MultiIndex in fit." + ) + else: + X_mi = None + else: + # single series -> wrap to MultiIndex with one id level + self._was_single_series_ = True + time_name = y.index.name if y.index.name is not None else "time" + id_name = self._single_id_name_ + id_val = self._single_id_value_ + y_mi = y.copy() + y_mi.index = pd.MultiIndex.from_arrays( + [[id_val] * len(y_mi), y_mi.index], names=[id_name, time_name] + ) + if X is not None: + if isinstance(X.index, pd.MultiIndex): + raise TypeError( + "For single-series fit, X should have a simple time index." + ) + X_mi = X.copy() + X_mi.index = pd.MultiIndex.from_arrays( + [[id_val] * len(X_mi), X_mi.index], names=[id_name, time_name] + ) + else: + X_mi = None + + y = y_mi + X = X_mi + + # store names + self._id_names_ = list(y.index.names[:-1]) + self._time_name_ = y.index.names[-1] + self._y_name_ = y.name # basic imputation on y - if self.impute_missing in ("ffill", "bfill"): - y = y.fillna(method=self.impute_missing) + if self.impute_missing == "ffill": + y = y.ffill() + elif self.impute_missing == "bfill": + y = y.bfill() elif self.impute_missing is not None: raise ValueError("impute_missing must be 'ffill', 'bfill', or None.") @@ -490,275 +853,656 @@ def _fit( if x_mode == "auto": x_mode = "concurrent" if X is not None else "none" - # fit K direct heads (1..steps_ahead) + # resolve normalization strategy (allow factory or strategy) + self._norm_strategy_ = _resolve_normalization_strategy( + self.normalization_strategy + ) + + # fit K direct heads using pooled data dir_estimators: List[RegressorMixin] = [] for h in range(1, self.steps_ahead + 1): - Xt_h, yt_h = _build_supervised_table( + Xt_h_all, yt_h_all = _build_supervised_table_global( y=y, X=X, window_length=self.window_length, steps_ahead=h, x_mode=x_mode, - normalization_strategy=self.normalization_strategy, ) + # remember X columns (consistency check at predict) + if self._x_columns_ is None and X is not None and X.shape[1] > 0: + self._x_columns_ = list(X.columns) + + # row-wise normalization on lags & target + Xt_h_all_n, yt_h_all_n = _normalize_supervised_rowwise( + Xt_h_all, yt_h_all, self.window_length, self._norm_strategy_ + ) + est_h = clone(self.estimator) - est_h.fit(Xt_h.values, yt_h.values) + est_h.fit(Xt_h_all_n.values, yt_h_all_n.values) dir_estimators.append(est_h) # learned state self._dir_estimators_ = dir_estimators - self._estimator_ = dir_estimators[0] # 1-step model + self._estimator_ = dir_estimators[0] self._x_used_ = (x_mode == "concurrent") and (X is not None) - self._x_columns_ = list(X.columns) if (X is not None) else None - self._y_train_index_ = y.index - self._y_name_ = y.name - # bootstrap last window for recursion (stored oldest->newest, as y.iloc preserves) - last = y.iloc[-self.window_length :].to_numpy() - if len(last) != self.window_length: - raise ValueError( - "Not enough observations to form last window. " - f"Need window_length={self.window_length}, got {len(y)}." - ) - self._last_window_ = last.astype(float).reshape(-1) + # store per-group last window and time indices + last_windows = {} + time_idx_map = {} + ids_list = [] + for ids, y_flat in _iter_series_groups(y): + ids_list.append(ids) + if not y_flat.index.is_monotonic_increasing: + y_flat = y_flat.sort_index() + time_idx_map[ids] = y_flat.index + last = y_flat.iloc[-self.window_length :].to_numpy() + if len(last) != self.window_length: + raise ValueError( + f"Group {ids}: not enough observations for last window. " + f"Need window_length={self.window_length}, got {len(y_flat)}." + ) + last_windows[ids] = last.astype(float).reshape(-1) - # store training series for optional updates + self._last_windows_ = last_windows + self._train_time_index_ = time_idx_map + self._ids_ = ids_list + + # store training for potential refit in update self._y_train_ = y.copy() self._X_train_ = X.copy() if X is not None else None return self - # -------------------- predict logic -------------------- + # -------------------- predict -------------------- def _predict( - self, fh: ForecastingHorizon, X: Optional[pd.DataFrame] = None + self, + fh: Union[ForecastingHorizon, Sequence, pd.Index, pd.MultiIndex], + X: Optional[pd.DataFrame] = None, ) -> pd.Series: - """Forecast time series at future horizon (private core, called by BaseForecaster).""" + """Forecast pooled multi-series or a single series at future horizon.""" if ( self._estimator_ is None - or self._last_window_ is None + or self._last_windows_ is None or self._dir_estimators_ is None + or self._train_time_index_ is None + or self._ids_ is None ): raise RuntimeError("Call fit(...) before predict(...).") - # relative steps (strictly positive due to tag capability:insample=False) - rel = fh.to_relative(self.cutoff) - rel_steps = np.asarray(rel, dtype=int).reshape(-1) - pos_steps = _as_positive_int_fh(rel_steps) - H = int(pos_steps.max()) - - # build absolute indexes robustly (supports various sktime versions) - all_rel = ForecastingHorizon(np.arange(1, H + 1, dtype=int), is_relative=True) - abs_all_like = all_rel.to_absolute(self.cutoff) - abs_all_idx = _fh_to_absolute_index( - abs_all_like, cutoff=self.cutoff, y_index=self._y_train_index_, H=H - ) + mode_abs_multi = isinstance(fh, pd.MultiIndex) + mode_abs_single = isinstance(fh, pd.Index) and not isinstance(fh, pd.MultiIndex) + mode_rel = False + req_steps_all: Optional[np.ndarray] = None - abs_req_like = fh.to_absolute(self.cutoff) - abs_req_idx = _fh_to_absolute_index( - abs_req_like, - cutoff=self.cutoff, - y_index=self._y_train_index_, - steps=rel_steps, - H=H, - ) + if not (mode_abs_multi or mode_abs_single): + if isinstance(fh, ForecastingHorizon): + rel = fh.to_relative(self.cutoff) + req_steps_all = _as_int_fh(np.asarray(rel, dtype=int)) + else: + req_steps_all = _as_int_fh(np.asarray(fh)) + mode_rel = True + + if mode_abs_single and not self._was_single_series_: + raise TypeError( + "Absolute fh as a simple Index is only valid when the model was fit " + "on a single series. For multi-series, pass a MultiIndex fh." + ) - # if X was used in fit, we need concurrent X for *all* 1..H steps if self._x_used_: if X is None: raise ValueError( "This model was fit with exogenous variables. " "Provide X with rows for all required forecast timestamps." ) - if not isinstance(X, pd.DataFrame): - raise TypeError( - "X must be a pandas DataFrame when provided to predict." - ) - if not X.index.is_monotonic_increasing: - X = X.sort_index() - - X_all = _select_future_rows(X, abs_all_idx) if self._x_columns_ is not None: - missing_cols = [c for c in self._x_columns_ if c not in X_all.columns] - if missing_cols: + missing = [c for c in self._x_columns_ if c not in X.columns] + if missing: raise ValueError( - f"X is missing columns seen in training: {missing_cols}" + f"X is missing columns seen in training: {missing}" + ) + if self._was_single_series_: + if isinstance(X.index, pd.MultiIndex): + raise TypeError( + "For single-series prediction, X should have a simple time index." + ) + else: + if not isinstance(X.index, pd.MultiIndex): + raise TypeError( + "For multi-series prediction, X must have a MultiIndex index." + ) + if self._x_columns_ is not None: + X = X[self._x_columns_] + + out_series: List[pd.Series] = [] + + for ids in self._ids_: + time_idx_train = self._train_time_index_[ids] + y_group_train, X_group_train = self._prepare_group_training_views(ids) + + if mode_abs_multi: + try: + req_times_all = pd.Index( + fh.xs(ids, level=list(range(fh.nlevels - 1))) + ) + except Exception: + req_times_all = pd.Index([]) + if len(req_times_all) == 0: + continue + + mask_in = req_times_all.isin(time_idx_train) + ins_times_req = req_times_all[mask_in] + fut_times_req = req_times_all[~mask_in] + + series_parts: List[pd.Series] = [] + + if len(ins_times_req) > 0: + ins_vals = self._predict_insample_sequence( + ids, y_group_train, X_group_train, ins_times_req + ) + series_parts.append( + self._build_output_series(ids, ins_times_req, ins_vals) + ) + + if len(fut_times_req) > 0: + full_future, steps_for_req = _steps_and_full_future_for_group( + time_idx_train, req_times=fut_times_req + ) + if len(full_future) > 0: + if steps_for_req is None: + raise RuntimeError( + "Internal error: expected absolute-step mapping." + ) + preds_full = self._forecast_future_for_group( + ids, full_future, X + ) + fut_vals = preds_full[steps_for_req - 1] + series_parts.append( + self._build_output_series(ids, fut_times_req, fut_vals) + ) + + if not series_parts: + continue + + combined = pd.concat(series_parts) + target_index = self._build_output_index(ids, req_times_all) + combined = combined.reindex(target_index) + out_series.append(combined) + + elif mode_abs_single: + req_times_all = pd.Index(fh) + if len(req_times_all) == 0: + continue + + mask_in = req_times_all.isin(time_idx_train) + ins_times_req = req_times_all[mask_in] + fut_times_req = req_times_all[~mask_in] + + series_parts = [] + + if len(ins_times_req) > 0: + ins_vals = self._predict_insample_sequence( + ids, y_group_train, X_group_train, ins_times_req + ) + series_parts.append( + self._build_output_series(ids, ins_times_req, ins_vals) + ) + + if len(fut_times_req) > 0: + full_future, steps_for_req = _steps_and_full_future_for_group( + time_idx_train, req_times=fut_times_req + ) + if len(full_future) > 0: + if steps_for_req is None: + raise RuntimeError( + "Internal error: expected absolute-step mapping." + ) + preds_full = self._forecast_future_for_group( + ids, full_future, X + ) + fut_vals = preds_full[steps_for_req - 1] + series_parts.append( + self._build_output_series(ids, fut_times_req, fut_vals) + ) + + if not series_parts: + continue + + combined = pd.concat(series_parts) + target_index = self._build_output_index(ids, req_times_all) + combined = combined.reindex(target_index) + out_series.append(combined) + + else: + assert req_steps_all is not None + if req_steps_all.size == 0: + continue + + pos_steps = sorted({int(s) for s in req_steps_all if s > 0}) + ins_steps = [int(s) for s in req_steps_all if s <= 0] + + ins_step_to_time: Dict[int, Any] = {} + insample_times_order: List[Any] = [] + n_train = len(time_idx_train) + for step in ins_steps: + pos = n_train - 1 + step + if pos < 0: + raise ValueError( + "Requested in-sample step extends before available data." + ) + target_time = time_idx_train[pos] + ins_step_to_time[step] = target_time + if target_time not in insample_times_order: + insample_times_order.append(target_time) + + insample_pred_map: Dict[Any, float] = {} + if insample_times_order: + ins_vals_unique = self._predict_insample_sequence( + ids, y_group_train, X_group_train, insample_times_order ) - X_all = X_all[self._x_columns_] - X_block = X_all.to_numpy() + insample_pred_map = dict(zip(insample_times_order, ins_vals_unique)) + + full_future = pd.Index([]) + preds_full = np.array([], dtype=float) + if pos_steps: + full_future, _ = _steps_and_full_future_for_group( + time_idx_train, rel_steps=np.asarray(pos_steps, dtype=int) + ) + if len(full_future) > 0: + preds_full = self._forecast_future_for_group( + ids, full_future, X + ) + + out_times: List[Any] = [] + out_values: List[float] = [] + for step in req_steps_all: + if step > 0: + if step - 1 >= len(preds_full): + raise ValueError( + "Requested relative step exceeds computed horizon." + ) + target_time = full_future[step - 1] + value = preds_full[step - 1] + else: + target_time = ins_step_to_time[step] + value = insample_pred_map.get(target_time, np.nan) + out_times.append(target_time) + out_values.append(float(value)) + + series_rel = self._build_output_series(ids, out_times, out_values) + out_series.append(series_rel) + + if len(out_series) == 0: + return pd.Series([], dtype=float, name=self._y_name_) + + y_pred = pd.concat(out_series) + + if mode_abs_multi: + y_pred = y_pred.reindex(fh) + elif mode_abs_single: + y_pred = y_pred.reindex(pd.Index(fh)) else: - X_block = None + y_pred = y_pred.sort_index() - preds = np.zeros(H, dtype=float) + if self._y_is_dataframe_: + col_name = self._y_column_name_ or self._y_name_ or "y" + return y_pred.to_frame(name=col_name) + + return y_pred + + def _prepare_group_training_views( + self, ids: Tuple + ) -> Tuple[pd.Series, Optional[pd.DataFrame]]: + """Return training y/X slices for the given group ids.""" + if self._y_train_ is None: + raise RuntimeError( + "Training data is not available for in-sample prediction." + ) + + y_group = _flatten_multiindex_to_time(self._y_train_, ids) + if not y_group.index.is_monotonic_increasing: + y_group = y_group.sort_index() + + X_group = None + if self._x_used_ and self._X_train_ is not None: + X_group = _flatten_multiindex_to_time(self._X_train_, ids) + if not X_group.index.is_monotonic_increasing: + X_group = X_group.sort_index() + + return y_group, X_group + + def _predict_insample_single( + self, + ids: Tuple, + y_group: pd.Series, + X_group: Optional[pd.DataFrame], + target_time, + ) -> float: + """Predict a single in-sample timestamp for a specific group.""" + positions = y_group.index.get_indexer([target_time]) + if positions.size == 0 or positions[0] == -1: + raise ValueError( + f"Timestamp {target_time} not found in training data for group {ids}." + ) + pos = int(positions[0]) + + if pos < self.window_length: + return float("nan") + + history = y_group.iloc[pos - self.window_length : pos] + if len(history) != self.window_length: + return float("nan") + + lag_feats = history.to_numpy(dtype=float)[::-1] + + if self._norm_strategy_ is not None: + transform, inv = self._norm_strategy_(lag_feats) + lag_feats_n, _ = transform(lag_feats, None) + else: + lag_feats_n = lag_feats + inv = lambda v: float(v) - # ----- Direct part (1..min(K, H)) using *observed* window only ----- - K = min(self.steps_ahead, H) - last_obs = self._last_window_.copy() # oldest -> newest - for i in range(1, K + 1): - # features in model order: most recent first - y_feats = last_obs[::-1] # y_lag_1 := most recent true observation - - # per-step normalization (fit on current window) - if self.normalization_strategy is not None: - tr, inv = self.normalization_strategy(y_feats.copy()) - y_feats_n = np.asarray(tr(y_feats), dtype=float) + if self._x_used_: + if X_group is None: + raise ValueError( + "Stored exogenous features are required for in-sample prediction." + ) + if target_time not in X_group.index: + raise ValueError( + "Missing exogenous data for in-sample timestamp " + f"{target_time} in group {ids}." + ) + xrow = X_group.loc[target_time] + if isinstance(xrow, pd.DataFrame): + xrow = xrow.iloc[0] + if self._x_columns_ is not None: + x_values = xrow[self._x_columns_].to_numpy(dtype=float) + else: + x_values = xrow.to_numpy(dtype=float) + row = np.concatenate([lag_feats_n, x_values]) + else: + row = lag_feats_n + + yhat_n = float( + np.asarray(self._estimator_.predict(row.reshape(1, -1))).ravel()[0] + ) + return float(inv(yhat_n)) + + def _predict_insample_sequence( + self, + ids: Tuple, + y_group: pd.Series, + X_group: Optional[pd.DataFrame], + target_times: Sequence, + ) -> np.ndarray: + """Predict multiple in-sample timestamps for a specific group.""" + times = list(target_times) + preds = [self._predict_insample_single(ids, y_group, X_group, t) for t in times] + return np.asarray(preds, dtype=float) + + def _build_output_index(self, ids: Tuple, times: Sequence) -> pd.Index: + """Construct the output index for a group's predictions.""" + times_index = pd.Index(times) + if self._was_single_series_: + return pd.Index(times_index, name=self._time_name_) + return _make_group_future_multiindex( + ids, times_index, self._id_names_, self._time_name_ + ) + + def _build_output_series( + self, ids: Tuple, times: Sequence, values: Sequence[float] + ) -> pd.Series: + """Helper to build a Series with the correct index for a group's output.""" + idx = self._build_output_index(ids, times) + values_arr = np.asarray(values, dtype=float) + if len(idx) != len(values_arr): + raise ValueError("Mismatched lengths for forecast times and values.") + return pd.Series(values_arr, index=idx, name=self._y_name_) + + def _forecast_future_for_group( + self, + ids: Tuple, + full_future: pd.Index, + X: Optional[pd.DataFrame], + ) -> np.ndarray: + """Produce out-of-sample predictions for a group over a horizon.""" + H = len(full_future) + if H == 0: + return np.array([], dtype=float) + + X_block = None + if self._x_used_: + if X is None: + raise ValueError( + "Exogenous data X must be provided for out-of-sample forecasts." + ) + if self._was_single_series_: + X_needed = _select_future_rows(X, full_future) + X_block = X_needed.to_numpy() + else: + group_future_index = _make_group_future_multiindex( + ids, full_future, self._id_names_, self._time_name_ + ) + X_needed = _select_future_rows(X, group_future_index) + X_block = X_needed.to_numpy() + + preds = np.zeros(H, dtype=float) + K = self.steps_ahead + + last_obs = self._last_windows_[ids].copy() + for i in range(1, min(K, H) + 1): + y_feats = last_obs[::-1] + if self._norm_strategy_ is not None: + transform, inv = self._norm_strategy_(y_feats) + y_feats_n, _ = transform(y_feats, None) else: - # identity mapping y_feats_n = y_feats - inv = lambda a: a # noqa: E731 + inv = lambda v: float(v) if X_block is not None: row = np.concatenate([y_feats_n, X_block[i - 1]]) else: row = y_feats_n - yhat_norm = float( + yhat_n = float( np.asarray( self._dir_estimators_[i - 1].predict(row.reshape(1, -1)) ).ravel()[0] ) - # map back to original scale - yhat = float(np.asarray(inv(np.array([yhat_norm], dtype=float)))[0]) - preds[i - 1] = yhat + preds[i - 1] = inv(yhat_n) - # ----- Prepare rolling state after K direct steps ----- - last_roll = self._last_window_.copy() - for i in range(1, K + 1): + last_roll = self._last_windows_[ids].copy() + for i in range(1, min(K, H) + 1): last_roll = np.roll(last_roll, -1) last_roll[-1] = preds[i - 1] - # ----- Recursive continuation (K+1..H) using 1-step model ----- for i in range(K + 1, H + 1): y_feats = last_roll[::-1] - - if self.normalization_strategy is not None: - tr, inv = self.normalization_strategy(y_feats.copy()) - y_feats_n = np.asarray(tr(y_feats), dtype=float) + if self._norm_strategy_ is not None: + transform, inv = self._norm_strategy_(y_feats) + y_feats_n, _ = transform(y_feats, None) else: y_feats_n = y_feats - inv = lambda a: a # noqa: E731 + inv = lambda v: float(v) if X_block is not None: row = np.concatenate([y_feats_n, X_block[i - 1]]) else: row = y_feats_n - yhat_norm = float( + yhat_n = float( np.asarray(self._estimator_.predict(row.reshape(1, -1))).ravel()[0] ) - yhat = float(np.asarray(inv(np.array([yhat_norm], dtype=float)))[0]) - + yhat = inv(yhat_n) preds[i - 1] = yhat + last_roll = np.roll(last_roll, -1) last_roll[-1] = yhat - # assemble Series for all 1..H steps, then subset to requested fh - y_all = pd.Series( - preds, - index=abs_all_idx, - name=self._y_name_ if self._y_name_ is not None else "y", - ) - y_req = y_all.reindex(abs_req_idx) - return y_req + return preds - # -------------------- optional: update logic -------------------- + # -------------------- update -------------------- def _update( self, y: pd.Series, X: Optional[pd.DataFrame] = None, update_params: bool = True ): - """Update forecaster with new data. If update_params=True, refit; else only roll window.""" + """Update rolling windows; refit on appended data if `update_params=True`.""" if ( self._estimator_ is None - or self._last_window_ is None + or self._last_windows_ is None or self._dir_estimators_ is None + or self._train_time_index_ is None + or self._ids_ is None ): raise RuntimeError("Call fit(...) before update(...).") - y = _ensure_series(y) - if len(y) == 0: + if y is None or len(y) == 0: return self - # roll last window with the new y values - new_vals = y.to_numpy().astype(float).reshape(-1) - if len(new_vals) >= self.window_length: - self._last_window_ = new_vals[-self.window_length :] - else: - rolled = np.roll(self._last_window_, -len(new_vals)) - rolled[-len(new_vals) :] = new_vals - self._last_window_ = rolled - - # refit if requested - if update_params: - # append to stored training data and refit from scratch (simple & robust) - y_full = pd.concat([self._y_train_, y]) - X_full = None - if self._X_train_ is not None: - if X is None: - raise ValueError( - "This model was originally fit with X; update with matching X." - ) - X_full = pd.concat([self._X_train_, X]).sort_index() - - _check_regressor(self.estimator) + if isinstance(y, pd.DataFrame): + if y.shape[1] != 1: + raise ValueError( + "ReductionForecaster update expects a single target column." + ) + col_name = y.columns[0] + y = y.iloc[:, 0].copy() + y.name = col_name + + # Coerce y (and X) to the internal MultiIndex shape if needed + if self._was_single_series_: + time_name = self._time_name_ or "time" + id_name = self._id_names_[0] if self._id_names_ else self._single_id_name_ + id_val = self._single_id_value_ + if not isinstance(y.index, pd.MultiIndex): + y = y.copy() + y.index = pd.MultiIndex.from_arrays( + [[id_val] * len(y), y.index], names=[id_name, time_name] + ) + if X is not None and not isinstance(X.index, pd.MultiIndex): + X = X.copy() + X.index = pd.MultiIndex.from_arrays( + [[id_val] * len(X), X.index], names=[id_name, time_name] + ) - # impute like in fit - if self.impute_missing in ("ffill", "bfill"): - y_imp = y_full.fillna(method=self.impute_missing) + # roll last windows for groups present in y + for ids, y_flat in _iter_series_groups(y): + new_vals = y_flat.to_numpy(dtype=float).reshape(-1) + if len(new_vals) == 0: + continue + if len(new_vals) >= self.window_length: + self._last_windows_[ids] = new_vals[-self.window_length :] else: - y_imp = y_full.copy() - - x_mode = self.x_mode - if x_mode == "auto": - x_mode = "concurrent" if X_full is not None else "none" - - # refit K heads - dir_estimators: List[RegressorMixin] = [] - for h in range(1, self.steps_ahead + 1): - Xt_h, yt_h = _build_supervised_table( - y=y_imp, - X=X_full, - window_length=self.window_length, - steps_ahead=h, - x_mode=x_mode, - normalization_strategy=self.normalization_strategy, + rolled = np.roll(self._last_windows_[ids], -len(new_vals)) + rolled[-len(new_vals) :] = new_vals + self._last_windows_[ids] = rolled + # update stored time index for the group + existing_index = self._train_time_index_[ids] + new_index = y_flat.index + if isinstance(existing_index, pd.DatetimeIndex): + combined = existing_index.append(pd.DatetimeIndex(new_index)) + elif isinstance(existing_index, pd.PeriodIndex): + combined = existing_index.append( + pd.PeriodIndex(new_index, freq=existing_index.freq) ) - est_h = clone(self.estimator) - est_h.fit(Xt_h.values, yt_h.values) - dir_estimators.append(est_h) - - # update learned state - self._dir_estimators_ = dir_estimators - self._estimator_ = dir_estimators[0] - self._x_used_ = (x_mode == "concurrent") and (X_full is not None) - self._x_columns_ = list(X_full.columns) if (X_full is not None) else None - self._y_train_index_ = y_full.index - self._y_name_ = y_full.name - self._y_train_ = y_full.copy() - self._X_train_ = X_full.copy() if X_full is not None else None - - # refresh last window from y_full - last = y_imp.iloc[-self.window_length :].to_numpy() - self._last_window_ = last.astype(float).reshape(-1) + else: + combined = existing_index.append(pd.Index(new_index)) + + if not combined.is_monotonic_increasing: + combined = combined.sort_values() + + self._train_time_index_[ids] = combined + + if not update_params: + return self + + # Refit from scratch on concatenated data (simple & robust) + y_full = pd.concat([self._y_train_, y]).sort_index() + X_full = None + if self._X_train_ is not None or X is not None: + if (self._X_train_ is not None) and (X is None): + raise ValueError( + "This model was originally fit with X; update requires matching X." + ) + X_full = pd.concat([self._X_train_, X]).sort_index() + + _check_regressor(self.estimator) + + # impute like in fit + if self.impute_missing == "ffill": + y_imp = y_full.ffill() + elif self.impute_missing == "bfill": + y_imp = y_full.bfill() + else: + y_imp = y_full.copy() + + x_mode = self.x_mode + if x_mode == "auto": + x_mode = "concurrent" if X_full is not None else "none" + + dir_estimators: List[RegressorMixin] = [] + for h in range(1, self.steps_ahead + 1): + Xt_h_all, yt_h_all = _build_supervised_table_global( + y=y_imp, + X=X_full, + window_length=self.window_length, + steps_ahead=h, + x_mode=x_mode, + ) + Xt_h_all_n, yt_h_all_n = _normalize_supervised_rowwise( + Xt_h_all, yt_h_all, self.window_length, self._norm_strategy_ + ) + est_h = clone(self.estimator) + est_h.fit(Xt_h_all_n.values, yt_h_all_n.values) + dir_estimators.append(est_h) + + # update learned state + self._dir_estimators_ = dir_estimators + self._estimator_ = dir_estimators[0] + self._x_used_ = (x_mode == "concurrent") and (X_full is not None) + self._x_columns_ = ( + list(X_full.columns) if (X_full is not None) else self._x_columns_ + ) + + # refresh per-group windows and time index maps from y_imp + last_windows = {} + time_idx_map = {} + ids_list = [] + for ids, y_flat in _iter_series_groups(y_imp): + ids_list.append(ids) + if not y_flat.index.is_monotonic_increasing: + y_flat = y_flat.sort_index() + time_idx_map[ids] = y_flat.index + last = y_flat.iloc[-self.window_length :].to_numpy() + last_windows[ids] = last.astype(float).reshape(-1) + self._last_windows_ = last_windows + self._train_time_index_ = time_idx_map + self._ids_ = ids_list + + # store full data for potential next update + self._y_train_ = y_full.copy() + self._X_train_ = X_full.copy() if X_full is not None else None return self - # -------------------- fitted params exposure (optional) -------------------- + # -------------------- fitted params -------------------- def _get_fitted_params(self): - """Return fitted parameters.""" + """Expose fitted parameters and learned state.""" return { "x_used": self._x_used_, "x_columns": self._x_columns_, - "last_window": ( - None if self._last_window_ is None else self._last_window_.copy() - ), - "one_step_estimator": self._estimator_, "direct_estimators": self._dir_estimators_, - "y_train_index": self._y_train_index_, + "one_step_estimator": self._estimator_, + "last_windows": { + k: v.copy() for k, v in (self._last_windows_ or {}).items() + }, + "id_names": self._id_names_, + "time_name": self._time_name_, + "was_single_series": self._was_single_series_, + "y_was_dataframe": self._y_is_dataframe_, } - # -------------------- test params for sktime test suite -------------------- + # -------------------- test params -------------------- @classmethod def get_test_params(cls, parameter_set: str = "default"): """Return parameter settings for the estimator tests.""" - # import inside to keep soft deps contained in tests from sklearn.linear_model import LinearRegression, Ridge if parameter_set == "fast": @@ -766,7 +1510,7 @@ def get_test_params(cls, parameter_set: str = "default"): "estimator": LinearRegression(), "window_length": 4, "steps_ahead": 2, - "normalization_strategy": meanvar_window_normalizer, + "normalization_strategy": mean_window_normalizer, # factory form } return [ @@ -774,19 +1518,19 @@ def get_test_params(cls, parameter_set: str = "default"): "estimator": LinearRegression(), "window_length": 5, "steps_ahead": 1, - "normalization_strategy": None, + "normalization_strategy": mean_window_normalizer(), # strategy form }, { "estimator": Ridge(alpha=0.1), "window_length": 3, "steps_ahead": 3, - "normalization_strategy": meanvar_window_normalizer, + "normalization_strategy": None, }, ] # --------------------------------------------------------------------------- -# Convenience factory (matching the earlier API idea; optional) +# Convenience factory # --------------------------------------------------------------------------- @@ -795,35 +1539,20 @@ def make_reduction( strategy: str = "recursive", window_length: int = 10, steps_ahead: Optional[int] = None, + normalization_strategy: Optional[ + Union[str, Callable[[np.ndarray], Tuple[Callable, Callable]]] + ] = None, x_mode: str = "auto", impute_missing: Optional[str] = "bfill", - normalization_strategy: NormStrategy = None, ) -> ReductionForecaster: """ Construct a ReductionForecaster. In this unified design: - - If ``strategy='recursive'`` and ``steps_ahead is None``, you'll get K=1 (pure recursive). + - If ``strategy='recursive'`` and ``steps_ahead is None``, you'll get K=1. - If ``strategy='direct'`` and you pass ``steps_ahead=K``, you'll get K direct heads for 1..K and recursive continuation beyond K. - Any other combination behaves the same as setting K=max(1, steps_ahead). - - ``normalization_strategy`` may be provided either way and is applied per-window. - - Parameters - ---------- - estimator : sklearn-style regressor - strategy : {"recursive", "direct"}, default="recursive" - window_length : int, default=10 - steps_ahead : int or None, default=None - Number of direct heads (K). If None, K=1 for "recursive" and K=1 for "direct" - unless explicitly provided. - x_mode : {"auto","none","concurrent"}, default="auto" - impute_missing : {"ffill","bfill",None}, default="bfill" - normalization_strategy : callable or None, default=None - - Returns - ------- - ReductionForecaster """ strategy = (strategy or "recursive").lower() if strategy not in ("recursive", "direct"): @@ -840,85 +1569,87 @@ def make_reduction( estimator=estimator, window_length=window_length, steps_ahead=K, + normalization_strategy=normalization_strategy, x_mode=x_mode, impute_missing=impute_missing, - normalization_strategy=normalization_strategy, ) # --------------------------------------------------------------------------- -# Example normalization strategy -# --------------------------------------------------------------------------- - - -def meanvar_window_normalizer( - window: np.ndarray, -) -> Tuple[Callable[[np.ndarray], np.ndarray], Callable[[np.ndarray], np.ndarray]]: - """ - Return (transform, inverse_transform) based on the window's mean and std. - - Parameters - ---------- - window : 1D ndarray - The y-lag window in feature order (most recent first). Only its statistics - are used; it is NOT modified in-place. - - Returns - ------- - transform : f(arr_1d) -> arr_1d - Applies (arr - mean) / max(std, eps) - inverse_transform : f(arr_1d) -> arr_1d - Applies arr * max(std, eps) + mean - """ - w = np.asarray(window, dtype=float).ravel() - mu = float(np.mean(w)) if w.size else 0.0 - sigma = float(np.std(w)) if w.size else 1.0 - # avoid division by zero - scale = sigma if sigma > 0.0 else 1.0 - - def transform(arr: np.ndarray) -> np.ndarray: - a = np.asarray(arr, dtype=float).ravel() - return (a - mu) / scale - - def inverse_transform(arr: np.ndarray) -> np.ndarray: - a = np.asarray(arr, dtype=float).ravel() - return a * scale + mu - - return transform, inverse_transform - - -# --------------------------------------------------------------------------- -# Minimal self-check (optional) +# Minimal smoke test (optional) # --------------------------------------------------------------------------- if __name__ == "__main__": - # tiny smoke test if run directly from sklearn.linear_model import LinearRegression rng = np.random.default_rng(0) - n = 80 - t = pd.date_range("2023-01-01", periods=n, freq="D") - y = pd.Series( - np.sin(np.linspace(0, 6, n)) + 0.1 * rng.standard_normal(n), index=t, name="y" + + # -------- Single series example -------- + n = 50 + t = pd.date_range("2024-01-01", periods=n, freq="D") + y_single = pd.Series( + np.sin(np.linspace(0, 4, n)) + 0.1 * rng.standard_normal(n), + index=t, + name="y", ) - X = pd.DataFrame({"cos": np.cos(np.linspace(0, 6, n))}, index=t) + X_single = pd.DataFrame({"cos": np.cos(np.linspace(0, 4, n))}, index=t) - f = make_reduction( + f_single = make_reduction( LinearRegression(), strategy="direct", window_length=7, steps_ahead=3, - normalization_strategy=meanvar_window_normalizer, + normalization_strategy=mean_window_normalizer, # factory OR mean_window_normalizer() ) - f.fit(y, X=X) - - # make a future X for next H days - H = 10 - fh = ForecastingHorizon(np.arange(1, H + 1), is_relative=True) - # construct X rows for absolute fh timestamps - abs_idx = _fh_to_absolute_index( - fh.to_absolute(f.cutoff), cutoff=f.cutoff, y_index=y.index, H=H + f_single.fit(y_single, X=X_single) + + H = 5 + future_times = pd.date_range(t[-1] + pd.Timedelta(days=1), periods=H, freq="D") + Xf_single = pd.DataFrame( + {"cos": np.cos(np.linspace(4, 4 + 0.05 * H, H))}, index=future_times ) - Xf = pd.DataFrame({"cos": np.cos(np.linspace(6, 6 + 0.1 * H, H))}, index=abs_idx) + print("Single-series forecast:") + print(f_single.predict(fh=future_times, X=Xf_single)) + + # -------- Multi-series example -------- + ids = ["A", "B"] + ys = [] + Xs = [] + for i, s in enumerate(ids): + y = pd.Series( + np.sin(np.linspace(0, 4, n)) + 0.1 * rng.standard_normal(n) + i, + index=t, + name="y", + ) + y.index = pd.MultiIndex.from_product([[s], y.index], names=["id", "time"]) + ys.append(y) + + X = pd.DataFrame({"cos": np.cos(np.linspace(0, 4, n))}, index=t) + X.index = pd.MultiIndex.from_product([[s], X.index], names=["id", "time"]) + Xs.append(X) + + y_all = pd.concat(ys) + X_all = pd.concat(Xs) + + f_multi = make_reduction( + LinearRegression(), + strategy="direct", + window_length=7, + steps_ahead=3, + normalization_strategy=mean_window_normalizer, + ) + f_multi.fit(y_all, X=X_all) + + future_times = pd.date_range(t[-1] + pd.Timedelta(days=1), periods=H, freq="D") + fh_abs = pd.MultiIndex.from_product([ids, future_times], names=["id", "time"]) + Xf = [] + for s in ids: + Xs_f = pd.DataFrame( + {"cos": np.cos(np.linspace(4, 4 + 0.05 * H, H))}, index=future_times + ) + Xs_f.index = pd.MultiIndex.from_product([[s], Xs_f.index], names=["id", "time"]) + Xf.append(Xs_f) + Xf = pd.concat(Xf) - print(f.predict(fh, X=Xf)) + print("\nMulti-series forecast:") + print(f_multi.predict(fh=fh_abs, X=Xf)) diff --git a/tests/forecasting/__pycache__/test_reduction.cpython-311-pytest-8.4.2.pyc b/tests/forecasting/__pycache__/test_reduction.cpython-311-pytest-8.4.2.pyc deleted file mode 100644 index b2db9b7..0000000 Binary files a/tests/forecasting/__pycache__/test_reduction.cpython-311-pytest-8.4.2.pyc and /dev/null differ diff --git a/tests/forecasting/test_reduction.py b/tests/forecasting/test_reduction.py index fec0791..136bd42 100644 --- a/tests/forecasting/test_reduction.py +++ b/tests/forecasting/test_reduction.py @@ -1,12 +1,45 @@ -"""Test the sktime contract for Prophet and HierarchicalProphet.""" +"""Test suite for the ReductionForecaster implementation.""" +import numpy as np +import pandas as pd import pytest # noqa: F401 +from sklearn.linear_model import LinearRegression +from sktime.forecasting.base import ForecastingHorizon from sktime.utils.estimator_checks import check_estimator, parametrize_with_checks -from tsbook.forecasting.global_reduction import GlobalReductionForecaster +from tsbook.forecasting.reduction import ReductionForecaster -@parametrize_with_checks([GlobalReductionForecaster]) +@parametrize_with_checks([ReductionForecaster]) def test_sktime_api_compliance(obj, test_name): - """Test the sktime contract for GlobalReductionForecaster.""" + """Test the sktime contract for ReductionForecaster.""" check_estimator(obj, tests_to_run=test_name, raise_exceptions=True) + + +def test_reduction_insample_predictions_cover_training(): + """Ensure in-sample predictions return finite values for recent history.""" + + n = 30 + idx = pd.RangeIndex(n, name="time") + y = pd.Series(np.arange(n, dtype=float), index=idx, name="y") + + forecaster = ReductionForecaster( + estimator=LinearRegression(), + window_length=5, + steps_ahead=3, + ) + forecaster.fit(y) + + fh = ForecastingHorizon([-5, -1, 0, 1, 2], is_relative=True) + y_pred = forecaster.predict(fh) + + insample_steps = [-5, -1, 0] + insample_times = [len(idx) - 1 + step for step in insample_steps] + + insample_forecasts = y_pred.loc[insample_times] + assert not insample_forecasts.isna().any() + + observed_values = y.loc[insample_times] + assert np.allclose(insample_forecasts.values, observed_values.values, atol=1e-6) + + assert len(y_pred) == len(fh)