Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adam forecaster #216

Merged
merged 6 commits into from
Feb 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .github/workflows/python_ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: Python CI

on:
push:
branches: [Python]
pull_request:
branches: [Python]

jobs:
linting:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: chartboost/ruff-action@v1
with:
src: "./python"
82 changes: 75 additions & 7 deletions python/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,8 @@ readme = "README.md"
requires-python = ">=3.8,<3.11"
dependencies = ["pybind11[global]>=2.6.0", "numpy>=1.14"]

# [tool.setuptools.packages]
# find = {}

[project.optional-dependencies]
dev = ["flake8", "black", "pytest", "pydocstyle", "pre-commit", "ipykernel"]

# [tool.pytest.ini_options]
# addopts = "--ignore=../src/carma"
dev = ["pytest", "ruff", "pre-commit", "ipykernel"]

[tool.scikit-build]
# wheel.expand-macos-universal-tags = true
Expand All @@ -28,3 +22,77 @@ ninja.make-fallback = true
cmake.verbose = true
ninja.minimum-version = "1.5"
cmake.minimum-version = "3.25"

[tool.ruff]
# Exclude a variety of commonly ignored directories.
exclude = [
".bzr",
".direnv",
".eggs",
".git",
".git-rewrite",
".hg",
".mypy_cache",
".nox",
".pants.d",
".pytype",
".ruff_cache",
".svn",
".tox",
".venv",
"__pypackages__",
"_build",
"buck-out",
"build",
"dist",
"node_modules",
"venv",
"tools/cookiecutter_templates",
]

# Same as Black.
line-length = 88
indent-width = 4

# Assume Python 3.8
target-version = "py38"

[tool.ruff.lint]
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or
# McCabe complexity (`C901`) by default.
# Rules for ruff are found here: https://docs.astral.sh/ruff/rules/
select = [
"E4",
"E7",
"E9",
"E501",
"F",
"I", # isort rules
"N", # PEP-8 naming rules
]
ignore = [
# uppercase names are used for matrices
"N803", # allow uppercase argument names
"N806", # allow uppercase variable names
]

# Allow fix for all enabled rules (when `--fix`) is provided.
fixable = ["ALL"]
unfixable = []

# Allow unused variables when underscore-prefixed.
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"

[tool.ruff.format]
# Like Black, use double quotes for strings.
quote-style = "double"

# Like Black, indent with spaces, rather than tabs.
indent-style = "space"

# Like Black, respect magic trailing commas.
skip-magic-trailing-comma = false

# Like Black, automatically detect the appropriate line ending.
line-ending = "auto"
53 changes: 29 additions & 24 deletions python/smooth/adam_general/adam_profile.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import numpy as np


