Skip to content

Commit cace2a4

Browse files
committed
Enable customization of saves based on Annoy-related types.
1 parent bef51bf commit cace2a4

3 files changed

Lines changed: 60 additions & 4 deletions

File tree

include/knncolle_annoy/Annoy.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,11 @@ class AnnoyPrebuilt final : public knncolle::Prebuilt<Index_, Data_, Distance_>
336336
const auto dname = get_distance_name<AnnoyDistance_>();
337337
knncolle::quick_save(prefix + "distance", dname, std::strlen(dname));
338338

339+
auto& cust = customize_save_for_annoy_types<AnnoyDistance_, AnnoyIndex_, AnnoyData_>();
340+
if (cust) {
341+
cust(prefix);
342+
}
343+
339344
// Not bothering to save anything else; the RNG and thread policy
340345
// should not be relevant once the index is built.
341346

include/knncolle_annoy/utils.hpp

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <cstddef>
88
#include <stdexcept>
99
#include <type_traits>
10+
#include <functional>
1011

1112
#include "knncolle/knncolle.hpp"
1213

@@ -47,6 +48,8 @@ struct has_name<AnnoyDistance_, decltype(AnnoyDistance_::name(), 0)> {
4748
* @tparam AnnoyDistance_ An **Annoy**-compatible class to compute the distance between vectors, as used in `AnnoyBuilder()`.
4849
* @return Name of the distance metric, e.g., `"euclidean"`, `"manhattan"`.
4950
* This is taken from `AnnoyDistance_::name()` if such a method exists, otherwise `"unknown"` is returned.
51+
*
52+
* For unknown distances, consider using `customize_save_for_annoy_types()` to add more information to the on-disk representation during a `knncolle::Prebuilt::save()` call.
5053
*/
5154
template<typename AnnoyDistance_>
5255
const char* get_distance_name() {
@@ -79,6 +82,8 @@ enum class NumericType : char {
7982
/**
8083
* @tparam Type_ Some integer or floating-point type, typically used as `AnnoyIndex_` or `AnnoyData_` in `AnnoyBuilder()`.
8184
* @return Identity of the numeric type.
85+
*
86+
* For unknown types, consider using `customize_save_for_annoy_types()` to add more information to the on-disk representation during a `knncolle::Prebuilt::save()` call.
8287
*/
8388
template<typename Type_>
8489
NumericType get_numeric_type() {
@@ -217,14 +222,29 @@ inline PrebuiltSaveConfig scan_prebuilt_save_config(const std::string& prefix) {
217222
PrebuiltSaveConfig config;
218223
config.index = types[0];
219224
config.data = types[1];
220-
221-
const std::string distpath = prefix + "distance";
222-
std::ifstream input(distpath);
223-
config.distance.insert(config.distance.end(), (std::istreambuf_iterator<char>(input)), (std::istreambuf_iterator<char>()) );
225+
config.distance = knncolle::quick_load_as_string(prefix + "distance");
224226

225227
return config;
226228
}
227229

230+
/**
231+
* Define a customized saving function to preserve type information from the Annoy index in `knncolle::Prebuilt::save()`.
232+
* Users can provide their own function here, to handle types that are unknown to `get_numeric_type()` or `get_distance_name()`.
233+
* Any modifications to this function are not thread-safe and should be done in a serial section.
234+
*
235+
* @tparam AnnoyDistance_ An **Annoy**-compatible class to compute the distance between vectors.
236+
* @tparam AnnoyIndex_ Integer type for the observation indices in the Annoy index.
237+
* @tparam AnnoyData_ Floating-point type for data in the Annoy index.
238+
*
239+
* @return A global function for saving information about `AnnoyDistance_`, `AnnoyIndex_` and `AnnoyData_`.
240+
* If set, this will be called by the `knncolle::Prebuilt::save()` method for the Annoy `Prebuilt` subclass.
241+
*/
242+
template<class AnnoyDistance_, typename AnnoyIndex_, typename AnnoyData_>
243+
std::function<void(const std::string&)>& customize_save_for_annoy_types() {
244+
static std::function<void(const std::string&)> fun;
245+
return fun;
246+
}
247+
228248
}
229249

230250
#endif

tests/src/load_annoy_prebuilt.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,37 @@ TEST_F(AnnoyLoadPrebuiltTest, Manhattan) {
7474
}
7575
}
7676

77+
TEST_F(AnnoyLoadPrebuiltTest, Custom) {
78+
auto& cust = knncolle_annoy::customize_save_for_annoy_types<Annoy::Euclidean, int, float>();
79+
cust = [](const std::string& prefix) -> void {
80+
knncolle::quick_save(prefix + "FOO", "bar", 3);
81+
};
82+
83+
knncolle_annoy::AnnoyBuilder<int, double, double, Annoy::Euclidean> ab;
84+
auto aptr = ab.build_unique(knncolle::SimpleMatrix<int, double>(ndim, nobs, data.data()));
85+
86+
const auto prefix = (savedir / "custom_").string();
87+
aptr->save(prefix);
88+
89+
// Custom function is respected.
90+
EXPECT_EQ(knncolle::quick_load_as_string(prefix + "FOO"), "bar");
91+
cust = nullptr;
92+
93+
// Everything else is still fine.
94+
auto reloaded = knncolle::load_prebuilt_shared<int, double, double>(prefix);
95+
std::vector<int> output_i, output_i2;
96+
std::vector<double> output_d, output_d2;
97+
98+
auto searcher = aptr->initialize();
99+
auto researcher = reloaded->initialize();
100+
for (int x = 0; x < nobs; ++x) {
101+
searcher->search(x, 5, &output_i, &output_d);
102+
researcher->search(x, 5, &output_i2, &output_d2);
103+
EXPECT_EQ(output_i, output_i2);
104+
EXPECT_EQ(output_d, output_d2);
105+
}
106+
}
107+
77108
TEST_F(AnnoyLoadPrebuiltTest, Error) {
78109
knncolle_annoy::AnnoyBuilder<int, double, double, Annoy::Manhattan> ab;
79110
auto aptr = ab.build_unique(knncolle::SimpleMatrix<int, double>(ndim, nobs, data.data()));

0 commit comments

Comments
 (0)