Geometric temporal embeddings for machine learning — Archimedean spiral positional encodings for LSTMs, Transformers, and any time series model
Project description
spiral-time
Geometric temporal embeddings for machine learning.
Spiral time replaces scalar time features with a geometrically structured embedding derived from an Archimedean spiral. Every timestep is represented as a point in 2D spiral space, decomposing naturally into:
- Angular component
[cos(θ), sin(θ)]— phase within the recurring cycle (seasonality) - Radial component
r = z-score(θ)— cumulative progression along the trend
In a 10-experiment ablation on US monthly retail sales, multi-period spiral time embeddings achieved 1.69% MAPE — outperforming scalar time (9.76%) by 83% and hand-engineered sinusoidal features (4.62%) by 63%.
Installation
# Core (NumPy only)
pip install spiral-time
# With PyTorch support
pip install spiral-time[torch]
# Full (torch + pandas + sklearn + matplotlib)
pip install spiral-time[full]
Quick Start
NumPy — works with any model
import numpy as np
from spiral_time import spiral_embedding
t = np.arange(168) # 168 monthly timesteps
emb = spiral_embedding(t, periods=[3, 6, 12]) # quarterly + semi-annual + annual
# emb.shape → (168, 9)
# Drop into any sklearn / XGBoost / LightGBM pipeline
from sklearn.ensemble import GradientBoostingRegressor
X = np.hstack([values.reshape(-1, 1), emb])
model = GradientBoostingRegressor().fit(X[:-12], y[:-12])
LSTM / RNN
from spiral_time import spiral_embedding
# Replace scalar time feature with spiral embedding
t_features = spiral_embedding(t, periods=[3, 6, 12]) # (n, 9)
# Concatenate with value sequence as LSTM input
x = np.concatenate([values[:, None], t_features], axis=1)
Transformer — drop-in positional encoding
import torch
from spiral_time import SpiralTimeEncoding
# Replace this:
# self.pos_enc = PositionalEncoding(d_model=128)
# With this:
self.pos_enc = SpiralTimeEncoding(
d_model=128,
periods=[24, 168, 720], # daily, weekly, monthly (hourly data)
)
# Forward call unchanged:
x = self.pos_enc(x) # (batch, seq_len, 128)
Auto-detect periods from data
from spiral_time.embedding import detect_periods, spiral_embedding
periods = detect_periods(y, n_periods=3)
print(periods) # e.g. [365.2, 30.4, 7.0]
emb = spiral_embedding(t, periods=periods)
Stateful embedding (no data leakage)
from spiral_time import SpiralEmbedding
emb = SpiralEmbedding(periods=[3, 6, 12])
X_train = emb.fit_transform(t_train) # fits normalisation on train set
X_test = emb.transform(t_test) # applies train stats to test set
The Embedding
For a set of periods {T₁, T₂, ..., T_K}:
MTE(t) = [cos(θ₁), sin(θ₁), r₁, cos(θ₂), sin(θ₂), r₂, ..., cos(θ_K), sin(θ_K), r_K]
where:
θ_k = 2π·t / T_k
r_k = z-score(θ_k) ← independently normalised per period
Output dimension: 3K (with radial) or 2K (phase only).
The angular components [cos(θ), sin(θ)] are identical to the sinusoidal positional encodings used in Transformer architectures. The radial component r_k adds the trend axis — making explicit how many cycles have elapsed at each timescale.
Transformer Classes
from spiral_time import (
SpiralTimeEncoding, # fixed multi-period spiral PE
MultiPeriodSpiralAttention, # learned period weighting
SpiralTransformerForecaster, # full reference implementation
)
SpiralTimeEncoding
Drop-in for PositionalEncoding. Pre-computes spiral embeddings and projects to d_model via a learned linear layer.
MultiPeriodSpiralAttention
Learns a softmax-normalised weight over a set of candidate periods. After training, inspect period_weights to discover which timescales the model relied on:
mpa = MultiPeriodSpiralAttention(
d_model=128,
candidate_periods=[3, 7, 14, 30, 60, 90, 180, 365],
)
# ... train model ...
weights = torch.softmax(mpa.period_weights, dim=0)
for T, w in sorted(zip(candidate_periods, weights.tolist()), key=lambda x: -x[1]):
print(f"T={T:>4} weight={w:.3f}")
SpiralTransformerForecaster
Minimal end-to-end reference implementation. Use this as a template for integrating spiral PE into PatchTST, iTransformer, Autoformer, or any existing architecture.
Benchmark Results
LSTM ablation — US Monthly Retail Sales (168 months)
| Encoding | MAPE |
|---|---|
| Scalar time | 9.76% |
| Engineered [cos, sin] | 4.62% |
| Spiral Archimedean (z-score r) | 3.19% |
| Multi-period Spiral ×3 | 1.69% |
Transformer benchmark — ETTh1 / ETTm1 (OT target)
(Run python benchmarks/ett_benchmark.py to reproduce)
| Dataset | PE | pred=24 MAE | pred=48 MAE | pred=96 MAE |
|---|---|---|---|---|
| ETTh1 | No PE | — | — | — |
| ETTh1 | Sinusoidal | — | — | — |
| ETTh1 | Spiral Time | — | — | — |
| ETTm1 | No PE | — | — | — |
| ETTm1 | Sinusoidal | — | — | — |
| ETTm1 | Spiral Time | — | — | — |
Fill in after running the benchmark — results are saved to benchmarks/ett_results.json.
When to Use Spiral Time
Spiral time is most effective when your data has:
- Known or estimable dominant periods (daily, weekly, annual, etc.)
- A long-run trend layered on those cycles (growth, decline, drift)
- Multiple interacting periodicities (the multi-period extension gives the largest gain)
It is a direct drop-in for:
- Scalar time index features (
t = 0, 1, 2, ...) - Hand-engineered date features (
month,day_of_week,hour) - Standard sinusoidal positional encodings in Transformers
Applications
| Domain | Periods to use | Expected benefit |
|---|---|---|
| Energy demand (hourly) | [24, 168, 8760] | Daily + weekly + annual cycles |
| Retail sales (monthly) | [3, 6, 12] | Quarterly + semi-annual + annual |
| Financial returns (daily) | [5, 21, 63, 252] | Weekly + monthly + quarterly + annual |
| Drug dosing (hourly) | [24, 168] | Circadian + weekly schedule |
| EEG / neural signals | [band-specific] | Neural oscillation phase encoding |
| Weather (daily) | [365, 1461] | Annual + 4-year leap cycle |
Theoretical Background
Spiral time is grounded in differential geometry. Each timestep maps to a point on an Archimedean spiral in 2D space:
tₓ(θ) = aθ · cos(θ)
tᵧ(θ) = aθ · sin(θ)
This framework:
- Generalises Transformer sinusoidal PE by adding an explicit trend coordinate
- Connects to Hawking–Hartle imaginary time and Kaluza–Klein extra dimensions
- Provides a geometric basis for phase resonance in attention mechanisms
See the full paper: blog_post.md
Citation
@article{spiraltime2026,
title = {Spiral Time: A Geometric Reframing of Temporal Structure and Its Applications in Machine Learning},
author = {[Your Name]},
year = {2026},
url = {https://github.com/your-username/spiral-time}
}
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file spiral_time-0.1.0.tar.gz.
File metadata
- Download URL: spiral_time-0.1.0.tar.gz
- Upload date:
- Size: 13.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
437b57aed606c8c406c58d2cc50f1124762bb6b3c377647b119616909a348dae
|
|
| MD5 |
07a93d6fcb49a85bdafa7cf7550f8a96
|
|
| BLAKE2b-256 |
523a1cea33f47757c2e7f91b8dd1dbd73ec11d3e2cde0403d88ee9381aeb6313
|
File details
Details for the file spiral_time-0.1.0-py3-none-any.whl.
File metadata
- Download URL: spiral_time-0.1.0-py3-none-any.whl
- Upload date:
- Size: 10.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f55a3e8d751838bef9d35e675d9a0f75a5dd495dd2d73ce20870ba7c302c1b41
|
|
| MD5 |
08ebd3c3cf419f75a331920ee6738031
|
|
| BLAKE2b-256 |
f55a1280980825adc5e0bc69d8358724a76b74a918b1f7d48f950e79078dee3e
|