Skip to content

Commit ab0b3ba

Browse files
committed
Project init section
0 parents  commit ab0b3ba

21 files changed

Lines changed: 3419 additions & 0 deletions

.gitignore

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
# Build artifacts and compiled files
2+
*.o
3+
*.so
4+
*.dylib
5+
*.dll
6+
*.a
7+
*.lib
8+
*.exe
9+
10+
# Python
11+
__pycache__/
12+
*.py[cod]
13+
*$py.class
14+
*.egg-info/
15+
dist/
16+
build/
17+
.eggs/
18+
*.egg
19+
.pytest_cache/
20+
.coverage
21+
htmlcov/
22+
.tox/
23+
.venv/
24+
venv/
25+
env/
26+
ENV/
27+
28+
# Rust (Cargo)
29+
target/
30+
Cargo.lock
31+
*.pdb
32+
33+
# C/C++
34+
*.obj
35+
*.elf
36+
*.ilk
37+
*.map
38+
*.exp
39+
*.gch
40+
*.pch
41+
*.tmp
42+
*.tmp_proj
43+
*_wpftmp.csproj
44+
*.log
45+
*.vspscc
46+
*.vssscc
47+
.builds
48+
*.pidb
49+
*.svclog
50+
*.scc
51+
52+
# IDE and Editor files
53+
.vscode/
54+
.idea/
55+
*.swp
56+
*.swo
57+
*~
58+
.DS_Store
59+
Thumbs.db
60+
61+
# CMake
62+
CMakeCache.txt
63+
CMakeFiles/
64+
cmake_install.cmake
65+
Makefile
66+
*.cmake
67+
!CMakeLists.txt
68+
69+
# Generated files
70+
*.feather
71+
*.index
72+
*.bin
73+
*.dat
74+
75+
# OS specific
76+
.DS_Store
77+
.DS_Store?
78+
._*
79+
.Spotlight-V100
80+
.Trashes
81+
ehthumbs.db
82+
Thumbs.db
83+
84+
# Temporary files
85+
*.tmp
86+
*.temp
87+
*.bak
88+
*.backup
89+
*~
90+
91+
# Logs
92+
*.log
93+
logs/
94+
95+
# Test outputs
96+
test_output/
97+
test_results/
98+
99+
# Documentation build
100+
docs/_build/
101+
docs/build/
102+
103+
# Environment variables
104+
.env
105+
.env.local
106+
.env.*.local
107+
108+
# Package manager files
109+
node_modules/
110+
package-lock.json
111+
yarn.lock
112+
113+
# Jupyter Notebook
114+
.ipynb_checkpoints
115+
116+
# pyenv
117+
.python-version
118+
119+
# Pipenv
120+
Pipfile.lock
121+
122+
# PEP 582
123+
__pypackages__/
124+
125+
# Celery
126+
celerybeat-schedule
127+
celerybeat.pid
128+
129+
# SageMath parsed files
130+
*.sage.py
131+
132+
# Spyder project settings
133+
.spyderproject
134+
.spyproject
135+
136+
# Rope project settings
137+
.ropeproject
138+
139+
# mkdocs documentation
140+
/site
141+
142+
# mypy
143+
.mypy_cache/
144+
.dmypy.json
145+
dmypy.json

