diff --git a/.gitignore b/.gitignore index 15ea4ff8ca03b77ee0d96126258add6476f37c8f..6493e79f68659307ecac5dc213e93c6dbab30cc4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,74 @@ /target /.vscode -/res \ No newline at end of file +/res + +# Byte-compiled / optimized / DLL files +__pycache__/ +.pytest_cache/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +.venv/ +env/ +bin/ +build/ +develop-eggs/ +dist/ +eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +include/ +man/ +venv/ +*.egg-info/ +.installed.cfg +*.egg + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt +pip-selfcheck.json + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.cache +nosetests.xml +coverage.xml + +# Translations +*.mo + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +# Rope +.ropeproject + +# Django stuff: +*.log +*.pot + +.DS_Store + +# Sphinx documentation +docs/_build/ + +# PyCharm +.idea/ + +# VSCode +.vscode/ + +# Pyenv +.python-version diff --git a/Cargo.lock b/Cargo.lock index c48b7990d9429e47daf27c6d08841a7feb56595c..cd391e5fcac2891f35f91b53047564b314ebcdb0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -98,11 +98,23 @@ dependencies = [ "wasi", ] +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "indoc" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" + [[package]] name = "libc" -version = "0.2.162" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "log" @@ -116,6 +128,15 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + [[package]] name = "npyz" version = "0.8.3" @@ -172,9 +193,9 @@ checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "pest" -version = "2.7.14" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" +checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" dependencies = [ "memchr", "thiserror", @@ -183,9 +204,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.14" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d214365f632b123a47fd913301e14c946c61d1c183ee245fa76eb752e59a02dd" +checksum = "816518421cfc6887a0d62bf441b6ffb4536fcc926395a69e1a85852d4363f57e" dependencies = [ "pest", "pest_generator", @@ -193,9 +214,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.14" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb55586734301717aea2ac313f50b2eb8f60d2fc3dc01d190eefa2e625f60c4e" +checksum = "7d1396fd3a870fc7838768d171b4616d5c91f6cc25e377b673d714567d99377b" dependencies = [ "pest", "pest_meta", @@ -206,15 +227,21 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.7.14" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b75da2a70cf4d9cb76833c990ac9cd3923c9a8905a8929789ce347c84564d03d" +checksum = "e1e58089ea25d717bfd31fb534e4f3afcc2cc569c70de3e239778991ea3b7dea" dependencies = [ "once_cell", "pest", "sha2", ] +[[package]] +name = "portable-atomic" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" + [[package]] name = "ppv-lite86" version = "0.2.20" @@ -226,9 +253,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.89" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -238,6 +265,7 @@ name = "proximity-cache" version = "0.1.0" dependencies = [ "npyz", + "pyo3", "quickcheck", "rand", ] @@ -255,6 +283,69 @@ dependencies = [ "pest_derive", ] +[[package]] +name = "pyo3" +version = "0.23.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e484fd2c8b4cb67ab05a318f1fd6fa8f199fcc30819f08f07d200809dba26c15" +dependencies = [ + "cfg-if", + "indoc", + "libc", + "memoffset", + "once_cell", + "portable-atomic", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.23.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc0e0469a84f208e20044b98965e1561028180219e35352a2afaf2b942beff3b" +dependencies = [ + "once_cell", + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.23.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb1547a7f9966f6f1a0f0227564a9945fe36b90da5a93b3933fc3dc03fae372d" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.23.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb6da8ec6fa5cedd1626c886fc8749bdcbb09424a86461eb8cdf096b7c33257" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.23.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38a385202ff5a92791168b1136afae5059d3ac118457bb7bc304c197c2d33e7d" +dependencies = [ + "heck", + "proc-macro2", + "pyo3-build-config", + "quote", + "syn", +] + [[package]] name = "quickcheck" version = "1.0.3" @@ -268,9 +359,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] @@ -319,9 +410,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -347,29 +438,35 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.87" +version = "2.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +checksum = "46f71c0377baf4ef1cc3e3402ded576dccc315800fbc62dfc7fe04b009773b4a" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + [[package]] name = "thiserror" -version = "1.0.69" +version = "2.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.69" +version = "2.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4" dependencies = [ "proc-macro2", "quote", @@ -390,9 +487,15 @@ checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "unindent" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" [[package]] name = "version_check" diff --git a/Cargo.toml b/Cargo.toml index dfbed59a0cc93d145ca6298f461ed69afc7e594b..061865a880705d8901b53952099aff0a13c8752e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,20 +5,23 @@ edition = "2021" authors = ["SaCS laboratory, EPFL. Correspond with mathis[d o t]randl[a t]epfl.ch"] description = "Experiments on approximate vector search in high-dimensional spaces" readme = "README.md" -license-file = "LICENSE" +license = "MIT" repository = "https://gitlab.epfl.ch/randl/proximity" -[dependencies] -npyz = "0.8.3" -rand = "0.8.5" - -[dev-dependencies] -quickcheck = "1.0.3" - +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] -name = "proximitylib" +name = "proximipy" +crate-type = ["cdylib"] path = "src/lib.rs" [[bin]] name = "proximitybin" -path = "src/main.rs" \ No newline at end of file +path = "src/main.rs" + +[dev-dependencies] +quickcheck = "1.0.3" + +[dependencies] +pyo3 = "0.23.3" +npyz = "0.8.3" +rand = "0.8.5" \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000000000000000000000000000000000000..2b35bc6a9117526799582aebf27ac2741b124a3c --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,15 @@ +[build-system] +requires = ["maturin>=1.8,<2.0"] +build-backend = "maturin" + +[project] +name = "proximipylib" +requires-python = ">=3.8" +classifiers = [ + "Programming Language :: Rust", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", +] +dynamic = ["version"] +[tool.maturin] +features = ["pyo3/extension-module"] diff --git a/src/caching/bounded/bounded_linear_cache.rs b/src/caching/bounded/bounded_linear_cache.rs index 3062ad0a608f53ebcc48b44c13dfeae51856d1be..ce8ded8f95e92ee86b5e11b90de131fd514a9b74 100644 --- a/src/caching/bounded/bounded_linear_cache.rs +++ b/src/caching/bounded/bounded_linear_cache.rs @@ -1,6 +1,8 @@ use std::collections::HashMap; use std::hash::Hash; +use pyo3::{pyclass, pymethods}; + use crate::numerics::comp::ApproxComparable; use crate::caching::approximate_cache::ApproximateCache; @@ -43,6 +45,7 @@ use super::list_node::{Node, SharedNode}; /// - `find(&mut self, key: &K) -> Option<V>`: Attempts to find a value matching the given key approximately. Promotes the found key to the head of the list. /// - `insert(&mut self, key: K, value: V)`: Inserts a key-value pair into the cache. Evicts the least recently used item if the cache is full. /// - `len(&self) -> usize`: Returns the current size of the cache. + pub struct BoundedLinearCache<K, V> { max_capacity: usize, map: HashMap<K, SharedNode<K, V>>, @@ -63,13 +66,13 @@ where let node: SharedNode<K, V> = self.map.get(matching).cloned()?; self.list.remove(node.clone()); self.list.add_to_head(node.clone()); - return Some(node.borrow().value.clone()); + return Some(node.try_lock().unwrap().value.clone()); } fn insert(&mut self, key: K, value: V) { if self.len() == self.max_capacity { if let Some(tail) = self.list.remove_tail() { - self.map.remove(&tail.borrow().key); + self.map.remove(&tail.try_lock().unwrap().key); } } let new_node = Node::new(key.clone(), value); @@ -95,6 +98,38 @@ impl<K, V> BoundedLinearCache<K, V> { } } +macro_rules! create_pythonized_interface { + ($name: ident, $type: ident) => { + #[pyclass] + pub struct $name { + inner: BoundedLinearCache::<$type, $type>, + } + #[pymethods] + impl $name { + #[new] + pub fn new(max_capacity: usize, tolerance: f32) -> Self { + Self { + inner: BoundedLinearCache::new(max_capacity, tolerance), + } + } + + fn find(&mut self, k : $type) -> Option<$type> { + self.inner.find(&k) + } + + fn insert(&mut self, key: $type, value: $type) { + self.inner.insert(key, value) + } + + fn len(&self) -> usize { + self.inner.len() + } + } + }; +} + +create_pythonized_interface!(I16Cache, i16); + #[cfg(test)] mod tests { use super::*; diff --git a/src/caching/bounded/linked_list.rs b/src/caching/bounded/linked_list.rs index 513e55ab05600279edf50c5ba2fa2f1b98f8c819..d72423968d4e76de9a87396a1e919e916ba18396 100644 --- a/src/caching/bounded/linked_list.rs +++ b/src/caching/bounded/linked_list.rs @@ -1,4 +1,4 @@ -use std::rc::Rc; +use std::sync::Arc; use crate::caching::bounded::list_node::SharedNode; @@ -16,11 +16,11 @@ impl<K, V> DoublyLinkedList<K, V> { } pub(crate) fn add_to_head(&mut self, node: SharedNode<K, V>) { - node.borrow_mut().next = self.head.clone(); - node.borrow_mut().prev = None; + node.try_lock().unwrap().next = self.head.clone(); + node.try_lock().unwrap().prev = None; if let Some(head) = self.head.clone() { - head.borrow_mut().prev = Some(Rc::downgrade(&node)); + head.try_lock().unwrap().prev = Some(Arc::downgrade(&node)); } self.head = Some(node.clone()); @@ -31,17 +31,17 @@ impl<K, V> DoublyLinkedList<K, V> { } pub(crate) fn remove(&mut self, node: SharedNode<K, V>) { - let prev = node.borrow().prev.clone(); - let next = node.borrow().next.clone(); + let prev = node.try_lock().unwrap().prev.clone(); + let next = node.try_lock().unwrap().next.clone(); if let Some(prev_node) = prev.as_ref().and_then(|weak| weak.upgrade()) { - prev_node.borrow_mut().next = next.clone(); + prev_node.try_lock().unwrap().next = next.clone(); } else { self.head = next.clone(); } if let Some(next_node) = next { - next_node.borrow_mut().prev = prev; + next_node.try_lock().unwrap().prev = prev; } else { self.tail = prev.and_then(|weak| weak.upgrade()); } @@ -68,12 +68,12 @@ mod tests { let node2 = Node::new(2, 20); list.add_to_head(node1.clone()); - assert_eq!(list.head.as_ref().unwrap().borrow().key, 1); - assert_eq!(list.tail.as_ref().unwrap().borrow().key, 1); + assert_eq!(list.head.as_ref().unwrap().try_lock().unwrap().key, 1); + assert_eq!(list.tail.as_ref().unwrap().try_lock().unwrap().key, 1); list.add_to_head(node2.clone()); - assert_eq!(list.head.as_ref().unwrap().borrow().key, 2); - assert_eq!(list.tail.as_ref().unwrap().borrow().key, 1); + assert_eq!(list.head.as_ref().unwrap().try_lock().unwrap().key, 2); + assert_eq!(list.tail.as_ref().unwrap().try_lock().unwrap().key, 1); } #[test] @@ -90,13 +90,13 @@ mod tests { // List is now: {3, 2, 1} list.remove(node2.clone()); // List should now be: {3, 1} - assert_eq!(list.head.as_ref().unwrap().borrow().key, 3); - assert_eq!(list.tail.as_ref().unwrap().borrow().key, 1); + assert_eq!(list.head.as_ref().unwrap().try_lock().unwrap().key, 3); + assert_eq!(list.tail.as_ref().unwrap().try_lock().unwrap().key, 1); list.remove(node3.clone()); // List should now be: {1} - assert_eq!(list.head.as_ref().unwrap().borrow().key, 1); - assert_eq!(list.tail.as_ref().unwrap().borrow().key, 1); + assert_eq!(list.head.as_ref().unwrap().try_lock().unwrap().key, 1); + assert_eq!(list.tail.as_ref().unwrap().try_lock().unwrap().key, 1); list.remove(node1.clone()); // List should now be empty @@ -115,13 +115,13 @@ mod tests { // List is now: {2, 1} let removed_tail = list.remove_tail().unwrap(); - assert_eq!(removed_tail.borrow().key, 1); + assert_eq!(removed_tail.try_lock().unwrap().key, 1); // List should now be: {2} - assert_eq!(list.head.as_ref().unwrap().borrow().key, 2); - assert_eq!(list.tail.as_ref().unwrap().borrow().key, 2); + assert_eq!(list.head.as_ref().unwrap().try_lock().unwrap().key, 2); + assert_eq!(list.tail.as_ref().unwrap().try_lock().unwrap().key, 2); let removed_tail = list.remove_tail().unwrap(); - assert_eq!(removed_tail.borrow().key, 2); + assert_eq!(removed_tail.try_lock().unwrap().key, 2); // List should now be empty assert!(list.head.is_none()); assert!(list.tail.is_none()); @@ -139,17 +139,17 @@ mod tests { list.add_to_head(node3.clone()); // List is now: {3, 2, 1} - assert_eq!(list.head.as_ref().unwrap().borrow().key, 3); - assert_eq!(list.tail.as_ref().unwrap().borrow().key, 1); + assert_eq!(list.head.as_ref().unwrap().try_lock().unwrap().key, 3); + assert_eq!(list.tail.as_ref().unwrap().try_lock().unwrap().key, 1); list.remove(node1.clone()); // List should now be: {3, 2} - assert_eq!(list.head.as_ref().unwrap().borrow().key, 3); - assert_eq!(list.tail.as_ref().unwrap().borrow().key, 2); + assert_eq!(list.head.as_ref().unwrap().try_lock().unwrap().key, 3); + assert_eq!(list.tail.as_ref().unwrap().try_lock().unwrap().key, 2); list.add_to_head(node1.clone()); // List should now be: {1, 3, 2} - assert_eq!(list.head.as_ref().unwrap().borrow().key, 1); - assert_eq!(list.tail.as_ref().unwrap().borrow().key, 2); + assert_eq!(list.head.as_ref().unwrap().try_lock().unwrap().key, 1); + assert_eq!(list.tail.as_ref().unwrap().try_lock().unwrap().key, 2); } } diff --git a/src/caching/bounded/list_node.rs b/src/caching/bounded/list_node.rs index dafe9f6394610c7e95aeba68b225da5d0a6c25e3..2ca9264d8bb8ad376f4d84f22a1a04514b8cdede 100644 --- a/src/caching/bounded/list_node.rs +++ b/src/caching/bounded/list_node.rs @@ -1,10 +1,7 @@ -use std::{ - cell::RefCell, - rc::{Rc, Weak}, -}; +use std::sync::{Arc, Weak, Mutex}; -pub(crate) type SharedNode<K, V> = Rc<RefCell<Node<K, V>>>; -pub(crate) type WeakSharedNode<K, V> = Weak<RefCell<Node<K, V>>>; +pub(crate) type SharedNode<K, V> = Arc<Mutex<Node<K, V>>>; +pub(crate) type WeakSharedNode<K, V> = Weak<Mutex<Node<K, V>>>; #[derive(Debug)] pub struct Node<K, V> { @@ -15,8 +12,8 @@ pub struct Node<K, V> { } impl<K, V> Node<K, V> { - pub fn new(key: K, value: V) -> Rc<RefCell<Self>> { - Rc::new(RefCell::new(Node { + pub fn new(key: K, value: V) -> Arc<Mutex<Self>> { + Arc::new(Mutex::new(Node { key, value, prev: None, diff --git a/src/lib.rs b/src/lib.rs index b35094c9e3d98f314a8100e9bce23e02a9658b81..9aa51bed92e048f47e55065763bc792c2965bb9e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,8 @@ #![feature(portable_simd, test, array_chunks)] +use caching::bounded::bounded_linear_cache::I16Cache; +use pyo3::prelude::*; + extern crate npyz; extern crate rand; extern crate test; @@ -7,3 +10,15 @@ extern crate test; pub mod caching; pub mod fs; pub mod numerics; + +#[pyfunction] +fn sum_as_string(a: usize, b: usize) -> PyResult<String> { + Ok((a + b).to_string()) +} + +#[pymodule] +fn proximipy(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_function(wrap_pyfunction!(sum_as_string, m)?)?; + m.add_class::<I16Cache>()?; + Ok(()) +}