#[cfg(feature = "python")]
use embeddenator_vsa::{ReversibleVSAConfig, SparseVec};
#[cfg(feature = "python")]
use pyo3::prelude::*;
#[cfg(feature = "python")]
use pyo3::types::PyBytes;
#[cfg(feature = "python")]
#[pyclass(name = "SparseVec")]
pub struct PySparseVec {
inner: SparseVec,
}
#[cfg(feature = "python")]
impl Default for PySparseVec {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "python")]
#[pymethods]
impl PySparseVec {
#[new]
pub fn new() -> Self {
Self {
inner: SparseVec::new(),
}
}
#[staticmethod]
pub fn from_indices(pos: Vec<usize>, neg: Vec<usize>) -> Self {
Self {
inner: SparseVec { pos, neg },
}
}
#[getter]
pub fn pos(&self) -> Vec<usize> {
self.inner.pos.clone()
}
#[getter]
pub fn neg(&self) -> Vec<usize> {
self.inner.neg.clone()
}
pub fn bundle(&self, other: &PySparseVec) -> PySparseVec {
PySparseVec {
inner: self.inner.bundle(&other.inner),
}
}
pub fn bind(&self, other: &PySparseVec) -> PySparseVec {
PySparseVec {
inner: self.inner.bind(&other.inner),
}
}
pub fn cosine(&self, other: &PySparseVec) -> f64 {
self.inner.cosine(&other.inner)
}
pub fn to_json(&self) -> PyResult<String> {
serde_json::to_string(&self.inner)
.map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))
}
#[staticmethod]
pub fn from_json(json: &str) -> PyResult<PySparseVec> {
let inner: SparseVec = serde_json::from_str(json)
.map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?;
Ok(PySparseVec { inner })
}
pub fn to_bytes<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
let bytes = bincode::serialize(&self.inner)
.map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?;
Ok(PyBytes::new(py, &bytes))
}
#[staticmethod]
pub fn from_bytes(bytes: &[u8]) -> PyResult<PySparseVec> {
let inner: SparseVec = bincode::deserialize(bytes)
.map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?;
Ok(PySparseVec { inner })
}
pub fn __repr__(&self) -> String {
format!(
"SparseVec(pos={:?}, neg={:?})",
&self.inner.pos[..self.inner.pos.len().min(5)],
&self.inner.neg[..self.inner.neg.len().min(5)]
)
}
pub fn __len__(&self) -> usize {
self.inner.pos.len() + self.inner.neg.len()
}
}
#[cfg(feature = "python")]
#[pyclass(name = "VSAConfig")]
pub struct PyVSAConfig {
inner: ReversibleVSAConfig,
}
#[cfg(feature = "python")]
impl Default for PyVSAConfig {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "python")]
#[pymethods]
impl PyVSAConfig {
#[new]
pub fn new() -> Self {
Self {
inner: ReversibleVSAConfig::default(),
}
}
#[staticmethod]
pub fn custom(
block_size: usize,
max_path_depth: usize,
base_shift: usize,
target_sparsity: usize,
) -> Self {
Self {
inner: ReversibleVSAConfig {
block_size,
max_path_depth,
base_shift,
target_sparsity,
},
}
}
#[getter]
pub fn block_size(&self) -> usize {
self.inner.block_size
}
#[getter]
pub fn max_path_depth(&self) -> usize {
self.inner.max_path_depth
}
#[getter]
pub fn base_shift(&self) -> usize {
self.inner.base_shift
}
#[getter]
pub fn target_sparsity(&self) -> usize {
self.inner.target_sparsity
}
pub fn encode(&self, data: &[u8], path: Option<&str>) -> PySparseVec {
PySparseVec {
inner: SparseVec::encode_data(data, &self.inner, path),
}
}
pub fn decode<'py>(
&self,
py: Python<'py>,
vec: &PySparseVec,
expected_size: usize,
path: Option<&str>,
) -> PyResult<Bound<'py, PyBytes>> {
let decoded = vec.inner.decode_data(&self.inner, path, expected_size);
Ok(PyBytes::new(py, &decoded))
}
pub fn to_json(&self) -> PyResult<String> {
serde_json::to_string(&self.inner)
.map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))
}
#[staticmethod]
pub fn from_json(json: &str) -> PyResult<PyVSAConfig> {
let inner: ReversibleVSAConfig = serde_json::from_str(json)
.map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?;
Ok(PyVSAConfig { inner })
}
pub fn __repr__(&self) -> String {
format!(
"VSAConfig(block_size={}, max_path_depth={}, base_shift={}, target_sparsity={})",
self.inner.block_size,
self.inner.max_path_depth,
self.inner.base_shift,
self.inner.target_sparsity
)
}
}
#[cfg(feature = "python")]
#[pymodule]
fn embeddenator_interop(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<PySparseVec>()?;
m.add_class::<PyVSAConfig>()?;
Ok(())
}
#[cfg(all(test, feature = "python"))]
mod tests {
use super::*;
#[test]
fn test_py_sparse_vec_creation() {
let vec = PySparseVec::new();
assert_eq!(vec.pos().len(), 0);
assert_eq!(vec.neg().len(), 0);
}
#[test]
fn test_py_sparse_vec_operations() {
let vec1 = PySparseVec::from_indices(vec![1, 2, 3], vec![4, 5]);
let vec2 = PySparseVec::from_indices(vec![2, 3, 4], vec![5, 6]);
let bundled = vec1.bundle(&vec2);
assert!(bundled.pos().len() > 0 || bundled.neg().len() > 0);
let bound = vec1.bind(&vec2);
assert!(bound.pos().len() > 0 || bound.neg().len() > 0);
let similarity = vec1.cosine(&vec2);
assert!(similarity.is_finite());
}
#[test]
fn test_py_vsa_config() {
let config = PyVSAConfig::new();
assert!(config.block_size() > 0);
assert!(config.max_path_depth() > 0);
let custom = PyVSAConfig::custom(256, 10, 1000, 200);
assert_eq!(custom.block_size(), 256);
assert_eq!(custom.max_path_depth(), 10);
assert_eq!(custom.base_shift(), 1000);
assert_eq!(custom.target_sparsity(), 200);
}
#[test]
fn test_py_encode_decode() {
use pyo3::Python;
Python::attach(|py| {
let config = PyVSAConfig::new();
let data = b"Hello from Python!";
let encoded = config.encode(data, None);
assert!(encoded.pos().len() > 0 || encoded.neg().len() > 0);
let decoded = config.decode(py, &encoded, data.len(), None).unwrap();
let decoded_bytes = decoded.as_bytes();
assert_eq!(decoded_bytes, data);
});
}
}