diff --git a/examples/rust/fft/Cargo.toml b/examples/rust/fft/Cargo.toml new file mode 100644 index 0000000..c7cb089 --- /dev/null +++ b/examples/rust/fft/Cargo.toml @@ -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" + diff --git a/examples/rust/fft/Makefile b/examples/rust/fft/Makefile new file mode 100644 index 0000000..f511fd1 --- /dev/null +++ b/examples/rust/fft/Makefile @@ -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 + + diff --git a/examples/rust/fft/README.md b/examples/rust/fft/README.md new file mode 100644 index 0000000..c4e7a60 --- /dev/null +++ b/examples/rust/fft/README.md @@ -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])` +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 +buffer.len() < len +``` + +### `st_planner_inverse(len: u8, buffer: [Complex])` +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 <) -> list +st-process-inverse: func(l: u8, buf: list) -> list +st-scalar-process-forward: func(l: u8, buf: list) -> list +st-scalar-process-inverse: func(l: u8, buf: list) -> list diff --git a/examples/rust/fft/src/lib.rs b/examples/rust/fft/src/lib.rs new file mode 100644 index 0000000..837f3a0 --- /dev/null +++ b/examples/rust/fft/src/lib.rs @@ -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) -> Vec { + list_num_complex.iter().map(|x| Stcomplex64 { + re: x.re, + im: x.im + }).collect::>() +} + +fn from_st_complex(list_st_complex: Vec) -> Vec { + list_st_complex.iter().map(|x| Complex64::new(x.re, x.im)).collect::>() +} + +impl crate::fft::Fft for Fft { + fn st_process_forward(l: u8, buf: Vec) -> Vec { + 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) -> Vec { + 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) -> Vec { + 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) -> Vec { + 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) + } +} diff --git a/examples/rust/fft/test/main.rs b/examples/rust/fft/test/main.rs new file mode 100644 index 0000000..bfdc3f0 --- /dev/null +++ b/examples/rust/fft/test/main.rs @@ -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 for Complex64Def { + fn from(def: Complex64) -> Complex64Def { + Complex64Def { re : def.re, im : def.im } + } +} + +fn from_num_complex(list_num_complex: Vec) -> Vec { + list_num_complex.iter().map(|x| Complex64Def { + re: x.re, + im: x.im + }).collect::>() +} + +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) { + 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) { + 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()); +} +