diff --git a/.gitignore b/.gitignore index 494af1a..a17badf 100644 --- a/.gitignore +++ b/.gitignore @@ -135,3 +135,18 @@ dmypy.json # pixi environments .pixi + +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..578246f --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "ec-tools" +version = "0.1.0" +edition = "2024" + +# [package.metadata.maturin] +# name = "ec_tools.semi_integration_rs" + +[dependencies] +pyo3 = { version = "^0.24", features = ["extension-module"] } +numpy = "^0.24" + +[lib] +name = "ec_tools" +crate-type = ["cdylib"] diff --git a/ec_tools/helper.py b/ec_tools/helper.py index 5f27972..58392f8 100644 --- a/ec_tools/helper.py +++ b/ec_tools/helper.py @@ -69,6 +69,31 @@ def determine_scan_rate(t, x): return np.abs(np.diff(x) / np.diff(t)).mean() +def find_extrema_indeces(y, mode="all"): + """Return the indexes of limits of cyclic voltammetry. + + Workaround for Windows platform: + list(array([...], dtype=int64)) + TEST: + >>> E = np.array([0.1, 0.11, 0.12, 0.13, 0.14, 0.15, 0.14, 0.13, 0.12, 0.11, 0.10, 0.09, 0.10, 0.11, 0.12, 0.13, 0.14]) + >>> list(find_extrema_indeces(E)) + [5, 11] + + >>> list(find_extrema_indeces(E, mode="pos")) + [5] + """ + signs = np.diff(np.sign(np.diff(y))) + + if mode == "all": + extrema = np.where((signs == 2) | (signs == -2))[0] + elif mode == "pos": + extrema = np.where(signs == -2)[0] + elif mode == "neg": + extrema = np.where(signs == 2)[0] + + return extrema + 1 + + def detect_step(t, x): """Returns the index of the step in given t and x arrays. Index is the where the changed value of t located. diff --git a/ec_tools/semi_integration_rs/__init__.py b/ec_tools/semi_integration_rs/__init__.py new file mode 100644 index 0000000..8e8bccf --- /dev/null +++ b/ec_tools/semi_integration_rs/__init__.py @@ -0,0 +1 @@ +from ec_tools.semi_integration_rs import * diff --git a/ec_tools/semi_integration_rs/__init__.pyi b/ec_tools/semi_integration_rs/__init__.pyi new file mode 100644 index 0000000..e29e6fe --- /dev/null +++ b/ec_tools/semi_integration_rs/__init__.pyi @@ -0,0 +1,4 @@ +def semi_integration_rs(param1: str, param2: int) -> int: + """ + Function description + """ diff --git a/pixi.lock b/pixi.lock index 40b75bf..afd00b5 100644 --- a/pixi.lock +++ b/pixi.lock @@ -6986,6 +6986,8 @@ packages: - liblzma >=5.8.1,<6.0a0 - libxml2-16 2.15.1 ha9997c6_0 - libzlib >=1.3.1,<2.0a0 + constrains: + - icu <0.0a0 license: MIT license_family: MIT purls: [] @@ -8521,6 +8523,8 @@ packages: - vc14_runtime >=14.44.35208 constrains: - libopenblas !=0.3.6 + - tbb >=2021.6.0 + - cuda-version >=11.2 - cudatoolkit >=11.2 - tbb >=2021.6.0 - scipy >=1.0 @@ -8568,7 +8572,6 @@ packages: constrains: - numpy-base <0a0 license: BSD-3-Clause - license_family: BSD purls: - pkg:pypi/numpy?source=hash-mapping size: 9451141 @@ -8589,7 +8592,6 @@ packages: constrains: - numpy-base <0a0 license: BSD-3-Clause - license_family: BSD purls: - pkg:pypi/numpy?source=compressed-mapping size: 8820654 @@ -8610,7 +8612,6 @@ packages: constrains: - numpy-base <0a0 license: BSD-3-Clause - license_family: BSD purls: - pkg:pypi/numpy?source=hash-mapping size: 8919466 @@ -8651,7 +8652,6 @@ packages: constrains: - numpy-base <0a0 license: BSD-3-Clause - license_family: BSD purls: - pkg:pypi/numpy?source=hash-mapping size: 6556655 @@ -8754,7 +8754,6 @@ packages: constrains: - numpy-base <0a0 license: BSD-3-Clause - license_family: BSD purls: - pkg:pypi/numpy?source=hash-mapping size: 6988856 @@ -8773,7 +8772,6 @@ packages: constrains: - numpy-base <0a0 license: BSD-3-Clause - license_family: BSD purls: - pkg:pypi/numpy?source=hash-mapping size: 8596533 @@ -8792,7 +8790,6 @@ packages: constrains: - numpy-base <0a0 license: BSD-3-Clause - license_family: BSD purls: - pkg:pypi/numpy?source=hash-mapping size: 7992092 @@ -8811,7 +8808,6 @@ packages: constrains: - numpy-base <0a0 license: BSD-3-Clause - license_family: BSD purls: - pkg:pypi/numpy?source=hash-mapping size: 8082659 @@ -8950,7 +8946,6 @@ packages: constrains: - numpy-base <0a0 license: BSD-3-Clause - license_family: BSD purls: - pkg:pypi/numpy?source=hash-mapping size: 6596153 @@ -8973,7 +8968,6 @@ packages: constrains: - numpy-base <0a0 license: BSD-3-Clause - license_family: BSD purls: - pkg:pypi/numpy?source=compressed-mapping size: 8074590 @@ -8996,7 +8990,6 @@ packages: constrains: - numpy-base <0a0 license: BSD-3-Clause - license_family: BSD purls: - pkg:pypi/numpy?source=compressed-mapping size: 7438208 @@ -9019,7 +9012,6 @@ packages: constrains: - numpy-base <0a0 license: BSD-3-Clause - license_family: BSD purls: - pkg:pypi/numpy?source=hash-mapping size: 7522467 @@ -9112,7 +9104,6 @@ packages: - python >=3.8 - python license: Apache-2.0 - license_family: APACHE purls: - pkg:pypi/packaging?source=hash-mapping size: 62477 @@ -9137,7 +9128,7 @@ packages: - python >=3.9 license: ISC purls: - - pkg:pypi/pexpect?source=hash-mapping + - pkg:pypi/pexpect?source=compressed-mapping size: 53561 timestamp: 1733302019362 - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.3-pyh145f28c_0.conda @@ -12608,7 +12599,6 @@ packages: - libcxx >=18 - __osx >=10.13 license: MIT - license_family: MIT purls: [] size: 145204 timestamp: 1745308032698 @@ -12619,7 +12609,6 @@ packages: - libcxx >=18 - __osx >=11.0 license: MIT - license_family: MIT purls: [] size: 136222 timestamp: 1745308075886 @@ -12634,7 +12623,6 @@ packages: - vc14_runtime >=14.29.30139 - ucrt >=10.0.20348.0 license: MIT - license_family: MIT purls: [] size: 148572 timestamp: 1745308037198 diff --git a/pyproject.toml b/pyproject.toml index da2053b..4dbc174 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [build-system] -requires = ["setuptools"] -build-backend = "setuptools.build_meta" +requires = ["maturin>=1.5,<2.0"] +build-backend = "maturin" [project] name = "ec-tools" @@ -10,6 +10,8 @@ readme = "README.md" requires-python = ">=3.10.0" dependencies = [ "numpy>=2.0.3,<3", "transonic>=0.7.3,<0.9" ] version = "0.1.0" +[tool.maturin] +features = ["pyo3/extension-module"] [tool.setuptools] packages = ["ec_tools",] diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..1c29370 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,10 @@ +// use pyo3::prelude::*; + +mod semi_integration_rs; + +// #[pymodule] +// fn _rs(_py: Python, m: &PyModule) -> PyResult<()> { +// semi_integration::init_linalg(_py, m)?; + +// Ok(()) +// } diff --git a/src/semi_integration_rs/mod.rs b/src/semi_integration_rs/mod.rs new file mode 100644 index 0000000..2e4aba7 --- /dev/null +++ b/src/semi_integration_rs/mod.rs @@ -0,0 +1,79 @@ +use pyo3::{pymodule, types::PyModule, PyResult, Python}; +use numpy::ndarray::{Array1, ArrayView1}; +use numpy::{IntoPyArray, PyReadonlyArray1, PyArray1}; + +use std::f64::consts::PI; + +#[pymodule] +fn semi_integration_rs(_py: Python<'_>, m: &PyModule) -> PyResult<()> { + fn prepare_kernel(q: f64, Δx: f64, N: u64, c1: u64, c2: u64) -> (Array1, Array1, Array1) { + let length = N as f64; + let τ0 = Δx * length.powf(0.5); + let a0 = (PI * q).sin() / (PI *q * τ0.powf(q)); + // total number of filters + let n_filters = 2 * c1 * c2 + 1; + // dimension according to the number of filters + // filter weights + let mut w1 = Array1::::zeros(n_filters as usize); + let mut w2 = Array1::::zeros(n_filters as usize); + // auxiliary array + let s = Array1::::zeros(n_filters as usize); + + for i in 0..2*c1 as usize *c2 as usize { + let j:f64 = i as f64 - c1 as f64 * c2 as f64; + let a_j = (a0 / c2 as f64) * f64::exp(j as f64 / c2 as f64); + let t_j = τ0 * f64::exp(-j as f64 / (q*c2 as f64) ); + w1[i] = t_j / (Δx + t_j); + w2[i] = a_j * (1f64 - w1[i])}; + (s, w1, w2) + } + + fn semi_integration(y: ArrayView1<'_, f64>, q: Option, Δx: Option, c1: Option, c2: Option) -> Array1 { + let N = y.len(); + let mut R = Array1::::zeros(N); + let ( mut s, w1, w2) = prepare_kernel( + q.unwrap_or( -0.5), + Δx.unwrap_or(1.0), + N as u64, + c1.unwrap_or(2u64), + c2.unwrap_or(8u64) + ); + for k in 1..N { + for i in 0..s.len() { + s[i] = s[i]*w1[i] + &y[k]*w2[i]; + R[k] = R[k] + s[i]; + } + } + R + } +// wrapper of `semi_integration_rs` +#[pyfn(m)] +#[pyo3(name = "semi_integration")] +fn semi_integration_py<'py>( + py: Python<'py>, + y: PyReadonlyArray1, + q: Option, + Δx: Option, + c1: Option, + c2: Option, +) -> &'py PyArray1 { + semi_integration(y.as_array(), q, Δx, c1, c2).into_pyarray(py) + // Array1::::zeros(2).into_pyarray(py) +} + +// wrapper of `prepare_kernel_rs` +#[pyfn(m)] +#[pyo3(name = "prepare_kernel")] +fn prepare_kernel_py<'py>( + py: Python<'py>, + q: f64, + Δx: f64, + N: u64, + c1: u64, + c2: u64, +) -> (&'py PyArray1, &'py PyArray1, &'py PyArray1) { + let (s , w1, w2) = prepare_kernel(q, Δx, N as u64, c1, c2); + (s.into_pyarray(py) , w1.into_pyarray(py), w2.into_pyarray(py)) +} + Ok(()) +} \ No newline at end of file