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
20 changes: 20 additions & 0 deletions examples/rust/fft/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
name = "fft"
version = "0.1.0"
edition = "2021"

[dependencies]
wit-bindgen-rust = { git = "https://github.com/bytecodealliance/wit-bindgen.git", rev = "60e3c5b41e616fee239304d92128e117dd9be0a7" }
rustfft = "6.1.0"
num-complex = "0.4.3"
serde_json = "1.0.97"
serde = { version = "1.0.164", features = ["derive"] }

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


[[bin]]
name = "test"
path = "test/main.rs"

52 changes: 52 additions & 0 deletions examples/rust/fft/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
MODULE := fft

.PHONY: debug
debug: $(eval TGT:=debug)
debug: wasm

.PHONY: release
release: $(eval TGT:=release)
release: RELFLAGS = --release
release: wasm

.PHONY: wasm
wasm:
cargo wasi build --lib $(RELFLAGS)

.PHONY: test
test: debug
writ-docker --verbose \
-e '[{"re":1.0,"im":2.0}]' \
--wit $(MODULE).wit target/wasm32-wasi/debug/$(MODULE).wasm st-process-forward \
1 '[{"re":1.0,"im":2.0}]'
@echo PASS
writ-docker --verbose \
-e '[{"re":1.0,"im":2.0}]' \
--wit $(MODULE).wit target/wasm32-wasi/debug/$(MODULE).wasm st-process-inverse \
1 '[{"re":1.0,"im":2.0}]'
@echo PASS
writ-docker --verbose \
-e '[{"re":1.0,"im":2.0}]' \
--wit $(MODULE).wit target/wasm32-wasi/debug/$(MODULE).wasm st-scalar-process-forward \
1 '[{"re":1.0,"im":2.0}]'
@echo PASS
writ-docker --verbose \
-e '[{"re":1.0,"im":2.0}]' \
--wit $(MODULE).wit target/wasm32-wasi/debug/$(MODULE).wasm st-scalar-process-inverse \
1 '[{"re":1.0,"im":2.0}]'
@echo PASS
writ-docker --verbose \
-e '[{"re":2.5,"im":4.5},{"re":-0.5,"im":-0.5}]' \
--wit $(MODULE).wit target/wasm32-wasi/debug/$(MODULE).wasm st-process-forward \
2 '[{"re":1.0,"im":2.0},{"re":1.5,"im":2.5}]'
@echo PASS
writ-docker --verbose \
-e '[{"re":2.5,"im":4.5},{"re":-0.5,"im":-0.5}]' \
--wit $(MODULE).wit target/wasm32-wasi/debug/$(MODULE).wasm st-process-inverse \
2 '[{"re":1.0,"im":2.0},{"re":1.5,"im":2.5}]'
@echo PASS
.PHONY: clean
clean:
@cargo clean


95 changes: 95 additions & 0 deletions examples/rust/fft/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Fast Fourier Transform in SingleStoreDB

## Introduction

