PPF - Symbolic Form Discovery
Discover interpretable mathematical formulas from data, then deploy them anywhere.
PPF (Promising Partial Form) is a law-aware symbolic regression library that finds compact mathematical expressions describing your data. Unlike black-box neural networks, PPF produces human-readable formulas grounded in physical reality that can be deployed to edge devices in under 100 bytes.
Core Philosophy
A Promising Partial Form is a mathematical expression that explains a meaningful portion of a signal with high explanatory power and low complexity. Complex signals are modeled as layered sums:
y(t) ≈ f₁(t) + f₂(t) + ... + fₖ(t) + ε(t)
where each fᵢ(t) is a discovered form (oscillation, decay, trend, etc.) and ε(t) is noise-like residual.
PPF repeatedly discovers → subtracts → analyzes residuals to reveal the layered mathematical structure hidden in data. This approach applies to any 1D time-series or signal analysis task - from real-time sensor monitoring to scientific data exploration to serving as an interpretable front-end to downstream AI systems.
Why PPF?
| Traditional ML | PPF Approach |
|---|---|
| Train neural network | Discover formula |
| Deploy 10-100KB model | Deploy 50-byte expression |
| 1000s of MACs/inference | 10 FLOPs/evaluation |
| Black box | Interpretable |
| Requires TensorFlow Lite | Runs on any microcontroller |
Key insight: Many real-world signals (sensor data, biological rhythms, physical systems) follow simple mathematical forms. PPF attempts to discover those forms automatically.
Information-theoretic view: Where neural networks compress data into opaque weight matrices, PPF compresses data into explicit equations. This enables a layered architecture: Raw Signal → PPF (laws) → ML (meaning) → Decisions, where PPF converts high-entropy measurements into low-entropy symbolic representations.
Quick Start
pip install timeseries-formula-finder
import numpy as np
from ppf import SymbolicRegressor, export_python, export_c
# Generate example data
t = np.linspace(0, 10, 200)
y = 2.5 * np.exp(-0.3 * t) * np.sin(4.0 * t + 0.5) + np.random.randn(200) * 0.1
# Discover the underlying formula
regressor = SymbolicRegressor(generations=30)
result = regressor.discover(t, y, verbose=True)
print(f"Discovered: {result.best_tradeoff.expression_string}")
print(f"R-squared: {result.best_tradeoff.r_squared:.4f}")
# Output: Discovered: 2.498*exp(-0.301*x)*sin(4.002*x + 0.497)
# Output: R-squared: 0.9847
# Export to standalone Python (no PPF dependency)
python_code = export_python(result.best_tradeoff.expression, fn_name="predict")
exec(python_code)
print(predict(1.5)) # Works without importing ppf
# Export to C for embedded deployment
c_code = export_c(result.best_tradeoff.expression, use_float=True)
# Compile with: gcc -std=c99 -O2 -lm model.c
Command-Line Interface
PPF includes a powerful CLI for quick analysis without writing code:
# Discover formulas from CSV data
ppf discover data.csv -x time -y signal
# Verbose mode with specific discovery mode
ppf discover data.csv --mode oscillator -v -g 100
# Detect mathematical forms in windows
ppf detect sensor.csv --min-r-squared 0.8
# Extract forms iteratively until residuals are noise
ppf stack data.csv --entropy-method spectral
# Analyze with SSA decomposition (no extra dependencies)
ppf hybrid data.csv --method ssa
# Export discovered formula to Python code
ppf --json discover data.csv | ppf export python -f predict > model.py
# Export to C for embedded systems
ppf --json discover data.csv | ppf export c -f sensor_model --float > model.h
# Show available discovery modes
ppf info modes
Key Commands:
| Command | Purpose |
|---|---|
discover |
Symbolic regression to find formulas |
detect |
Detect forms in data windows |
stack |
Extract forms layer-by-layer |
hierarchy |
Find nested multi-timescale patterns |
hybrid |
EMD/SSA decomposition + interpretation |
export |
Export to Python/C/JSON |
features |
Extract ML-ready features |
info |
Show modes, forms, macros |
Use ppf --help or ppf <command> --help for detailed documentation.
See docs/CLI.md for the complete CLI reference.
Features
Multi-Domain Discovery
PPF includes domain-specific "vocabularies" that guide the search:
from ppf import SymbolicRegressor, DiscoveryMode
regressor = SymbolicRegressor()
# Let PPF auto-detect the best domain
result = regressor.discover(x, y, mode=DiscoveryMode.AUTO)
# Or specify a domain if you know it
result = regressor.discover(x, y, mode=DiscoveryMode.OSCILLATOR) # Vibrations, waves
result = regressor.discover(x, y, mode=DiscoveryMode.CIRCUIT) # RC charging, decay
result = regressor.discover(x, y, mode=DiscoveryMode.GROWTH) # Sigmoids, saturation
result = regressor.discover(x, y, mode=DiscoveryMode.RATIONAL) # Ratios, feedback
result = regressor.discover(x, y, mode=DiscoveryMode.UNIVERSAL) # Power laws, Gaussians
Macro Templates
PPF includes "macros" - pre-composed functional forms that capture common physics:
| Macro | Formula | Use Case |
|---|---|---|
DAMPED_SIN |
a·exp(-k·t)·sin(ω·t + φ) |
Vibration decay |
RC_CHARGE |
a·(1 - exp(-k·t)) + c |
Capacitor charging |
GAUSSIAN |
a·exp(-((x-μ)/σ)²) |
Peaks, distributions |
SIGMOID |
a / (1 + exp(-k·(x-x₀))) |
Saturation curves |
POWER_LAW |
a·x^b + c |
Scaling phenomena |
HILL |
a·x^n / (k^n + x^n) |
Enzyme kinetics |
These let PPF find complex forms (multiplicative compositions across function families) that pure GP struggles with.
Export to Edge Devices
Discovered formulas export to production-ready code:
from ppf import export_python, export_c, export_json
# Standalone Python evaluator
code = export_python(expr, fn_name="predict_temp", safe=True)
# Includes safety wrappers for div-by-zero, log(0), exp overflow
# C99 for microcontrollers
code = export_c(expr, use_float=True, safe=True)
# Compiles on ESP32, STM32, Arduino, etc.
# JSON bundle for storage/transmission
bundle = export_json(result, variables=["t"])
# Send via MQTT, store in database, audit trail
Feature Extraction for Downstream ML
Use discovered forms as features for classification/regression:
from ppf import extract_features, feature_vector
# Extract interpretable features
features = extract_features(result)
print(features["dominant_family"]) # "oscillation"
print(features["damping_k"]) # 0.301
print(features["omega"]) # 4.002
# Convert to ML-ready vector
vec, names = feature_vector(features, schema="ppf.features.v1.full")
# Feed to sklearn, XGBoost, etc.
Installation
From PyPI (recommended)
pip install timeseries-formula-finder
From source
git clone https://github.com/pcoz/timeseries-formula-finder.git
cd timeseries-formula-finder
pip install -e .
Optional dependencies
# For hybrid decomposition (EMD/SSA)
pip install timeseries-formula-finder[hybrid]
# For development (tests)
pip install timeseries-formula-finder[dev]
Documentation
| Document | Description |
|---|---|
| USER_GUIDE.md | Complete usage guide with examples |
| docs/CLI.md | Command-line interface reference |
| docs/PPF_Paper.md | Core PPF concepts and architecture |
| docs/PPF_Information_Theory_Paper.md | Information-theoretic foundations |
| COMPARISON.md | How PPF compares to PySR, gplearn, Eureqa, AI Feynman |
| USE_CASES.md | Edge AI, ECG analysis, predictive maintenance |
| TESTING.md | Test suite details and datasets used |
| docs/PPF_EXPORT_LAYER_TSD.md | Export layer technical specification |
| docs/IOT_SENSOR_ANALYSIS.md | Real IoT sensor case study |
| docs/ECG_ANALYSIS.md | ECG waveform analysis case study |
| DOCUMENTATION.md | Comprehensive API documentation |
Use Cases
Edge AI / IoT Sensors
Deploy temperature prediction models to ESP32 in 50 bytes instead of TensorFlow Lite:
# Discover daily temperature cycle from sensor data
result = regressor.discover(hours, temp, mode=DiscoveryMode.OSCILLATOR)
# Found: T(t) = 36 + 5·cos(2π·t/24 - 2) [R² = 0.58]
# Export to C for microcontroller
c_code = export_c(result.best_tradeoff.expression, use_float=True)
# 3 parameters, 10 FLOPs, runs at 100kHz on ESP32
Biomedical Signal Analysis
Extract interpretable features from ECG waveforms:
# Analyze T-wave morphology
result = regressor.discover(t_wave_time, t_wave_amplitude, mode=DiscoveryMode.UNIVERSAL)
# Found: Damped cosine (R² = 0.96) - indicates repolarization dynamics
# Use parameters as cardiac health features
features = extract_features(result)
# damping_k, omega → feed to arrhythmia classifier
Predictive Maintenance
Classify machine health from vibration signatures:
# Healthy machine: clean sinusoid
# Bearing fault: damped oscillations (impact response)
# Imbalance: dominant 1x RPM
# The discovered FORM is the diagnosis
result = regressor.discover(t, vibration, mode=DiscoveryMode.OSCILLATOR)
if "exp(-" in result.best_tradeoff.expression_string:
print("Bearing fault detected - damped oscillation signature")
Architecture
ppf/
├── __main__.py # Entry point for python -m ppf
├── cli/ # Command-line interface
│ ├── main.py # Main dispatcher, argparse setup
│ ├── utils.py # Data loading, output formatting
│ └── commands/ # Individual command modules
│ ├── discover.py # ppf discover
│ ├── detect.py # ppf detect
│ ├── stack.py # ppf stack
│ ├── hierarchy.py # ppf hierarchy
│ ├── hybrid.py # ppf hybrid
│ ├── export.py # ppf export (python/c/json)
│ ├── features.py # ppf features
│ └── info.py # ppf info
├── symbolic_types.py # Expression trees, macros, primitives
├── symbolic.py # GP engine, symbolic regressor
├── symbolic_utils.py # Printing, simplification
├── detector.py # Fixed-form fitting (legacy)
├── residual_layer.py # Multi-layer decomposition
├── hierarchical.py # Windowed parameter evolution
├── hybrid.py # EMD/SSA + PPF interpretation
├── export/
│ ├── python_export.py # export_python()
│ ├── c_export.py # export_c()
│ ├── json_export.py # export_json()
│ └── load.py # load_json()
└── features/
├── extract.py # extract_features()
└── vectorize.py # feature_vector()
Benchmarks
Performance on standard symbolic regression benchmarks:
| Benchmark | PPF R² | Complexity | Notes |
|---|---|---|---|
| Kepler's 3rd Law | 0.9999 | 3 | T² = a³ |
| Damped Oscillator | 0.9847 | 8 | Macro template |
| Logistic Growth | 0.9923 | 5 | Sigmoid macro |
| Nguyen-1 (x³+x²+x) | 0.9998 | 7 | Polynomial mode |
See BENCHMARK_COMPARISON.md for detailed results.
License
MIT License - see LICENSE for details.
Copyright (c) 2026 Edward Chalk (fleetingswallow.com)
Citation
If you use PPF in research, please cite:
@software{timeseries-formula-finder,
author = {Chalk, Edward},
title = {Timeseries Formula Finder: Discover Mathematical Forms in Time-Series Data},
year = {2026},
url = {https://github.com/pcoz/timeseries-formula-finder}
}
Contact
- Website: fleetingswallow.com
- Email: edward@fleetingswallow.com
Metadata
Release files for timeseries-formula-finder 0.2.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| timeseries_formula_finder-0.2.1.tar.gz | 172.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| timeseries_formula_finder-0.2.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 271.1 kB
Release files / timeseries_formula_finder-0.2.1.tar.gz
| Download URL | timeseries_formula_finder-0.2.1.tar.gz |
|---|---|
| Size | 172.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
14a8bc125f21f33d6125caadeb9f8d2a6f9f4cb1a3a5127dc1dfb13e4c4a1c49
|
|
BLAKE2b-256 checksum How to use checksums |
a7005f59680c7ad45931ac3d506db6f3d41c3c939dacee742eac09443833ee3c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.
Transparency logRelease files / timeseries_formula_finder-0.2.1-py3-none-any.whl
| Download URL | timeseries_formula_finder-0.2.1-py3-none-any.whl |
|---|---|
| Size | 98.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a6d39de8b72c270015a3aedf83001afc9186ebe2147de6ee7c90ce32944aa29c
|
|
BLAKE2b-256 checksum How to use checksums |
d23ff539b8a9e8558f2be13d961a1a80b0b4a66074fc02acabd60344f434fc3b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.
Transparency log