Skip to content

Commit

Permalink
initial packaging (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
jmoralez authored Oct 10, 2023
1 parent e6abb56 commit 21280d4
Show file tree
Hide file tree
Showing 9 changed files with 169 additions and 36 deletions.
41 changes: 41 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:

defaults:
run:
shell: bash -l {0}

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
run-tests:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
python-version: ['3.10']
steps:
- name: Clone repo
uses: actions/checkout@v3

- name: Set up environment
uses: mamba-org/setup-micromamba@v1
with:
environment-file: environment.yml
create-args: python=${{ matrix.python-version }}
cache-environment: true

- name: Install the library
run: pip install --no-build-isolation -v .

- name: Run tests
run: pytest
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,6 @@

# Python
__pycache__

# CMake
build
24 changes: 21 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,28 @@ if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release)
endif()

set(CMAKE_CXX_FLAGS "-fPIC -Wall -Wextra -Wpedantic")
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_FLAGS_DEBUG "-g")
set(CMAKE_CXX_FLAGS_RELEASE "-O3")

set(CMAKE_CXX_STANDARD 17)
if(APPLE)
set(CMAKE_SHARED_LIBRARY_SUFFIX ".so")
endif()

if(UNIX)
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fPIC -O3 -Wall -Wextra -Wpedantic")
else()
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /O2 /Ot /Oy /W4")
endif()


if(SKBUILD)
set(LIBRARY_OUTPUT_PATH ${SKBUILD_PLATLIB_DIR}/coreforecast/lib)
else()
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/coreforecast/lib)
endif()

include_directories(include)
add_library(coreforecast SHARED src/coreforecast.cpp)
if(MSVC)
set_target_properties(coreforecast PROPERTIES OUTPUT_NAME "libcoreforecast")
endif()
35 changes: 25 additions & 10 deletions coreforecast/grouped_array.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,24 @@
import ctypes
import platform

import numpy as np
from importlib_resources import files


_LIB = ctypes.CDLL("build/libcoreforecast.so")
if platform.system() in ("Windows", "Microsoft"):
prefix = "Release"
extension = "dll"
else:
prefix = ""
extension = "so"

_LIB = ctypes.CDLL(
str(files("coreforecast").joinpath("lib", prefix, f"libcoreforecast.{extension}"))
)


def _data_as_ptr(arr: np.ndarray, dtype):
return arr.ctypes.data_as(ctypes.POINTER(dtype))


class GroupedArray:
Expand All @@ -12,9 +27,9 @@ def __init__(self, data: np.ndarray, indptr: np.ndarray):
self.indptr = indptr
self._handle = ctypes.c_void_p()
_LIB.GroupedArray_CreateFromArrays(
data.ctypes.data_as(ctypes.POINTER(ctypes.c_float)),
_data_as_ptr(data, ctypes.c_float),
ctypes.c_int32(data.size),
indptr.ctypes.data_as(ctypes.POINTER(ctypes.c_int32)),
_data_as_ptr(indptr, ctypes.c_int32),
ctypes.c_int32(indptr.size),
ctypes.byref(self._handle),
)
Expand All @@ -25,29 +40,29 @@ def __del__(self):
def __len__(self):
return self.indptr.size - 1

def scaler_fit(self, stats_fn_name: str) -> None:
def scaler_fit(self, stats_fn_name: str) -> np.ndarray:
stats = np.empty((len(self), 2), dtype=np.float64)
stats_fn = getattr(_LIB, stats_fn_name)
stats_fn = _LIB[stats_fn_name]
stats_fn(
self._handle,
stats.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),
_data_as_ptr(stats, ctypes.c_double),
)
return stats

def scaler_transform(self, stats: np.ndarray) -> np.ndarray:
out = np.full_like(self.data, np.nan)
_LIB.GroupedArray_ScalerTransform(
self._handle,
stats.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),
out.ctypes.data_as(ctypes.POINTER(ctypes.c_float)),
_data_as_ptr(stats, ctypes.c_double),
_data_as_ptr(out, ctypes.c_float),
)
return out

def scaler_inverse_transform(self, stats: np.ndarray) -> np.ndarray:
out = np.empty_like(self.data)
_LIB.GroupedArray_ScalerInverseTransform(
self._handle,
stats.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),
out.ctypes.data_as(ctypes.POINTER(ctypes.c_float)),
_data_as_ptr(stats, ctypes.c_double),
_data_as_ptr(out, ctypes.c_float),
)
return out
13 changes: 13 additions & 0 deletions dev_environment.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
name: coreforecast
channels:
- conda-forge
dependencies:
- black
- build
- clang-format
- cmake
- mypy
- ninja
- numpy
- pytest
- scikit-build-core
6 changes: 2 additions & 4 deletions environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@ name: coreforecast
channels:
- conda-forge
dependencies:
- black
- clang-format
- cmake
- make
- ninja
- numpy
- pip
- pytest
- scikit-build-core
33 changes: 22 additions & 11 deletions include/coreforecast.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,36 @@