[Fast Fourier transform](https://en.wikipedia.org/wiki/Fast_Fourier_transform) is an algorithm that computes the discrete Fourier transform (DFT) of a sequence, or its inverse (IDFT).
Fourier analysis converts a signal from its original domain (often time or space) to a representation in the frequency domain and vice versa. The DFT is obtained by decomposing a sequence of values into components of different frequencies.
Fast Fourier transforms are widely used for applications in engineering, music, science, and mathematics. The FFT is used in digital recording, sampling, additive synthesis and pitch correction software.

This library uses [rustfft](https://docs.rs/rustfft/latest/rustfft/) for algorithm implementation. This is a high-performance, SIMD-accelerated FFT library, compute FFTs of any size, in O(nlogn) time. Support for hardware acceleration (Avx, Neon, Sse) from rustfft is not ported to this module.

## Contents
This library provides the following database objects.

### `st_planner_forward(len: u8, buffer: [Complex<f64>])`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use the database types here. So, for this case it would be:

st_planner_forward(len TINYINT UNSIGNED NOT NULL, buffer RECORD(re DOUBLE NOT NULL, im DOUBLE NOT NULL)

This is a bit long for the heading line, so maybe just put st_planner_forward in the heading and then put the UDF signature in the description.

This is a TVF that will create a a new FFT algorthim instance for computing forward FFT of size `len`. Divides the `buffer` into chunks of size `len`, and computes FFT forward on each chunk.
This method will panic (on Rust side) if:
```
buffer.len() % len > 0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using database operators, this would be:

LENGTH(buffer) MOD len > 0

buffer.len() < len
```

### `st_planner_inverse(len: u8, buffer: [Complex<f64>])`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comments as above in favor of using database syntax.

This is a TVF that will create a a new FFT algorthim instance for computing inverse FFT of size `len`. Divides the `buffer` into chunks of size `len`, and computes FFT inverse on each chunk.
This method will panic (on Rust side) if:
```
buffer.len() % len > 0
buffer.len() < len
```

## Building
The Wasm module can be built using the following commands. The build requires Rust with the WASI extension.
```bash
# Install the WASI cargo extension.
cargo install cargo-wasi

# Compile the Wasm module.
cargo wasi build --release
```
The binary will be placed in `target/wasm32-wasi/release/fft.wasm`.

## Deployment to SingleStoreDB

To install these functions using the MySQL client, use the following commands. This command assumes you have built the Wasm module and your current directory is the root of this Git repo. Replace `$DBUSER`, `$DBHOST`, `$DBPORT`, and `$DBNAME` with, respectively, your database username, hostname, port, and the name of the database where you want to deploy the functions.
```bash
cat <<EOF | mysql -u $DBUSER -h $DBHOST -P $DBPORT -D $DBNAME -p
CREATE FUNCTION st_process_forward RETURNS TABLE AS WASM FROM LOCAL INFILE "target/wasm32-wasi/release/fft.wasm" WITH WIT FROM LOCAL INFILE "fft.wit";
CREATE FUNCTION st_process_inverse RETURNS TABLE AS WASM FROM LOCAL INFILE "target/wasm32-wasi/release/fft.wasm" WITH WIT FROM LOCAL INFILE "fft.wit";
```

Alternatively, you can install these functions using [pushwasm](https://github.com/singlestore-labs/pushwasm) with the following command lines. As above, be sure to substitute the environment variables with values of your own.
```bash
pushwasm tvf --force --prompt --name st_process_forward \
--wasm target/wasm32-wasi/release/fft.wasm \
--wit fft.wit \
--abi canonical \
--conn "mysql://$DBUSER@$DBHOST:$DBPORT/$DBNAME"
pushwasm tvf --force --prompt --name st_process_inverse \
--wasm target/wasm32-wasi/release/fft.wasm \
--wit fft.wit \
--abi canonical \
--conn "mysql://$DBUSER@$DBHOST:$DBPORT/$DBNAME"
```

## Usage
The following is a simple example that creates two tables with a columns of strings. The first table's column is used to generate a Bloom Filter, which we store in a User Defined Variable. We then run the Bloom Filter on the strings in the second table.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you forgot to delete this paragraph :-)

The following is a simple example that performs forward FFT on a vector `buffer` of two complex numbers `{"re": 1.0, "im": 2.5}` and `{"re": 2.0, "im": 2.5}`. This will divides the vector `buffer` into chunks of size `1` and computes a FFT on each chunk.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

divides -> divide
computes -> compute

```sql
SELECT * FROM (st_process_forward(1, [ROW(1.0, 2.5), ROW(2.0, 2.5)]));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The outer parens aren't needed here.

```

This should produce the following output:
```console
+----+-----+
| re | im |
+----+-----+
| 1 | 2.5 |
| 2 | 2.5 |
+----+-----+
2 rows in set (0.004 sec)
```

## Additional Information

To learn about the process of developing a Wasm UDF or TVF in more detail, please have a look at our [tutorial](https://singlestore-labs.github.io/singlestore-wasm-toolkit/html/Tutorial-Overview.html).

The SingleStoreDB Wasm UDF/TVF documentation is [here](https://docs.singlestore.com/managed-service/en/reference/code-engine---powered-by-wasm.html).

## Resources

* [Fast Fourier Transform](https://en.wikipedia.org/wiki/Fast_Fourier_transform)
* [Rust FFT library](https://docs.rs/rustfft/latest/rustfft/)
* [Documentation](https://docs.singlestore.com)
* [Twitter](https://twitter.com/SingleStoreDevs)
* [SingleStore forums](https://www.singlestore.com/forum)
* [SingleStoreDB extension template for C++](https://github.com/singlestore-labs/singlestoredb-extension-cpp-template)
9 changes: 9 additions & 0 deletions examples/rust/fft/fft.wit
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
record stcomplex64 {
re: float64,
im: float64
}

st-process-forward: func(l: u8, buf: list<stcomplex64>) -> list<stcomplex64>
st-process-inverse: func(l: u8, buf: list<stcomplex64>) -> list<stcomplex64>
st-scalar-process-forward: func(l: u8, buf: list<stcomplex64>) -> list<stcomplex64>
st-scalar-process-inverse: func(l: u8, buf: list<stcomplex64>) -> list<stcomplex64>
48 changes: 48 additions & 0 deletions examples/rust/fft/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
use rustfft::{FftPlanner, FftPlannerScalar, num_complex::Complex64};
use fft::{Stcomplex64};

wit_bindgen_rust::export!("fft.wit");

struct Fft;

fn from_num_complex(list_num_complex: Vec<Complex64>) -> Vec<Stcomplex64> {
list_num_complex.iter().map(|x| Stcomplex64 {
re: x.re,
im: x.im
}).collect::<Vec<Stcomplex64>>()
}

fn from_st_complex(list_st_complex: Vec<Stcomplex64>) -> Vec<Complex64> {
list_st_complex.iter().map(|x| Complex64::new(x.re, x.im)).collect::<Vec<Complex64>>()
}

impl crate::fft::Fft for Fft {
fn st_process_forward(l: u8, buf: Vec<Stcomplex64>) -> Vec<Stcomplex64> {
let mut planner = FftPlanner::new();
let fft = planner.plan_fft_forward(usize::from(l));
let mut buffer = from_st_complex(buf);
fft.process(&mut buffer);
from_num_complex(buffer)
}
fn st_process_inverse(l: u8, buf: Vec<Stcomplex64>) -> Vec<Stcomplex64> {
let mut planner = FftPlanner::new();
let fft = planner.plan_fft_inverse(usize::from(l));
let mut buffer = from_st_complex(buf);
fft.process(&mut buffer);
from_num_complex(buffer)
}
fn st_scalar_process_forward(l: u8, buf: Vec<Stcomplex64>) -> Vec<Stcomplex64> {
let mut planner = FftPlannerScalar::new();
let fft = planner.plan_fft_forward(usize::from(l));
let mut buffer = from_st_complex(buf);
fft.process(&mut buffer);
from_num_complex(buffer)
}
fn st_scalar_process_inverse(l: u8, buf: Vec<Stcomplex64>) -> Vec<Stcomplex64> {
let mut planner = FftPlannerScalar::new();
let fft = planner.plan_fft_inverse(usize::from(l));
let mut buffer = from_st_complex(buf);
fft.process(&mut buffer);
from_num_complex(buffer)
}
}
54 changes: 54 additions & 0 deletions examples/rust/fft/test/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Generate json testing input / output for make test
use rustfft::{FftPlanner};
use num_complex::{Complex64};
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
pub struct Complex64Def {
pub re: f64,
pub im: f64,
}

impl From<Complex64> for Complex64Def {
fn from(def: Complex64) -> Complex64Def {
Complex64Def { re : def.re, im : def.im }
}
}

fn from_num_complex(list_num_complex: Vec<Complex64>) -> Vec<Complex64Def> {
list_num_complex.iter().map(|x| Complex64Def {
re: x.re,
im: x.im
}).collect::<Vec<Complex64Def>>()
}

fn print_json_debug<'a, T: Serialize + Deserialize<'a>>(type_with_serialize: T) {
let json_buffer = serde_json::to_string(&type_with_serialize).unwrap();
println!("{}", json_buffer);
}

fn test_forward(l: u8, mut buffer: Vec<Complex64>) {
let mut planner = FftPlanner::new();
let fft = planner.plan_fft_forward(usize::from(l));
fft.process(&mut buffer);
print_json_debug(from_num_complex(buffer));
}

fn test_inverse(l: u8, mut buffer: Vec<Complex64>) {
let mut planner = FftPlanner::new();
let fft = planner.plan_fft_inverse(usize::from(l));
fft.process(&mut buffer);
print_json_debug(from_num_complex(buffer));
}

fn main() {
//test_forward(1, vec![Complex64{ re: 1.0, im: 2.0 }; 1]);
//test_inverse(1, vec![Complex64{ re: 1.0, im: 2.0 }; 1]);
let vtor_input = vec![Complex64{ re: 1.0, im: 2.0 }, Complex64{ re: 1.5, im: 2.5 }];
let json_input = serde_json::to_string(&from_num_complex(vtor_input.clone())).unwrap();
println!("{}", json_input);
test_forward(1, vtor_input.clone());
test_forward(2, vtor_input.clone());
test_inverse(2, vtor_input.clone());
}