bindings/feather.cpp

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#include <pybind11/pybind11.h>
2+
#include <pybind11/stl.h>
3+
#include <pybind11/numpy.h>
4+
#include "../include/feather.h"
5+
6+
namespace py = pybind11;
7+
8+
PYBIND11_MODULE(feather_py, m) {
9+
m.doc() = "Feather: SQLite for Vectors";
10+
11+
py::class_<feather::DB, std::unique_ptr<feather::DB, py::nodelete>>(m, "DB")
12+
.def_static("open", &feather::DB::open, py::arg("path"), py::arg("dim") = 768)
13+
.def("add", [](feather::DB& db, uint64_t id, py::array_t<float> vec) {
14+
auto buf = vec.request();
15+
if (buf.size != db.dim()) throw std::runtime_error("Dimension mismatch");
16+
const float* ptr = static_cast<const float*>(buf.ptr);
17+
std::vector<float> vec_copy(ptr, ptr + buf.size);
18+
db.add(id, vec_copy);
19+
})
20+
.def("search", [](const feather::DB& db, py::array_t<float> q, size_t k = 5) {
21+
auto buf = q.request();
22+
if (buf.size != db.dim()) throw std::runtime_error("Query dimension mismatch");
23+
const float* ptr = static_cast<const float*>(buf.ptr);
24+
std::vector<float> query(ptr, ptr + buf.size);
25+
auto results = db.search(query, k);
26+
27+
py::array_t<uint64_t> ids(results.size());
28+
py::array_t<float> distances(results.size());
29+
auto ids_ptr = ids.mutable_data();
30+
auto dist_ptr = distances.mutable_data();
31+
32+
for (size_t i = 0; i < results.size(); ++i) {
33+
auto [id, dist] = results[i];
34+
ids_ptr[i] = id;
35+
dist_ptr[i] = dist;
36+
}
37+
return py::make_tuple(ids, distances);
38+
}, py::arg("q"), py::arg("k") = 5)
39+
.def("save", &feather::DB::save)
40+
.def("dim", &feather::DB::dim);
41+
}

feather-cli/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/target

feather-cli/Cargo.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
name = "feather-cli"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
clap = { version = "4.5", features = ["derive"] }
8+
anyhow = "1.0"
9+
ndarray = "0.15"
10+
ndarray-npy = "0.1" # ← REMOVE features
11+
12+
[build-dependencies]
13+
cc = "1.0"

feather-cli/build.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
fn main() {
2+
cc::Build::new()
3+
.cpp(true)
4+
.file("../src/feather_core.cpp")
5+
.include("../include")
6+
.compile("feather");
7+
8+
println!("cargo:rustc-link-search=native=..");
9+
println!("cargo:rustc-link-lib=static=feather");
10+
}