#include <cstdint>

#ifdef _MSC_VER
#define DLL_EXPORT __declspec(dllexport)
#else
#define DLL_EXPORT
#endif

typedef void *GroupedArrayHandle;

extern "C" {
int GroupedArray_CreateFromArrays(float *data, int32_t n_data, int32_t *indptr,
int32_t n_groups, GroupedArrayHandle *out);
DLL_EXPORT int GroupedArray_CreateFromArrays(float *data, int32_t n_data,
int32_t *indptr, int32_t n_groups,
GroupedArrayHandle *out);

int GroupedArray_Delete(GroupedArrayHandle handle);
DLL_EXPORT int GroupedArray_Delete(GroupedArrayHandle handle);

int GroupedArray_MinMaxScalerStats(GroupedArrayHandle handle, double *out);
DLL_EXPORT int GroupedArray_MinMaxScalerStats(GroupedArrayHandle handle,
double *out);

int GroupedArray_StandardScalerStats(GroupedArrayHandle handle, double *out);
DLL_EXPORT int GroupedArray_StandardScalerStats(GroupedArrayHandle handle,
double *out);

int GroupedArray_RobustScalerIqrStats(GroupedArrayHandle handle, double *out);
DLL_EXPORT int GroupedArray_RobustScalerIqrStats(GroupedArrayHandle handle,
double *out);

int GroupedArray_RobustScalerMadStats(GroupedArrayHandle handle, double *out);
DLL_EXPORT int GroupedArray_RobustScalerMadStats(GroupedArrayHandle handle,
double *out);

int GroupedArray_ScalerTransform(GroupedArrayHandle handle, double *stats,
float *out);
DLL_EXPORT int GroupedArray_ScalerTransform(GroupedArrayHandle handle,
double *stats, float *out);

int GroupedArray_ScalerInverseTransform(GroupedArrayHandle handle,
double *stats, float *out);
DLL_EXPORT int GroupedArray_ScalerInverseTransform(GroupedArrayHandle handle,
double *stats, float *out);
}
42 changes: 38 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,44 @@
[build-system]
requires = ["scikit-build-core"]
build-backend = "scikit_build_core.build"

[project]
name = "coreforecast"
version = "0.0.1"
requires-python = ">=3.7"
dependencies = [
"importlib_resources ; python_version < '3.10'",
"numpy",
]
license = {file = "LICENSE"}
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Science/Research",
"License :: OSI Approved :: Apache Software License",
"Natural Language :: English",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
]
description = "Fast implementations of common forecasting routines"
authors = [
{name = "José Morales", email = "[email protected]"},
]
readme = "README.md"
keywords = ["forecasting", "time-series"]

[project.urls]
homepage = "https://nixtla.github.io/coreforecast"
documentation = "https://nixtla.github.io/coreforecast"
repository = "https://github.com/Nixtla/coreforecast"

[build-system]
requires = ["scikit-build-core"]
build-backend = "scikit_build_core.build"

[tool.scikit-build]
cmake.verbose = true
logging.level = "INFO"
sdist.exclude = ["tests", "*.yml"]
sdist.reproducible = true
wheel.install-dir = ["coreforecast"]
wheel.packages = ["coreforecast"]
wheel.py-api = "cp37"
8 changes: 4 additions & 4 deletions src/coreforecast.cpp
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
#include <algorithm>
#include <math.h>
#include <cmath>
#include <numeric>

#include "coreforecast.h"

inline float CommonScalerTransform(float data, double scale, double offset) {
return (data - offset) / scale;
return static_cast<float>((data - offset) / scale);
}

inline float CommonScalerInverseTransform(float data, double scale,
double offset) {
return data * scale + offset;
return static_cast<float>(data * scale + offset);
}

inline int FirstNotNaN(const float *data, int n) {
Expand Down Expand Up @@ -68,7 +68,7 @@ inline void RobustScalerMadStats(const float *data, int n, double *stats) {
float *buffer = new float[n];
std::copy(data, data + n, buffer);
std::sort(buffer, buffer + n);
const float median = Quantile(buffer, 0.5F, n);
const float median = static_cast<float>(Quantile(buffer, 0.5F, n));
for (int i = 0; i < n; ++i) {
buffer[i] = std::abs(buffer[i] - median);
}
Expand Down

0 comments on commit 21280d4

Please sign in to comment.