def adamProfileCreator(
lagsModelAll, lagsModelMax, obsAll, lags=None, yIndex=None, yClasses=None
def adam_profile_creator(
lags_model_all, lags_model_max, obs_all, lags=None, y_index=None, y_classes=None
):
"""
Creates recent profile and the lookup table for adam.
Expand All @@ -14,50 +14,55 @@ def adamProfileCreator(
yIndex (list): The indices needed to get the specific dates (optional).
yClasses (list): The class used for the actual data (optional).
Returns:
dict: A dictionary with 'recent' (profilesRecentTable) and 'lookup' (indexLookupTable) as keys.
dict: A dictionary with 'recent' (profilesRecentTable) and 'lookup'
(indexLookupTable) as keys.
"""
# Initialize matrices
profilesRecentTable = np.zeros((len(lagsModelAll), lagsModelMax))
indexLookupTable = np.ones((len(lagsModelAll), obsAll + lagsModelMax))
profileIndices = (
np.arange(1, lagsModelMax * len(lagsModelAll) + 1)
.reshape(-1, len(lagsModelAll))
profiles_recent_table = np.zeros((len(lags_model_all), lags_model_max))
index_lookup_table = np.ones((len(lags_model_all), obs_all + lags_model_max))
profile_indices = (
np.arange(1, lags_model_max * len(lags_model_all) + 1)
.reshape(-1, len(lags_model_all))
.T
)

# Update matrices based on lagsModelAll
for i, lag in enumerate(lagsModelAll):
for i, lag in enumerate(lags_model_all):
# Create the matrix with profiles based on the provided lags.
# For every row, fill the first 'lag' elements from 1 to lag
profilesRecentTable[i, : lag[0]] = np.arange(1, lag[0] + 1)
profiles_recent_table[i, : lag[0]] = np.arange(1, lag[0] + 1)

# For the i-th row in indexLookupTable, fill with a repeated sequence starting from lagsModelMax to the end of the row.
# The repeated sequence is the i-th row of profileIndices, repeated enough times to cover 'obsAll' observations.
# For the i-th row in indexLookupTable, fill with a repeated sequence starting
# from lagsModelMax to the end of the row.
# The repeated sequence is the i-th row of profileIndices, repeated enough times
# to cover 'obsAll' observations.
# '- 1' at the end adjusts these values to Python's zero-based indexing.
indexLookupTable[i, lagsModelMax : (lagsModelMax + obsAll)] = ( # noqa
index_lookup_table[i, lags_model_max : (lags_model_max + obs_all)] = (
np.tile(
profileIndices[i, : lagsModelAll[i][0]],
int(np.ceil(obsAll / lagsModelAll[i][0])),
)[0:obsAll]
profile_indices[i, : lags_model_all[i][0]],
int(np.ceil(obs_all / lags_model_all[i][0])),
)[0:obs_all]
- 1
)

# Extract unique values from from lagsModelMax to lagsModelMax + obsAll of indexLookupTable
# Extract unique values from from lagsModelMax to lagsModelMax + obsAll of
# indexLookupTable
unique_values = np.unique(
indexLookupTable[i, lagsModelMax : lagsModelMax + obsAll] # noqa
index_lookup_table[i, lags_model_max : lags_model_max + obs_all] # noqa
)

# fix the head of teh data before the sample starts
# Repeat the unique values lagsModelMax times and then trim the sequence to only keep the first lagsModelMax elements
indexLookupTable[i, :lagsModelMax] = np.tile(unique_values, lagsModelMax)[
-lagsModelMax:
# Repeat the unique values lagsModelMax times and then trim the sequence to only
# keep the first lagsModelMax elements
index_lookup_table[i, :lags_model_max] = np.tile(unique_values, lags_model_max)[
-lags_model_max:
]

# Convert to int!
indexLookupTable = indexLookupTable.astype(int)
index_lookup_table = index_lookup_table.astype(int)

# Note: I skip andling of special cases (e.g., daylight saving time, leap years)
return {
"recent": np.array(profilesRecentTable, dtype="float64"),
"lookup": np.array(indexLookupTable, dtype="int64"),
"recent": np.array(profiles_recent_table, dtype="float64"),
"lookup": np.array(index_lookup_table, dtype="int64"),
}
83 changes: 65 additions & 18 deletions python/smooth/adam_general/sma.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import numpy as np
from smooth.adam_general._adam_general import adam_fitter
from smooth.adam_general.adam_profile import adamProfileCreator

from smooth.adam_general._adam_general import adam_fitter, adam_forecaster
from smooth.adam_general.adam_profile import adam_profile_creator


def sma(y, order=1, h=10, holdout=False):
"""SMA"""
y = y.astype(np.float64)

ic = lambda e: np.sum(e**2)
# ic = lambda e: np.sum(e**2)
obs_all = len(y) + h * (1 - holdout)
obs_in_sample = len(y) - h * holdout
y_in_sample = y
Expand All @@ -25,14 +26,14 @@ def sma(y, order=1, h=10, holdout=False):
def creator_sma(order):
# lags_model_all = np.ones(shape=(order, 1))
# This needs to be a vector of values
lags_model_all = np.arange(1, order+1, dtype="int32").reshape(order, 1)
lags_model_all = np.arange(1, order + 1, dtype="int32").reshape(order, 1)
lags_model_max = int(max(lags_model_all))
obs_states = obs_in_sample + lags_model_max

profiles_recent_table, index_lookup_table = adamProfileCreator(
lagsModelAll=lags_model_all,
lagsModelMax=lags_model_max,
obsAll=obs_all
profiles_recent_table, index_lookup_table = adam_profile_creator(
lags_model_all=lags_model_all,
lags_model_max=lags_model_max,
obs_all=obs_all,
).values()

# # This needs to be generated by a profileCreator() function
Expand All @@ -45,19 +46,40 @@ def creator_sma(order):
# np.arange(order), (obs_all + lags_model_max, 1)
# ).T

matF = np.ones((order, order)) / order
matWt = np.ones((obs_in_sample, order))
mat_F = np.ones((order, order)) / order
mat_Wt = np.ones((obs_in_sample, order))

vecG = np.ones(order) / order
vec_G = np.ones(order) / order
# matVt = np.zeros((order, obs_states))
matVt = np.empty((order, obs_states))
mat_Vt = np.empty((order, obs_states))
# matVt.fill(np.nan)

adam_fitted = adam_fitter(
matrixVt=matVt,
matrixWt=matWt,
matrixF=matF,
vectorG=vecG,
matrixVt=mat_Vt,
matrixWt=mat_Wt,
matrixF=mat_F,
vectorG=vec_G,
lags=lags_model_all,
indexLookupTable=index_lookup_table,
profilesRecent=profiles_recent_table,
E=E_type,
T=T_type,
S=S_type,
nNonSeasonal=components_num_ETS,
nSeasonal=components_num_ETS_seasonal,
nArima=order,
nXreg=xreg_number,
constant=constant_required,
vectorYt=y_in_sample,
vectorOt=ot,
backcast=True,
)

fitted_args = dict(
matrixVt=mat_Vt,
matrixWt=mat_Wt,
matrixF=mat_F,
vectorG=vec_G,
lags=lags_model_all,
indexLookupTable=index_lookup_table,
profilesRecent=profiles_recent_table,
Expand All @@ -74,6 +96,31 @@ def creator_sma(order):
backcast=True,
)

return adam_fitted
return adam_fitted, fitted_args

sma_fitted, fitted_args = creator_sma(order=order)

# need to convert some inputs to the expected dtypes. This is a temporary fix.
fitted_args["lags"] = np.array(fitted_args["lags"], dtype="uint64")
fitted_args["indexLookupTable"] = np.array(
fitted_args["indexLookupTable"], dtype="uint64"
)

sma_forecast = adam_forecaster(
matrixWt=fitted_args["matrixWt"],
matrixF=fitted_args["matrixF"],
lags=fitted_args["lags"],
indexLookupTable=fitted_args["indexLookupTable"],
profilesRecent=sma_fitted["profile"],
E=fitted_args["E"],
T=fitted_args["T"],
S=fitted_args["S"],
nNonSeasonal=fitted_args["nNonSeasonal"],
nSeasonal=fitted_args["nSeasonal"],
nArima=fitted_args["nArima"],
nXreg=fitted_args["nXreg"],
constant=fitted_args["constant"],
horizon=h,
)

return creator_sma(order=order)
return sma_forecast
3 changes: 2 additions & 1 deletion python/smooth/adam_general/test_script.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import numpy as np

from smooth.adam_general.sma import sma

if __name__ == "__main__":
y = np.arange(0, 100)
results = sma(y, order=5)
print(results["yFitted"])
print(results)
6 changes: 0 additions & 6 deletions python/smooth/my_linalg/__init__.py

This file was deleted.

Empty file.
33 changes: 0 additions & 33 deletions python/smooth/my_linalg/tests/test_my_linalg.py

This file was deleted.

Loading
Loading