feather-cli/main.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
use clap::{Parser, Subcommand};
2+
use std::path::PathBuf;
3+
use feather_cli::DB;
4+
use ndarray::Array1;
5+
use ndarray_npy::ReadNpyExt; // ← FINAL: Import trait
6+
7+
#[derive(Parser)]
8+
#[command(name = "feather")]
9+
struct Cli {
10+
#[command(subcommand)]
11+
command: Commands,
12+
}
13+
14+
#[derive(Subcommand)]
15+
enum Commands {
16+
New { path: PathBuf, #[arg(long)] dim: usize },
17+
Add { db: PathBuf, id: u64, #[arg(short)] npy: PathBuf },
18+
Search { db: PathBuf, #[arg(short)] npy: PathBuf, #[arg(long, default_value_t = 5)] k: usize },
19+
}
20+
21+
fn main() -> anyhow::Result<()> {
22+
let cli = Cli::parse();
23+
match cli.command {
24+
Commands::New { path, dim } => {
25+
DB::open(&path, dim).ok_or_else(|| anyhow::anyhow!("Failed to create DB"))?;
26+
println!("Created: {:?}", path);
27+
}
28+
Commands::Add { db, id, npy } => {
29+
let db = DB::open(&db, 0).ok_or_else(|| anyhow::anyhow!("Open failed"))?;
30+
let arr: Array1<f32> = npy.read_npy()?; // ← FINAL: PathBuf has .read_npy()
31+
db.add(id, arr.as_slice().unwrap());
32+
db.save();
33+
println!("Added ID {}", id);
34+
}
35+
Commands::Search { db, npy, k } => {
36+
let db = DB::open(&db, 0).ok_or_else(|| anyhow::anyhow!("Open failed"))?;
37+
let arr: Array1<f32> = npy.read_npy()?; // ← FINAL: PathBuf has .read_npy()
38+
let (ids, dists) = db.search(arr.as_slice().unwrap(), k);
39+
for (id, dist) in ids.iter().zip(dists.iter()) {
40+
println!("ID: {} dist: {:.4}", id, dist);
41+
}
42+
}
43+
}
44+
Ok(())
45+
}

feather-cli/src/build.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
fn main() {
2+
cc::Build::new()
3+
.cpp(true)
4+
.file("../src/feather_core.cpp")
5+
.include("../include")
6+
.flag("-std=c++17")
7+
.compile("feather");
8+
9+
println!("cargo:rustc-link-search=native=..");
10+
println!("cargo:rustc-link-lib=static=feather");
11+
}

feather-cli/src/lib.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
use std::ffi::{c_void, c_char};
2+
use std::path::Path;
3+
4+
#[repr(C)]
5+
pub struct DB(*mut c_void); // ← `pub`
6+
7+
extern "C" {
8+
fn feather_open(path: *const c_char, dim: usize) -> *mut c_void;
9+
fn feather_add(db: *mut c_void, id: u64, vec: *const f32, len: usize);
10+
fn feather_search(db: *mut c_void, query: *const f32, len: usize, k: usize,
11+
out_ids: *mut u64, out_dists: *mut f32);
12+
fn feather_save(db: *mut c_void);
13+
fn feather_close(db: *mut c_void);
14+
}
15+
16+
impl DB {
17+
pub fn open(path: &Path, dim: usize) -> Option<Self> {
18+
let c_path = std::ffi::CString::new(path.to_str()?).ok()?;
19+
let ptr = unsafe { feather_open(c_path.as_ptr(), dim) };
20+
if ptr.is_null() { None } else { Some(DB(ptr)) }
21+
}
22+
23+
pub fn add(&self, id: u64, vec: &[f32]) {
24+
unsafe { feather_add(self.0, id, vec.as_ptr(), vec.len()) }
25+
}
26+
27+
pub fn search(&self, query: &[f32], k: usize) -> (Vec<u64>, Vec<f32>) {
28+
let mut ids = vec![0u64; k];
29+
let mut dists = vec![0f32; k];
30+
unsafe {
31+
feather_search(self.0, query.as_ptr(), query.len(), k, ids.as_mut_ptr(), dists.as_mut_ptr())
32+
};
33+
(ids, dists)
34+
}
35+
36+
pub fn save(&self) { unsafe { feather_save(self.0) } }
37+
}
38+
39+
impl Drop for DB {
40+
fn drop(&mut self) { unsafe { feather_close(self.0) } }
41+
}

feather-cli/src/main.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
use clap::{Parser, Subcommand};
2+
use std::path::PathBuf;
3+
use std::fs::File;
4+
use feather_cli::DB;
5+
use ndarray::Array1;
6+
use ndarray_npy::read_npy; // ← FREE FUNCTION
7+
8+
#[derive(Parser)]
9+
#[command(name = "feather")]
10+
struct Cli {
11+
#[command(subcommand)]
12+
command: Commands,
13+
}
14+
15+
#[derive(Subcommand)]
16+
enum Commands {
17+
New { path: PathBuf, #[arg(long)] dim: usize },
18+
Add { db: PathBuf, id: u64, #[arg(short)] npy: PathBuf },
19+
Search { db: PathBuf, #[arg(short)] npy: PathBuf, #[arg(long, default_value_t = 5)] k: usize },
20+
}
21+
22+
fn main() -> anyhow::Result<()> {
23+
let cli = Cli::parse();
24+
match cli.command {
25+
Commands::New { path, dim } => {
26+
DB::open(&path, dim).ok_or_else(|| anyhow::anyhow!("Failed to create DB"))?;
27+
println!("Created: {:?}", path);
28+
}
29+
Commands::Add { db, id, npy } => {
30+
let db = DB::open(&db, 0).ok_or_else(|| anyhow::anyhow!("Open failed"))?;
31+
let file = File::open(&npy)?;
32+
let arr: Array1<f32> = read_npy(&file)?; // ← FIXED: free function
33+
db.add(id, arr.as_slice().unwrap());
34+
db.save();
35+
println!("Added ID {}", id);
36+
}
37+
Commands::Search { db, npy, k } => {
38+
let db = DB::open(&db, 0).ok_or_else(|| anyhow::anyhow!("Open failed"))?;
39+
let file = File::open(&npy)?;
40+
let arr: Array1<f32> = read_npy(&file)?; // ← FIXED: free function
41+
let (ids, dists) = db.search(arr.as_slice().unwrap(), k);
42+
for (id, dist) in ids.iter().zip(dists.iter()) {
43+
println!("ID: {} dist: {:.4}", id, dist);
44+
}
45+
}
46+
}
47+
Ok(())
48+
}

0 commit comments

Comments
 (0)