usearch 0.1.10 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -32,7 +32,7 @@ jobs:
32
32
  # needs: versioning
33
33
  strategy:
34
34
  matrix:
35
- os: [ubuntu-20.04, macOS-11]
35
+ os: [ubuntu-20.04, macOS-11, windows-2022]
36
36
 
37
37
  steps:
38
38
  - uses: actions/checkout@v3
@@ -130,6 +130,17 @@ jobs:
130
130
  env:
131
131
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
132
132
 
133
+ publish_swift:
134
+ name: Publish ObjC & Swift
135
+ runs-on: macos-latest
136
+ steps:
137
+ - uses: actions/checkout@v3
138
+ - run: git submodule update --init --recursive
139
+ - name: Build
140
+ run: swift build
141
+ - name: Run tests
142
+ run: swift test
143
+
133
144
  deploy_docs:
134
145
  name: Deploy Docs
135
146
  environment:
@@ -36,18 +36,19 @@
36
36
  }
37
37
  },
38
38
  {
39
- "name": "Debug C++ Bench on Text-to-Image 1M @Cos",
39
+ "name": "Debug C++ Bench on Text-to-Image 1B @Cos",
40
40
  "type": "cppdbg",
41
41
  "request": "launch",
42
42
  "program": "${workspaceFolder}/build_debug/bench",
43
43
  "cwd": "${workspaceFolder}",
44
44
  "args": [
45
45
  "--vectors",
46
- "datasets/t2i_1M/base.1M.fbin",
46
+ "datasets/t2i_1B/base.1B.fbin",
47
47
  "--queries",
48
- "datasets/t2i_1M/query.public.100K.fbin",
48
+ "datasets/t2i_1B/query.public.100K.fbin",
49
49
  "--neighbors",
50
- "datasets/t2i_1M/groundtruth.public.100K.ibin",
50
+ "datasets/t2i_1B/groundtruth.public.100K.ibin",
51
+ "--cos"
51
52
  ],
52
53
  "environment": [
53
54
  {
@@ -97,13 +97,19 @@
97
97
  "__memory": "cpp",
98
98
  "filesystem": "cpp",
99
99
  "__availability": "cpp",
100
- "version": "cpp"
100
+ "version": "cpp",
101
+ "future": "cpp"
101
102
  },
102
103
  "cSpell.words": [
104
+ "astype",
105
+ "dtype",
103
106
  "equidimensional",
107
+ "hashable",
104
108
  "jaccard",
109
+ "longlong",
105
110
  "memmap",
106
111
  "ndim",
112
+ "preprocess",
107
113
  "simsimd",
108
114
  "usearch"
109
115
  ],
package/CMakeLists.txt CHANGED
@@ -17,22 +17,18 @@ if(NOT CMAKE_BUILD_TYPE)
17
17
  set(CMAKE_BUILD_TYPE Release)
18
18
  endif()
19
19
 
20
- set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -O0")
21
- set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3")
22
- set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -O3 -pg -g")
23
-
24
20
  # List of all possible compiler IDs:
25
21
  # https://cmake.org/cmake/help/latest/variable/CMAKE_LANG_COMPILER_ID.html
26
22
  if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
27
23
  set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address")
28
- set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-conversion -Wno-unknown-pragmas")
24
+ set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wall -Wextra -Wno-conversion -Wno-unknown-pragmas")
29
25
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native")
30
26
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fmax-errors=1")
31
27
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fopenmp")
32
28
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pedantic")
33
29
 
34
30
  elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
35
- set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wfatal-errors")
31
+ set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wfatal-errors")
36
32
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fopenmp")
37
33
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pedantic")
38
34
 
@@ -46,6 +42,15 @@ elseif(CMAKE_CXX_COMPILER_ID STREQUAL "NVIDIA" OR CMAKE_CXX_COMPILER_ID STREQUAL
46
42
  elseif(CMAKE_CXX_COMPILER_ID STREQUAL "IntelLLVM")
47
43
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -w")
48
44
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ferror-limit=1")
45
+
46
+ elseif(CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
47
+ set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /DEBUG")
48
+ set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /O2")
49
+ set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} /O2 /DEBUG")
50
+ else()
51
+ set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -O0")
52
+ set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3")
53
+ set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -O3 -pg -g")
49
54
  endif()
50
55
 
51
56
  if(APPLE)
@@ -12,6 +12,7 @@ git submodule update --init --recursive
12
12
 
13
13
  ```sh
14
14
  cmake -B ./build_release \
15
+ -DCMAKE_CXX_COMPILER="g++-12" \
15
16
  -DCMAKE_BUILD_TYPE=Release \
16
17
  -DUSEARCH_USE_SIMD=1 \
17
18
  -DUSEARCH_USE_OPENMP=1 \
@@ -24,6 +25,14 @@ cmake -B ./build_release \
24
25
  --queries datasets/wiki_1M/query.public.100K.fbin \
25
26
  --neighbors datasets/wiki_1M/groundtruth.public.100K.ibin
26
27
 
28
+ # Large benchmark on a 1TB dataset:
29
+ ./build_release/bench \
30
+ --vectors datasets/t2i_1B/base.1B.fbin \
31
+ --queries datasets/t2i_1B/query.public.100K.fbin \
32
+ --neighbors datasets/t2i_1B/groundtruth.public.100K.ibin \
33
+ --output datasets/t2i_1B/index.usearch \
34
+ --cos
35
+
27
36
  # To benchmark in Python:
28
37
  python python/bench.py \
29
38
  --vectors datasets/wiki_1M/base.1M.fbin \
@@ -110,44 +119,57 @@ brew install --cask wolfram-engine
110
119
  BigANN benchmark is a good starting point, if you are searching for large collections of high-dimensional vectors.
111
120
  Those often come with precomputed ground-truth neighbors, which is handy for recall evaluation.
112
121
 
113
- | Dataset | Scalar Type | Dimensions | Metric | Size |
114
- | :------------------------------ | :---------: | :--------: | :----: | :----: |
115
- | [Microsoft SPACEV][spacev] | int8 | 100 | L2 | 93 GB |
116
- | [Microsoft Turing-ANNS][turing] | float32 | 100 | L2 | 373 GB |
117
- | [Yandex Deep1B][deep] | float32 | 96 | L2 | 358 GB |
118
- | [Yandex Text-to-Image][t2i] | float32 | 200 | Cos | 750 GB |
122
+ | Dataset | Scalar Type | Dimensions | Metric | Size |
123
+ | :-------------------------------------- | :---------: | :--------: | :----: | :-------: |
124
+ | [Unum UForm Wiki][unum-wiki] | float32 | 256 | IP | 1 GB |
125
+ | [Yandex Text-to-Image Sample][unum-t2i] | float32 | 200 | Cos | 1 GB |
126
+ | | | | | |
127
+ | [Microsoft SPACEV][spacev] | int8 | 100 | L2 | 93 GB |
128
+ | [Microsoft Turing-ANNS][turing] | float32 | 100 | L2 | 373 GB |
129
+ | [Yandex Deep1B][deep] | float32 | 96 | L2 | 358 GB |
130
+ | [Yandex Text-to-Image][t2i] | float32 | 200 | Cos | 750 GB |
131
+ | | | | | |
132
+ | [ViT-L/12 LAION][laion] | float32 | 2048 | Cos | 2 - 10 TB |
119
133
 
120
134
  Luckily, smaller samples of those datasets are available.
121
135
 
136
+ [unum-wiki]: https://huggingface.co/datasets/unum-cloud/ann-wiki-1m
137
+ [unum-t2i]: https://huggingface.co/datasets/unum-cloud/ann-t2i-1m
122
138
  [spacev]: https://github.com/microsoft/SPTAG/tree/main/datasets/SPACEV1B
123
139
  [turing]: https://learning2hash.github.io/publications/microsoftturinganns1B/
124
140
  [t2i]: https://research.yandex.com/blog/benchmarks-for-billion-scale-similarity-search
125
141
  [deep]: https://research.yandex.com/blog/benchmarks-for-billion-scale-similarity-search
142
+ [laion]: https://laion.ai/blog/laion-5b/#download-the-data
126
143
 
127
- ### Yandex Deep1B
128
-
129
- There is a 10M subset available.
144
+ ### Unum UForm Wiki
130
145
 
131
146
  ```sh
132
- mkdir -p datasets/deep/ && \
133
- wget -nc https://storage.yandexcloud.net/yandex-research/ann-datasets/DEEP/base.1B.fbin -P datasets/deep_1B/ &&
134
- wget -nc https://storage.yandexcloud.net/yandex-research/ann-datasets/DEEP/base.10M.fbin -P datasets/deep_1B/ &&
135
- wget -nc https://storage.yandexcloud.net/yandex-research/ann-datasets/DEEP/query.public.10K.fbin -P datasets/deep_1B/ &&
136
- wget -nc https://storage.yandexcloud.net/yandex-research/ann-datasets/DEEP/groundtruth.public.10K.ibin -P datasets/deep_1B/
147
+ mkdir -p datasets/wiki_1M/ && \
148
+ wget -nc https://huggingface.co/datasets/unum-cloud/ann-wiki-1m/resolve/main/base.1M.fbin -P datasets/wiki_1M/ &&
149
+ wget -nc https://huggingface.co/datasets/unum-cloud/ann-wiki-1m/resolve/main/query.public.100K.fbin -P datasets/wiki_1M/ &&
150
+ wget -nc https://huggingface.co/datasets/unum-cloud/ann-wiki-1m/resolve/main/groundtruth.public.100K.ibin -P datasets/wiki_1M/
137
151
  ```
138
152
 
139
153
  ### Yandex Text-to-Image
140
154
 
141
- There is a 1M subset available.
142
-
143
155
  ```sh
144
- mkdir -p datasets/t2i/ && \
156
+ mkdir -p datasets/t2i_1B/ && \
145
157
  wget -nc https://storage.yandexcloud.net/yandex-research/ann-datasets/T2I/base.1B.fbin -P datasets/t2i_1B/ &&
146
158
  wget -nc https://storage.yandexcloud.net/yandex-research/ann-datasets/T2I/base.1M.fbin -P datasets/t2i_1B/ &&
147
159
  wget -nc https://storage.yandexcloud.net/yandex-research/ann-datasets/T2I/query.public.100K.fbin -P datasets/t2i_1B/ &&
148
160
  wget -nc https://storage.yandexcloud.net/yandex-research/ann-datasets/T2I/groundtruth.public.100K.ibin -P datasets/t2i_1B/
149
161
  ```
150
162
 
163
+ ### Yandex Deep1B
164
+
165
+ ```sh
166
+ mkdir -p datasets/deep_1B/ && \
167
+ wget -nc https://storage.yandexcloud.net/yandex-research/ann-datasets/DEEP/base.1B.fbin -P datasets/deep_1B/ &&
168
+ wget -nc https://storage.yandexcloud.net/yandex-research/ann-datasets/DEEP/base.10M.fbin -P datasets/deep_1B/ &&
169
+ wget -nc https://storage.yandexcloud.net/yandex-research/ann-datasets/DEEP/query.public.10K.fbin -P datasets/deep_1B/ &&
170
+ wget -nc https://storage.yandexcloud.net/yandex-research/ann-datasets/DEEP/groundtruth.public.10K.ibin -P datasets/deep_1B/
171
+ ```
172
+
151
173
  ### Profiling
152
174
 
153
175
  With `perf`:
package/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "usearch"
3
- version = "0.1.10"
3
+ version = "0.2.2"
4
4
  authors = ["Ashot Vardanian <1983160+ashvardanian@users.noreply.github.com>"]
5
5
  description = "Smaller & Faster Single-File Vector Search Engine from Unum"
6
6
  edition = "2021"
package/Package.swift ADDED
@@ -0,0 +1,40 @@
1
+ // swift-tools-version:5.3
2
+
3
+ import PackageDescription
4
+
5
+ let package = Package(
6
+ name: "USearch",
7
+ products: [
8
+ .library(
9
+ name: "USearch",
10
+ targets: ["USearchObjective", "USearch"]
11
+ )
12
+ ],
13
+ dependencies: [],
14
+ targets: [
15
+ .target(
16
+ name: "USearchObjective",
17
+ path: "objc",
18
+ sources: ["USearchObjective.mm"],
19
+ cxxSettings: [
20
+ .headerSearchPath("../include/"),
21
+ .headerSearchPath("../src/"),
22
+ .headerSearchPath("../fp16/include/"),
23
+ .headerSearchPath("../simismd/include/")
24
+ ]
25
+ ),
26
+ .target(
27
+ name: "USearch",
28
+ dependencies: ["USearchObjective"],
29
+ path: "swift",
30
+ sources: ["USearch.swift", "Index+Sugar.swift"]
31
+ ),
32
+ .testTarget(
33
+ name: "USearchTests",
34
+ dependencies: ["USearch"],
35
+ path: "swift",
36
+ sources: ["Test.swift"]
37
+ )
38
+ ],
39
+ cxxLanguageStandard: CXXLanguageStandard.cxx11
40
+ )
package/README.md CHANGED
@@ -30,7 +30,7 @@ Vector Search Engine<br/>
30
30
  - [x] [Bring your threads](#bring-your-threads), like OpenMP.
31
31
  - [x] Multiple vectors per label.
32
32
  - [x] [Python](#python) bindings: `pip install usearch`.
33
- - [x] [JavaScript](#java) bindings: `npm install usearch`.
33
+ - [x] [JavaScript](#javascript) bindings: `npm install usearch`.
34
34
  - [x] [Rust](#rust) bindings: `cargo add usearch`.
35
35
  - [x] [Java](#java) bindings: `cloud.unum:usearch` on GitHub.
36
36
  - [ ] GoLang bindings.
@@ -108,7 +108,7 @@ So, if you are working on large datasets, but don't need the in-RAM throughput,
108
108
  ### Quantize on the Fly
109
109
 
110
110
  Most modern CPU have at least partial support for `half`-precision `f16_t` arithmetic.
111
- USearch supports automatic down-casting and up-casting between `f32_t`, `f16_t`, `f64_t`, and `i8q100_t` representations.
111
+ USearch supports automatic down-casting and up-casting between `f32_t`, `f16_t`, `f64_t`, and `f8_t` representations.
112
112
 
113
113
  > Making vectors smaller will help pack more of them in-memory, while also increasing performance on CPUs implementing native support for target type.
114
114
 
@@ -160,7 +160,7 @@ That can be one of the following templates:
160
160
 
161
161
  - `cos_gt<float>` for "Cosine" or "Angular" distance.
162
162
  - `ip_gt<float>` for "Inner Product" or "Dot Product" distance.
163
- - `l2_squared_gt<float>` for the squared "L2" or "Euclidean" distance.
163
+ - `l2sq_gt<float>` for the squared "L2" or "Euclidean" distance.
164
164
  - `jaccard_gt<int>` for "Jaccard" distance between two ordered sets of unique elements.
165
165
  - `bit_hamming_gt<uint>` for "Hamming" distance, as the number of shared bits in hashes.
166
166
  - `pearson_correlation_gt<float>` for "Pearson" correlation between probability distributions.
@@ -216,17 +216,26 @@ Assuming the presence of Global Interpreter Lock in Python, on large insertions
216
216
  $ pip install usearch
217
217
 
218
218
  import numpy as np
219
- import usearch
219
+ from usearch.index import Index
220
220
 
221
- index = usearch.Index(
221
+ index = Index(
222
222
  ndim=256, # Define the number of dimensions in input vectors
223
223
  metric='cos', # Choose the "metric" or "distance", default = 'ip', optional
224
- dtype='f32', # Quantize to 'f16' or 'i8q100' if needed, default = 'f32', optional
224
+ dtype='f32', # Quantize to 'f16' or 'f8' if needed, default = 'f32', optional
225
225
  connectivity=16, # How frequent should the connections in the graph be, optional
226
226
  expansion_add=128, # Control the recall of indexing, optional
227
227
  expansion_search=64, # Control the quality of search, optional
228
228
  )
229
229
 
230
+ vector = np.random.uniform(0, 0.3, (index.ndim)).astype(np.float32)
231
+ index.add(42, vector)
232
+ matches, distances, count = index.search(vector, 10)
233
+ ```
234
+
235
+ Same can be done with batches, rather than single entries.
236
+ Under the hood, worker threads will be spawned, to parallelize the procedure.
237
+
238
+ ```py
230
239
  n = 100
231
240
  labels = np.array(range(n), dtype=np.longlong)
232
241
  vectors = np.random.uniform(0, 0.3, (n, index.ndim)).astype(np.float32)
@@ -234,9 +243,6 @@ vectors = np.random.uniform(0, 0.3, (n, index.ndim)).astype(np.float32)
234
243
  # You can avoid copying the data
235
244
  # Handy when build 1B+ indexes of memory-mapped files
236
245
  index.add(labels, vectors, copy=True)
237
- assert len(index) == n
238
-
239
- # You can search a batch at once
240
246
  matches, distances, counts = index.search(vectors, 10)
241
247
  ```
242
248
 
@@ -267,7 +273,7 @@ Aside from the `add` and `search`, it also provides `add_in_thread` and `search_
267
273
  // cargo add usearch
268
274
 
269
275
  let quant: &str = "f16";
270
- let index = new_ip(5, &quant, 0, 0, 0).unwrap();
276
+ let index = new_ip(5, &quant, 0, 0, 0).unwrap();
271
277
 
272
278
  assert!(index.reserve(10).is_ok());
273
279
  assert!(index.capacity() >= 10);
@@ -292,8 +298,8 @@ assert!(index.load("index.usearch").is_ok());
292
298
  assert!(index.view("index.usearch").is_ok());
293
299
 
294
300
  // There are more "metrics" available
295
- assert!(new_l2(5, &quant, 0, 0, 0).is_ok());
296
- assert!(new_cos(5, &quant, 0, 0, 0).is_ok());
301
+ assert!(new_l2(5, &quant, 0, 0, 0).is_ok());
302
+ assert!(new_cos(5, &quant, 0, 0, 0).is_ok());
297
303
  assert!(new_haversine(&quant, 0, 0, 0).is_ok());
298
304
  ```
299
305
 
@@ -306,6 +312,19 @@ index.add(42, vec);
306
312
  int[] labels = index.search(vec, 5);
307
313
  ```
308
314
 
315
+ ### Objective-C and Swift
316
+
317
+ ```swift
318
+ let index = Index.l2(dimensions: 4, connectivity: 8)
319
+ let vectorA: [Float32] = [0.3, 0.5, 1.2, 1.4]
320
+ let vectorB: [Float32] = [0.4, 0.2, 1.2, 1.1]
321
+ index.add(label: 42, vector: vectorA[...])
322
+ index.add(label: 43, vector: vectorB[...])
323
+
324
+ let results = index.search(vector: vectorA[...], count: 10)
325
+ assert(results.0[0] == 42)
326
+ ```
327
+
309
328
  ### GoLang
310
329
 
311
330
  ### Wolfram
@@ -394,28 +413,27 @@ One can take an encoder model, like the multi-modal UForm, and a web-programming
394
413
  ```python
395
414
  import ucall.rich_posix as ucall
396
415
  import uform
397
- import usearch
416
+ from usearch.index import Index
398
417
 
399
418
  import numpy as np
400
419
  from PIL import Image
401
420
 
402
421
  server = ucall.Server()
403
422
  model = uform.get_model('unum-cloud/uform-vl-multilingual')
404
- index = usearch.Index(ndim=256)
423
+ index = Index(ndim=256)
405
424
 
406
425
  @server
407
426
  def add(label: int, photo: Image.Image):
408
427
  image = model.preprocess_image(photo)
409
428
  vector = model.encode_image(image).detach().numpy()
410
- labels = np.array([label], dtype=np.longlong)
411
- index.add(labels, vector, copy=True)
429
+ index.add(label, vector.flatten(), copy=True)
412
430
 
413
431
  @server
414
432
  def search(query: str) -> np.ndarray:
415
433
  tokens = model.preprocess_text(query)
416
434
  vector = model.encode_text(tokens).detach().numpy()
417
- neighbors = index.search(vector, 3)
418
- return neighbors[0][:neighbors[2][0]]
435
+ neighbors, _, _ = index.search(vector.flatten(), 3)
436
+ return neighbors
419
437
 
420
438
  server.run()
421
439
  ```
package/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.10
1
+ 0.2.2
package/build.gradle CHANGED
@@ -56,11 +56,15 @@ model {
56
56
  if (targetPlatform.operatingSystem.linux) {
57
57
  cppCompiler.args '-I', "${Jvm.current().javaHome}/include/linux"
58
58
  cppCompiler.args '-D_FILE_OFFSET_BITS=64'
59
+ cppCompiler.args '-std=c++11'
59
60
  } else if (targetPlatform.operatingSystem.macOsX) {
60
61
  cppCompiler.args '-I', "${Jvm.current().javaHome}/include/darwin"
61
62
  cppCompiler.args '-D_FILE_OFFSET_BITS=64'
63
+ cppCompiler.args '-std=c++11'
64
+ } else if (targetPlatform.operatingSystem.windows){
65
+ cppCompiler.args "-I${Jvm.current().javaHome}/include/win32"
66
+ cppCompiler.args '/std:c++11'
62
67
  }
63
- cppCompiler.args '-std=c++11'
64
68
  }
65
69
  }
66
70
  }
@@ -76,5 +76,3 @@ else()
76
76
  JEMALLOC_INCLUDE_DIR
77
77
  )
78
78
  endif()
79
-
80
- message("JEMALLOC_LIBRARIES:" ${JEMALLOC_LIBRARIES})
package/conanfile.py ADDED
@@ -0,0 +1,18 @@
1
+ from conans import ConanFile
2
+
3
+ class USearchConan(ConanFile):
4
+
5
+ name = 'USearch'
6
+ version = '0.2.2'
7
+ license = 'Apache 2.0'
8
+ url = 'https://github.com/unum-cloud/usearch'
9
+ description = 'Smaller & Faster Single-File Vector Search Engine from Unum'
10
+
11
+ # No settings/options are necessary, this is header only
12
+ # Potentially add unit-tests in the future:
13
+ # https://docs.conan.io/1/howtos/header_only.html#with-unit-tests
14
+ exports_sources = 'include/*'
15
+ no_copy_source = True
16
+
17
+ def package(self):
18
+ self.copy('*.h')