Skip to content
Open
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
94 changes: 94 additions & 0 deletions .github/workflows/ci-python-wrapper.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
name: CI (Python wrapper)

on:
pull_request:
branches:
- main
paths:
- symbolic_regression_py/**
- symbolic_regression/**
- dynamic_expressions/**
- .cargo/**
- Cargo.toml
- .github/workflows/ci-python-wrapper.yml
push:
branches:
- main
paths:
- symbolic_regression_py/**
- symbolic_regression/**
- dynamic_expressions/**
- .cargo/**
- Cargo.toml
- .github/workflows/ci-python-wrapper.yml
workflow_dispatch:

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

env:
CARGO_TERM_COLOR: always

jobs:
python-wrapper:
name: Python wrapper
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.9", "3.12", "3.13"]
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Install Rust
uses: actions-rust-lang/setup-rust-toolchain@v1

- name: Rust cache
uses: Swatinem/rust-cache@v2

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install uv
uses: astral-sh/setup-uv@v5

- name: Install wrapper
run: uv pip install --system -e symbolic_regression_py

- name: Run Python wrapper tests
run: python -m unittest discover symbolic_regression_py/tests

- name: Build Python wrapper wheel
working-directory: symbolic_regression_py
run: uvx maturin build --out ../dist

- name: Test built wheel
working-directory: symbolic_regression_py
run: |
python -m venv "$RUNNER_TEMP/pysr-rust-backend-wheel"
. "$RUNNER_TEMP/pysr-rust-backend-wheel/bin/activate"
python -m pip install --upgrade pip
python -m pip install ../dist/*.whl
python -c "import numpy as np, symbolic_regression_rs; X=np.linspace(-1,1,24,dtype=np.float32).reshape(-1,1); y=X[:,0]; out=symbolic_regression_rs.search(X,y,options={'seed':0,'niterations':1,'populations':1,'population_size':16,'ncycles_per_iteration':20,'maxsize':10,'maxdepth':10,'deterministic':True,'progress':False},operators={2:['+','sub','*','/']},variable_names=['x0']); assert out['hall_of_fame']"

- name: Build source distribution
working-directory: symbolic_regression_py
run: uvx maturin sdist --out ../dist

- name: Test source distribution
working-directory: symbolic_regression_py
run: |
python -m venv "$RUNNER_TEMP/pysr-rust-backend-sdist"
. "$RUNNER_TEMP/pysr-rust-backend-sdist/bin/activate"
python -m pip install --upgrade pip
python -m pip install ../dist/*.tar.gz
python -c "import numpy as np, symbolic_regression_rs; X=np.linspace(-1,1,24,dtype=np.float32).reshape(-1,1); y=X[:,0]; out=symbolic_regression_rs.search(X,y,options={'seed':0,'niterations':1,'populations':1,'population_size':16,'ncycles_per_iteration':20,'maxsize':10,'maxdepth':10,'deterministic':True,'progress':False},operators={2:['+','sub','*','/']},variable_names=['x0']); assert out['hall_of_fame']"

- name: cargo check
run: cargo check -p symbolic_regression_rs_py

- name: cargo fmt --check
run: cargo fmt --check -p symbolic_regression_rs_py
179 changes: 179 additions & 0 deletions .github/workflows/publish-python-wrapper.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
name: Publish Python wrapper

on:
workflow_dispatch:
inputs:
publish_ref:
description: Tag, branch, or commit SHA to publish
required: true
type: string
repository:
description: Package index to publish to
required: true
default: testpypi
type: choice
options:
- testpypi
- pypi

permissions:
contents: read

jobs:
build-linux-wheels:
name: Build Linux wheels
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ inputs.publish_ref }}

- name: Install Rust
uses: actions-rust-lang/setup-rust-toolchain@v1

- name: Rust cache
uses: Swatinem/rust-cache@v2

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Build wheel
uses: PyO3/maturin-action@v1
with:
working-directory: symbolic_regression_py
command: build
args: --release -i python${{ matrix.python-version }} --out ../dist
manylinux: auto

- name: Test built wheel
run: |
python -m pip install --upgrade pip
python -c "import glob, subprocess, sys; wheels=glob.glob('dist/*.whl'); assert len(wheels) == 1, wheels; subprocess.check_call([sys.executable, '-m', 'pip', 'install', wheels[0]])"
python -c "import numpy as np, symbolic_regression_rs; X=np.linspace(-1,1,24,dtype=np.float32).reshape(-1,1); y=X[:,0]; out=symbolic_regression_rs.search(X,y,options={'seed':0,'niterations':1,'populations':1,'population_size':16,'ncycles_per_iteration':20,'maxsize':10,'maxdepth':10,'deterministic':True,'progress':False},operators={2:['+','sub','*','/']},variable_names=['x0']); assert out['hall_of_fame']"

- name: Upload wheel
uses: actions/upload-artifact@v4
with:
name: pysr-rust-backend-linux-wheel-${{ matrix.python-version }}
path: dist/*.whl
if-no-files-found: error

build-host-wheels:
name: Build ${{ matrix.os }} wheels
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [windows-latest, macos-13, macos-latest]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ inputs.publish_ref }}

- name: Install Rust
uses: actions-rust-lang/setup-rust-toolchain@v1

- name: Rust cache
uses: Swatinem/rust-cache@v2

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Build wheel
uses: PyO3/maturin-action@v1
with:
working-directory: symbolic_regression_py
command: build
args: --release --out ../dist
manylinux: off

- name: Test built wheel
run: |
python -m pip install --upgrade pip
python -c "import glob, subprocess, sys; wheels=glob.glob('dist/*.whl'); assert len(wheels) == 1, wheels; subprocess.check_call([sys.executable, '-m', 'pip', 'install', wheels[0]])"
python -c "import numpy as np, symbolic_regression_rs; X=np.linspace(-1,1,24,dtype=np.float32).reshape(-1,1); y=X[:,0]; out=symbolic_regression_rs.search(X,y,options={'seed':0,'niterations':1,'populations':1,'population_size':16,'ncycles_per_iteration':20,'maxsize':10,'maxdepth':10,'deterministic':True,'progress':False},operators={2:['+','sub','*','/']},variable_names=['x0']); assert out['hall_of_fame']"

- name: Upload wheel
uses: actions/upload-artifact@v4
with:
name: pysr-rust-backend-${{ matrix.os }}-wheel-${{ matrix.python-version }}
path: dist/*.whl
if-no-files-found: error

build-sdist:
name: Build source distribution
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ inputs.publish_ref }}

- name: Install Rust
uses: actions-rust-lang/setup-rust-toolchain@v1

- name: Rust cache
uses: Swatinem/rust-cache@v2

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Build source distribution
working-directory: symbolic_regression_py
run: |
python -m pip install --upgrade pip
python -m pip install "maturin>=1.8,<2"
maturin sdist --out ../dist

- name: Upload source distribution
uses: actions/upload-artifact@v4
with:
name: pysr-rust-backend-sdist
path: dist/*.tar.gz
if-no-files-found: error

publish:
name: Publish distributions
runs-on: ubuntu-latest
needs:
- build-linux-wheels
- build-host-wheels
- build-sdist
environment:
name: ${{ inputs.repository }}
url: ${{ inputs.repository == 'pypi' && 'https://pypi.org/p/pysr-rust-backend' || 'https://test.pypi.org/p/pysr-rust-backend' }}
permissions:
contents: read
id-token: write
steps:
- name: Download distributions
uses: actions/download-artifact@v4
with:
pattern: pysr-rust-backend-*
path: dist
merge-multiple: true

- name: Publish to TestPyPI
if: inputs.repository == 'testpypi'
uses: pypa/gh-action-pypi-publish@release/v1
with:
repository-url: https://test.pypi.org/legacy/
skip-existing: true
verbose: true

- name: Publish to PyPI
if: inputs.repository == 'pypi'
uses: pypa/gh-action-pypi-publish@release/v1
with:
verbose: true
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,7 @@ Cargo.lock
!web_ui/package-lock.json
web_ui/src/generated/defaultCsv.ts
**/.playwright-browsers/**
**/__pycache__/
**/.pytest_cache/
!symbolic_regression_wasm/.cargo/
!symbolic_regression_wasm/.cargo/**
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ resolver = "2"
members = [
"dynamic_expressions",
"symbolic_regression",
"symbolic_regression_py",
"symbolic_regression_wasm",
]

Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@ Try out a fully browser-based demo of WebAssembly-compiled symbolic regression [
> This package is an **experiment**. The API is not stabilized, and you should expect large breaking changes in the syntax.
> This library is not ready for use.

This workspace contains three crates:
This workspace contains three Rust crates and one Python extension package:

| Crate | crates.io | CI |
|---|---|---|
| [`symbolic_regression`](./symbolic_regression) | [![crates.io](https://img.shields.io/crates/v/symbolic_regression)](https://crates.io/crates/symbolic_regression) | [![CI (symbolic_regression)](https://github.com/astro-automata/symbolic_regression.rs/actions/workflows/ci-symbolic-regression.yml/badge.svg?branch=main)](https://github.com/astro-automata/symbolic_regression.rs/actions/workflows/ci-symbolic-regression.yml) |
| [`dynamic_expressions`](./dynamic_expressions) | [![crates.io](https://img.shields.io/crates/v/dynamic_expressions)](https://crates.io/crates/dynamic_expressions) | [![CI (dynamic_expressions)](https://github.com/astro-automata/symbolic_regression.rs/actions/workflows/ci-dynamic-expressions.yml/badge.svg?branch=main)](https://github.com/astro-automata/symbolic_regression.rs/actions/workflows/ci-dynamic-expressions.yml) |
| [`symbolic_regression_wasm`](./symbolic_regression_wasm) | [![crates.io](https://img.shields.io/crates/v/symbolic_regression_wasm)](https://crates.io/crates/symbolic_regression_wasm) | [![CI (Web UI)](https://github.com/astro-automata/symbolic_regression.rs/actions/workflows/ci-web.yml/badge.svg?branch=main)](https://github.com/astro-automata/symbolic_regression.rs/actions/workflows/ci-web.yml) |
| [`symbolic_regression_py`](./symbolic_regression_py) | `pysr-rust-backend` package draft | [![CI (Python wrapper)](https://github.com/astroautomata/symbolic_regression.rs/actions/workflows/ci-python-wrapper.yml/badge.svg?branch=main)](https://github.com/astroautomata/symbolic_regression.rs/actions/workflows/ci-python-wrapper.yml) |

## Low-level API

Expand Down
22 changes: 22 additions & 0 deletions symbolic_regression_py/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[package]
name = "symbolic_regression_rs_py"
version = "0.1.0"
edition = "2021"
license = "Apache-2.0"
description = "Experimental Rust backend package for PySR."
homepage = "https://github.com/astroautomata/symbolic_regression.rs"
repository = "https://github.com/astroautomata/symbolic_regression.rs"
publish = false

[lib]
name = "symbolic_regression_rs"
crate-type = ["cdylib"]

[dependencies]
dynamic_expressions = { version = "0.10.1", path = "../dynamic_expressions", default-features = false }
ndarray = "0.17"
num-traits = "0.2"
numpy = "0.29"
pyo3 = { version = "0.29", features = ["extension-module"] }
rayon = "1"
symbolic_regression = { version = "0.12.2", path = "../symbolic_regression", default-features = false }
Loading