Skip to content

Commit

Permalink
Merge pull request #216 from ltsaprounis/adam_forecaster
Browse files Browse the repository at this point in the history
Adam forecaster
  • Loading branch information
config-i1 committed Feb 13, 2024
2 parents 3bc5fa0 + c0b48b2 commit 9e86476
Show file tree
Hide file tree
Showing 9 changed files with 230 additions and 89 deletions.
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

0 comments on commit 9e86476

Please sign in to comment.