blob: f327c4bc3c8957c78a82743bd3bed3192bf366ad [file] [log] [blame]
Marc Kupietz4b799e92018-01-02 11:04:56 +01001#define EXPORT __attribute__((visibility("visible")))
2#define IMPORT
Marc Kupietz12af0192021-03-13 18:05:14 +01003
Marc Kupietz39887082024-11-22 18:06:20 +01004#include "config.h"
5#include "export.h"
6#include "merge_operators.h"
Marc Kupietz28cc53e2017-12-23 17:24:55 +01007#include "rocksdb/db.h"
8#include "rocksdb/env.h"
Marc Kupietzc8ddf452018-01-07 21:33:12 +01009#include "rocksdb/table.h"
Marc Kupietze889cec2024-11-23 12:08:42 +010010#include "rocksdb/slice.h"
Marc Kupietz39887082024-11-22 18:06:20 +010011#include <algorithm>
Marc Kupietz2f65bcf2026-08-02 14:04:37 +020012#include <atomic>
Marc Kupietz39887082024-11-22 18:06:20 +010013#include <cassert>
14#include <cmath>
15#include <cstdint>
16#include <iostream>
17#include <memory>
Marc Kupietz122ba3c2026-08-09 17:13:24 +020018#include <mutex>
Marc Kupietz28cc53e2017-12-23 17:24:55 +010019#include <rocksdb/merge_operator.h>
Marc Kupietzc8ddf452018-01-07 21:33:12 +010020#include <rocksdb/slice_transform.h>
Marc Kupietz65d44792026-07-31 09:46:34 +090021#include <rocksdb/version.h>
Marc Kupietz122ba3c2026-08-09 17:13:24 +020022#include "rocksdb/write_batch.h"
Marc Kupietz39887082024-11-22 18:06:20 +010023#include <sstream> // for ostringstream
24#include <string>
Marc Kupietzc630c152025-01-23 11:17:47 +010025#include <thread>
Marc Kupietz65d44792026-07-31 09:46:34 +090026#include <utility>
Marc Kupietz39887082024-11-22 18:06:20 +010027#include <vector>
Marc Kupietz28cc53e2017-12-23 17:24:55 +010028
Marc Kupietz65d44792026-07-31 09:46:34 +090029/* Since rocksdb 11 DB::Open() and DB::OpenForReadOnly() hand the database back
30 as a unique_ptr instead of a raw pointer. */
31#if ROCKSDB_MAJOR >= 11
32#define ROCKSDB_DB_HANDLE std::unique_ptr<rocksdb::DB>
33#define ROCKSDB_DB_RELEASE(handle) (handle).release()
34#else
35#define ROCKSDB_DB_HANDLE rocksdb::DB *
36#define ROCKSDB_DB_RELEASE(handle) (handle)
37#endif
38
Marc Kupietz75af60f2019-01-22 22:34:29 +010039#define WINDOW_SIZE 5
Marc Kupietz98cbcdc2019-01-21 17:11:27 +010040#define FREQUENCY_THRESHOLD 5
Marc Kupietz28cc53e2017-12-23 17:24:55 +010041#define IS_BIG_ENDIAN (*(uint16_t *)"\0\xff" < 0x100)
Marc Kupietz39887082024-11-22 18:06:20 +010042#define encodeCollocation(w1, w2, dist) \
43 (((uint64_t)dist << 56) | ((uint64_t)w2 << 24) | w1)
Marc Kupietz18375e12017-12-24 10:11:18 +010044#define W1(key) (uint64_t)(key & 0xffffff)
45#define W2(key) (uint64_t)((key >> 24) & 0xffffff)
46#define DIST(key) (int8_t)((uint64_t)((key >> 56) & 0xff))
Marc Kupietzc8ddf452018-01-07 21:33:12 +010047
48typedef struct {
49 uint64_t freq;
50 char *word;
Marc Kupietz12af0192021-03-13 18:05:14 +010051} vocab_entry;
Marc Kupietzc8ddf452018-01-07 21:33:12 +010052
53// typedef struct Collocator {
54// uint64_t w2;
55// uint64_t sum;
56// };
57
Marc Kupietz28cc53e2017-12-23 17:24:55 +010058using namespace rocksdb;
Marc Kupietzc8ddf452018-01-07 21:33:12 +010059using namespace std;
Marc Kupietz28cc53e2017-12-23 17:24:55 +010060
Marc Kupietz4b799e92018-01-02 11:04:56 +010061namespace rocksdb {
Marc Kupietz39887082024-11-22 18:06:20 +010062class Collocator {
63public:
64 uint32_t w2;
65 uint64_t f2;
66 uint64_t raw;
67 double pmi;
68 double npmi;
69 double llr;
70 double lfmd;
71 double md;
Marc Kupietze889cec2024-11-23 12:08:42 +010072 double md_nws;
Marc Kupietz39887082024-11-22 18:06:20 +010073 uint64_t left_raw;
74 uint64_t right_raw;
75 double left_pmi;
76 double right_pmi;
77 double dice;
78 double logdice;
79 double ldaf;
80 int window;
81 int af_window;
Marc Kupietz28cc53e2017-12-23 17:24:55 +010082};
Marc Kupietz06c9a9f2018-01-02 16:56:43 +010083
Marc Kupietz39887082024-11-22 18:06:20 +010084size_t num_merge_operator_calls;
85
86void resetNumMergeOperatorCalls() { num_merge_operator_calls = 0; }
87
88size_t num_partial_merge_calls;
89
90void resetNumPartialMergeCalls() { num_partial_merge_calls = 0; }
91
92inline void EncodeFixed64(char *buf, uint64_t value) {
93 if (!IS_BIG_ENDIAN) {
94 memcpy(buf, &value, sizeof(value));
95 } else {
96 buf[0] = value & 0xff;
97 buf[1] = (value >> 8) & 0xff;
98 buf[2] = (value >> 16) & 0xff;
99 buf[3] = (value >> 24) & 0xff;
100 buf[4] = (value >> 32) & 0xff;
101 buf[5] = (value >> 40) & 0xff;
102 buf[6] = (value >> 48) & 0xff;
103 buf[7] = (value >> 56) & 0xff;
104 }
Marc Kupietz4a5e08a2018-06-05 11:07:11 +0200105}
106
Marc Kupietz39887082024-11-22 18:06:20 +0100107inline uint32_t DecodeFixed32(const char *ptr) {
108 if (!IS_BIG_ENDIAN) {
109 // Load the raw bytes
110 uint32_t result;
111 memcpy(&result, ptr, sizeof(result)); // gcc optimizes this to a plain load
112 return result;
113 } else {
114 return ((static_cast<uint32_t>(static_cast<unsigned char>(ptr[0]))) |
115 (static_cast<uint32_t>(static_cast<unsigned char>(ptr[1])) << 8) |
116 (static_cast<uint32_t>(static_cast<unsigned char>(ptr[2])) << 16) |
117 (static_cast<uint32_t>(static_cast<unsigned char>(ptr[3])) << 24));
118 }
119}
120
121inline uint64_t DecodeFixed64(const char *ptr) {
122 if (!IS_BIG_ENDIAN) {
123 // Load the raw bytes
124 uint64_t result;
125 memcpy(&result, ptr, sizeof(result)); // gcc optimizes this to a plain load
126 return result;
127 } else {
128 uint64_t lo = DecodeFixed32(ptr);
129 uint64_t hi = DecodeFixed32(ptr + 4);
130 return (hi << 32) | lo;
131 }
132}
133
134static inline double ca_pmi(uint64_t f1, uint64_t f2, uint64_t f12,
135 uint64_t total, double window_size) {
136 double r1 = f1 * window_size, c1 = f2, e = r1 * c1 / total, o = f12;
137 if (f12 < FREQUENCY_THRESHOLD)
138 return -1.0;
139 else
140 return log2(o / e);
141}
142
143// Bouma, Gerlof (2009): <a
144// href="https://svn.spraakdata.gu.se/repos/gerlof/pub/www/Docs/npmi-pfd.pdf">
145// Normalized (pointwise) mutual information in collocation extraction</a>. In
146// Proceedings of GSCL.
147static double ca_npmi(uint64_t f1, uint64_t f2, uint64_t f12,
148 uint64_t total, double window_size) {
149 double r1 = f1 * window_size, c1 = f2, e = r1 * c1 / total, o = f12;
150 if (f12 < FREQUENCY_THRESHOLD)
151 return -1.0;
152 else
153 return log2(o / e) / (-log2(o / total / window_size));
154}
155
156// Thanopoulos, A., Fakotakis, N., Kokkinakis, G.: Comparative evaluation of
157// collocation extraction metrics. In: International Conference on Language
158// Resources and Evaluation (LREC-2002). (2002) 620–625 double md =
159// log2(pow((double)max * window_size / total, 2) / (window_size *
160// ((double)_vocab[w1].freq/total) * ((double)_vocab[last_w2].freq/total)));
161static double ca_md(uint64_t f1, uint64_t f2, uint64_t f12,
162 uint64_t total, double window_size) {
163 const double r1 = f1 * window_size;
164 const double c1 = f2;
165 const double e = r1 * c1 / total;
166 const double o = f12;
167 return log2(o * o / e);
168}
169
170static double ca_lfmd(uint64_t f1, uint64_t f2, uint64_t f12,
171 uint64_t total, double window_size) {
172 double r1 = f1 * window_size, c1 = f2, e = r1 * c1 / total, o = f12;
173 if (f12 == 0)
174 return 0;
175 return log2(o * o * o / e);
176}
177
178// Evert, Stefan (2004): The Statistics of Word Cooccurrences: Word Pairs and
179// Collocations. PhD dissertation, IMS, University of Stuttgart. Published in
180// 2005, URN urn:nbn:de:bsz:93-opus-23714. Free PDF available from
181// http://purl.org/stefan.evert/PUB/Evert2004phd.pdf
182static double ca_ll(uint64_t w1, uint64_t w2, uint64_t w12, uint64_t n,
183 uint64_t window_size) {
184 double r1 = (double)w1 * window_size, r2 = (double)n - r1, c1 = w2,
185 c2 = n - c1, o11 = w12, o12 = r1 - o11, o21 = c1 - w12, o22 = r2 - o21,
186 e11 = r1 * c1 / n, e12 = r1 * c2 / n, e21 = r2 * c1 / n,
187 e22 = r2 * c2 / n;
188 return (2 * ((o11 > 0 ? o11 * log(o11 / e11) : 0) +
189 (o12 > 0 ? o12 * log(o12 / e12) : 0) +
190 (o21 > 0 ? o21 * log(o21 / e21) : 0) +
191 (o22 > 0 ? o22 * log(o22 / e22) : 0)));
192}
193
194static double ca_dice(uint64_t w1, uint64_t w2, uint64_t w12, uint64_t n,
195 uint64_t window_size) {
196 double r1 = (double)w1 * window_size, c1 = w2;
197 return 2 * w12 / (c1 + r1);
198}
199
200// Rychlý, Pavel (2008): <a
201// href="http://www.fi.muni.cz/usr/sojka/download/raslan2008/13.pdf">A
202// lexicographer-friendly association score.</a> In Proceedings of Recent
203// Advances in Slavonic Natural Language Processing, RASLAN, 6–9.
204static double ca_logdice(uint64_t w1, uint64_t w2, uint64_t w12,
205 uint64_t n, uint64_t window_size) {
206 double r1 = (double)w1 * window_size, c1 = w2;
207 return 14 + log2(2 * w12 / (c1 + r1));
208}
209
210class CountMergeOperator : public AssociativeMergeOperator {
211public:
212 CountMergeOperator() {
213 mergeOperator_ = MergeOperators::CreateUInt64AddOperator();
214 }
215
216 bool Merge(const Slice &key, const Slice *existing_value,
217 const Slice &value, std::string *new_value,
218 Logger *logger) const override {
219 assert(new_value->empty());
220 ++num_merge_operator_calls;
221 if (existing_value == nullptr) {
222 new_value->assign(value.data(), value.size());
223 return true;
224 }
225
226 return mergeOperator_->PartialMerge(key, *existing_value, value, new_value,
227 logger);
228 }
229
230 const char *Name() const override { return "UInt64AddOperator"; }
231
232private:
233 std::shared_ptr<MergeOperator> mergeOperator_;
234};
235
236class CollocatorIterator : public Iterator {
237 char prefixc[sizeof(uint64_t)]{};
238 Iterator *base_iterator_;
239
240public:
241 explicit CollocatorIterator(Iterator *base_iterator) : base_iterator_(base_iterator) {}
242
Marc Kupietzaa354d82026-07-31 09:17:57 +0900243 /* Takes ownership of the iterator handed in by SeekIterator(). Without this
244 every seek leaked a rocksdb iterator and the resources it pins. */
245 ~CollocatorIterator() override { delete base_iterator_; }
246
Marc Kupietz39887082024-11-22 18:06:20 +0100247 void setPrefix(char *prefix) { memcpy(prefixc, prefix, sizeof(uint64_t)); }
248
249 void SeekToFirst() override { base_iterator_->SeekToFirst(); }
250
251 void SeekToLast() override { base_iterator_->SeekToLast(); }
252
253 void Seek(const rocksdb::Slice &s) override { base_iterator_->Seek(s); }
254
255 void SeekForPrev(const rocksdb::Slice &s) override {
256 base_iterator_->SeekForPrev(s);
257 }
258
259 void Prev() override { base_iterator_->Prev(); }
260
261 void Next() override { base_iterator_->Next(); }
262
263 Slice key() const override;
264
265 Slice value() const override;
266
267 Status status() const override;
268
269 bool Valid() const override;
270
271 bool isValid();
272
273 uint64_t intValue();
274
275 uint64_t intKey();
276};
277
278// rocksdb::CollocatorIterator::CollocatorIterator(Iterator* base_iterator) {}
279
280bool CollocatorIterator::Valid() const {
281 return base_iterator_->Valid() && key().starts_with(std::string(prefixc, 3));
282}
283
284bool CollocatorIterator::isValid() {
285 return base_iterator_->Valid() && key().starts_with(std::string(prefixc, 3));
286 // return key().starts_with(std::string(prefixc,3));
287}
288
289uint64_t CollocatorIterator::intKey() {
290 return DecodeFixed64(base_iterator_->key().data());
291}
292
293uint64_t CollocatorIterator::intValue() {
294 return DecodeFixed64(base_iterator_->value().data());
295}
296
297class VocabEntry {
298public:
299 string word;
300 uint64_t freq;
301};
302
Marc Kupietz122ba3c2026-08-09 17:13:24 +0200303static uint64_t env_size(const char *name, uint64_t fallback);
304
Marc Kupietz39887082024-11-22 18:06:20 +0100305class CollocatorDB {
306 WriteOptions merge_option_; // for merge
Marc Kupietz2f65bcf2026-08-02 14:04:37 +0200307 // to repeat a write that rocksdb cancelled, rather than lose the count
308 WriteOptions blocking_merge_option_;
Marc Kupietz122ba3c2026-08-09 17:13:24 +0200309 mutable std::atomic<uint64_t> stalled_writes_{0};
310 mutable std::atomic<uint64_t> failed_writes_{0};
Marc Kupietz39887082024-11-22 18:06:20 +0100311 char _one[sizeof(uint64_t)]{};
312 Slice _one_slice;
313 vector<VocabEntry> _vocab;
314 uint64_t total = 0;
315 uint64_t sentences = 0;
316 float avg_window_size = 8.0;
317
Marc Kupietz122ba3c2026-08-09 17:13:24 +0200318 /* Single-key merges are collected in a batch, so that a long indexing run
319 does one rocksdb write per batch instead of one per collocation pair.
320 rocksdb inserts the merge operands of a key in order anyway, which is why
321 the collection point has a mutex: it is the same serialization rocksdb
322 would impose on the writes.
323
324 They are mutable so that the const read methods can write out what is
325 still buffered: a read has to see every increment that came before it. */
326 mutable std::mutex batch_mutex_;
327 mutable WriteBatch batch_;
328 size_t batch_target_ = 65536;
329
330 /* Writes out a batch of merge operands, retrying a cancelled write the same
331 way merge_one() used to, before the merges were batched.
332
333 Takes a batch that merge_one() or flush() took out of the shared batch,
334 and is called without batch_mutex_: the write is the slow part, and the
335 other threads must be able to keep accumulating while it is in flight.
336 Holding the mutex across the write made the indexer run slower with many
337 threads than with one, because the threads queued up on the mutex and
338 rocksdb never saw concurrent writers to batch into one. */
339 void write_batch(WriteBatch &to_write) const {
340 if (to_write.Count() == 0)
341 return;
342 Status s = db_->Write(merge_option_, &to_write);
343 if (s.ok())
344 return;
345 if (s.IsIncomplete()) {
346 ++stalled_writes_;
347 s = db_->Write(blocking_merge_option_, &to_write);
348 if (s.ok())
349 return;
350 }
351 if (failed_writes_++ == 0)
352 std::cerr << "collocatordb: cannot write, counts are lost: "
353 << s.ToString() << std::endl;
354 }
355
356 /* A read has to see the increments that are still in the batch, so the read
357 methods call this first. */
358 void flush() const {
359 WriteBatch to_write;
360 {
361 std::lock_guard<std::mutex> lock(batch_mutex_);
362 std::swap(batch_, to_write);
363 }
364 write_batch(to_write);
365 }
366
Marc Kupietz39887082024-11-22 18:06:20 +0100367protected:
368 std::shared_ptr<DB> db_;
369
370 WriteOptions put_option_;
371 ReadOptions get_option_;
372 WriteOptions delete_option_;
373
374 uint64_t default_{};
375
376 std::shared_ptr<DB> OpenDb(const char *dbname);
377
378 std::shared_ptr<DB> OpenDbForRead(const char *dbname);
379
380public:
Marc Kupietz2f65bcf2026-08-02 14:04:37 +0200381 virtual ~CollocatorDB(); // flushes what is still in memory, see close()
Marc Kupietz39887082024-11-22 18:06:20 +0100382 void readVocab(const string& fname);
383 string getWord(uint32_t w1);
384
385 uint64_t getWordId(const char *word) const;
386
Marc Kupietzd26b1052024-12-10 16:56:39 +0100387 uint64_t getCorpusSize() const;
388
Marc Kupietz21b964c2024-12-10 17:10:50 +0100389 uint64_t getWordFrequency(uint64_t w1);
390
Marc Kupietz39887082024-11-22 18:06:20 +0100391 CollocatorDB(const char *db_name, bool read_only);
392
393 // public interface of CollocatorDB.
394 // All four functions return false
395 // if the underlying level db operation failed.
396
397 // mapped to a levedb Put
398 bool set(const std::string &key, uint64_t value) {
399 // just treat the internal rep of int64 as the string
400 char buf[sizeof(value)];
401 EncodeFixed64(buf, value);
402 Slice slice(buf, sizeof(value));
403 auto s = db_->Put(put_option_, key, slice);
404
405 if (s.ok()) {
406 return true;
407 } else {
408 std::cerr << s.ToString() << std::endl;
409 return false;
410 }
411 }
412
413 DB *getDb() { return db_.get(); }
414
415 // mapped to a rocksdb Delete
416 bool remove(const std::string &key) {
417 auto s = db_->Delete(delete_option_, key);
418
419 if (s.ok()) {
420 return true;
421 } else {
422 std::cerr << s.ToString() << std::endl;
423 return false;
424 }
425 }
426
427 // mapped to a rocksdb Get
428 bool get(const std::string &key, uint64_t *value) {
Marc Kupietz122ba3c2026-08-09 17:13:24 +0200429 flush();
Marc Kupietz39887082024-11-22 18:06:20 +0100430 std::string str;
431 auto s = db_->Get(get_option_, key, &str);
432
433 if (s.IsNotFound()) {
434 // return default value if not found;
435 *value = default_;
436 return true;
437 } else if (s.ok()) {
438 // deserialization
439 if (str.size() != sizeof(uint64_t)) {
440 std::cerr << "value corruption\n";
441 return false;
442 }
443 *value = DecodeFixed64(&str[0]);
444 return true;
445 } else {
446 std::cerr << s.ToString() << std::endl;
447 return false;
448 }
449 }
450
451 uint64_t get(const uint32_t w1, const uint32_t w2, const int8_t dist) {
452 char encoded_key[sizeof(uint64_t)];
453 EncodeFixed64(encoded_key, encodeCollocation(w1, w2, dist));
454 uint64_t value = default_;
455 get(std::string(encoded_key, 8), &value);
456 return value;
457 }
458
Marc Kupietz2f65bcf2026-08-02 14:04:37 +0200459 /* A merge that does not lose the count. rocksdb cancels a write with
460 Status::Incomplete instead of waiting when it is asked not to slow down,
461 and then the increment is simply gone. Such a write was not applied, so
462 repeating it blocking cannot count twice. */
463 void merge_one(const Slice &key) {
Marc Kupietz122ba3c2026-08-09 17:13:24 +0200464 WriteBatch to_write;
465 {
466 std::lock_guard<std::mutex> lock(batch_mutex_);
467 batch_.Merge(key, _one_slice);
468 if (batch_.Count() >= (int)batch_target_)
469 std::swap(batch_, to_write);
Marc Kupietz2f65bcf2026-08-02 14:04:37 +0200470 }
Marc Kupietz122ba3c2026-08-09 17:13:24 +0200471 write_batch(to_write);
Marc Kupietz2f65bcf2026-08-02 14:04:37 +0200472 }
473
Marc Kupietz39887082024-11-22 18:06:20 +0100474 virtual void inc(const std::string &key) {
Marc Kupietz2f65bcf2026-08-02 14:04:37 +0200475 merge_one(Slice(key));
Marc Kupietz39887082024-11-22 18:06:20 +0100476 }
477
478 void inc(const uint64_t key) {
479 char encoded_key[sizeof(uint64_t)];
480 EncodeFixed64(encoded_key, key);
Marc Kupietz2f65bcf2026-08-02 14:04:37 +0200481 merge_one(Slice(encoded_key, sizeof(uint64_t)));
Marc Kupietz39887082024-11-22 18:06:20 +0100482 }
483
484 virtual void inc(uint32_t w1, uint32_t w2, uint8_t dist);
485
486 void dump(uint32_t w1, uint32_t w2, int8_t dist) const;
487
488 vector<Collocator> get_collocators(uint32_t w1);
489
490 vector<Collocator> get_collocators(uint32_t w1, uint32_t max_w2);
491
492 vector<Collocator> get_collocation_scores(uint32_t w1, uint32_t w2);
493
494 vector<Collocator> get_collocators(uint32_t w1, uint32_t min_w2,
495 uint32_t max_w2);
496
497 void applyCAMeasures(uint32_t w1, uint32_t w2,
498 uint64_t *sumWindow, uint64_t sum,
499 int usedPositions, int true_window_size,
500 Collocator *result) const;
501
502 void dumpSparseLlr(uint32_t w1, uint32_t min_cooccur);
503
504 string collocators2json(uint32_t w1, const vector<Collocator>& collocators);
505
506 // mapped to a rocksdb Merge operation
507 virtual bool add(const std::string &key, uint64_t value) {
508 char encoded[sizeof(uint64_t)];
509 EncodeFixed64(encoded, value);
510 Slice slice(encoded, sizeof(uint64_t));
511 auto s = db_->Merge(merge_option_, key, slice);
512
513 if (s.ok()) {
514 return true;
515 } else {
516 std::cerr << s.ToString() << std::endl;
517 return false;
518 }
519 }
520
521 CollocatorIterator *SeekIterator(uint64_t w1, uint64_t w2, int8_t dist) const;
Marc Kupietz2f65bcf2026-08-02 14:04:37 +0200522
523 /* Writes what is still in memory and closes the database. Without this
524 everything that has not been flushed yet is lost when the process ends,
525 because the write ahead log is switched off for speed. */
526 void close() {
527 if (!db_)
528 return;
Marc Kupietz122ba3c2026-08-09 17:13:24 +0200529 flush();
Marc Kupietz2f65bcf2026-08-02 14:04:37 +0200530 if (stalled_writes_ > 0)
531 std::cerr << "collocatordb: repeated " << stalled_writes_
532 << " writes that rocksdb had cancelled" << std::endl;
533 if (failed_writes_ > 0)
534 std::cerr << "collocatordb: " << failed_writes_
535 << " writes failed, the database is missing counts" << std::endl;
536 FlushOptions flush_options;
537 flush_options.wait = true;
538 Status s = db_->Flush(flush_options);
539 if (!s.ok() && !s.IsNotSupported())
540 std::cerr << "collocatordb: cannot write what is still in memory: "
541 << s.ToString() << std::endl;
542 db_.reset();
543 }
544
Marc Kupietz39887082024-11-22 18:06:20 +0100545};
546
Marc Kupietz2f65bcf2026-08-02 14:04:37 +0200547CollocatorDB::~CollocatorDB() { close(); }
548
Marc Kupietz39887082024-11-22 18:06:20 +0100549CollocatorDB::CollocatorDB(const char *db_name,
550 bool read_only = false) {
Marc Kupietz122ba3c2026-08-09 17:13:24 +0200551 batch_target_ = (size_t)env_size("COLLOCATORDB_BATCH_SIZE", 65536);
Marc Kupietz39887082024-11-22 18:06:20 +0100552 // merge_option_.sync = true;
553 if (read_only)
554 db_ = OpenDbForRead(strdup(db_name));
555 else
556 db_ = OpenDb(db_name);
557 assert(db_);
558 uint64_t one = 1;
559 EncodeFixed64(_one, one);
560 _one_slice = Slice(_one, sizeof(uint64_t));
561}
562
563void CollocatorDB::inc(const uint32_t w1, const uint32_t w2,
564 const uint8_t dist) {
565 inc(encodeCollocation(w1, w2, dist));
566}
567
568void CollocatorDB::readVocab(const string& fname) {
569 char strbuf[2048];
570 uint64_t freq;
571 FILE *fin = fopen(fname.c_str(), "rb");
572 if (fin == nullptr) {
573 cout << "Vocabulary file " << fname << " not found\n";
574 exit(1);
575 }
576 uint64_t i = 0;
577 while (fscanf(fin, "%s %lu", strbuf, &freq) == 2) {
578 _vocab.push_back({strbuf, freq});
579 total += freq;
580 i++;
581 }
582 fclose(fin);
583
584 char size_fname[256];
585 strcpy(size_fname, fname.c_str());
586 char *pos = strstr(size_fname, ".vocab");
587 if (pos) {
588 *pos = 0;
589 strcat(size_fname, ".size");
590 FILE *fp = fopen(size_fname, "r");
591 if (fp != nullptr) {
592 fscanf(fp, "%lu", &sentences);
593 fscanf(fp, "%lu", &total);
594 float sl = (float)total / (float)sentences;
595 float w = WINDOW_SIZE;
596 avg_window_size =
597 ((sl > 2 * w ? (sl - 2 * w) * 2 * w : 0) + (double)w * (3 * w - 1)) /
598 sl;
599 fprintf(stdout,
600 "Size corrections found: corpus size: %lu tokens in %lu "
601 "sentences, avg. sentence size: %f, avg. window size: %f\n",
602 total, sentences, sl, avg_window_size);
603 fclose(fp);
604 } else {
605 // std::cout << "size file " << size_fname << " not found\n";
606 }
607 } else {
608 std::cout << "cannot determine size file " << size_fname << "\n";
609 }
610}
611
612std::shared_ptr<DB> CollocatorDB::OpenDbForRead(const char *name) {
Marc Kupietz65d44792026-07-31 09:46:34 +0900613 ROCKSDB_DB_HANDLE db;
Marc Kupietz39887082024-11-22 18:06:20 +0100614 Options options;
615 options.env->SetBackgroundThreads(4);
616 options.create_if_missing = true;
617 options.merge_operator = std::make_shared<CountMergeOperator>();
618 options.max_successive_merges = 0;
619 // options.prefix_extractor.reset(NewFixedPrefixTransform(8));
620 options.IncreaseParallelism();
621 options.OptimizeLevelStyleCompaction();
622 options.prefix_extractor.reset(NewFixedPrefixTransform(3));
623 ostringstream dbname, vocabname;
624 dbname << name << ".rocksdb";
625 auto s = DB::OpenForReadOnly(options, dbname.str(), &db);
626 if (!s.ok()) {
627 std::cerr << s.ToString() << std::endl;
628 assert(false);
629 }
630 vocabname << name << ".vocab";
631 readVocab(vocabname.str());
Marc Kupietz65d44792026-07-31 09:46:34 +0900632 return std::shared_ptr<DB>(ROCKSDB_DB_RELEASE(db));
Marc Kupietz39887082024-11-22 18:06:20 +0100633}
634
Marc Kupietz2f65bcf2026-08-02 14:04:37 +0200635 /* Reads a size from the environment, so that a long indexing run can be
636 tuned without recompiling. Returns the default when unset or unusable. */
637 static uint64_t env_size(const char *name, uint64_t fallback) {
638 const char *value = getenv(name);
639 if (value == nullptr)
640 return fallback;
641 char *end = nullptr;
642 unsigned long long parsed = strtoull(value, &end, 10);
643 if (end == value || parsed == 0) {
644 std::cerr << "collocatordb: ignoring " << name << "=" << value << std::endl;
645 return fallback;
646 }
647 return (uint64_t)parsed;
648 }
649
Marc Kupietzc630c152025-01-23 11:17:47 +0100650 std::shared_ptr<DB> CollocatorDB::OpenDb(const char *dbname) {
Marc Kupietz65d44792026-07-31 09:46:34 +0900651 ROCKSDB_DB_HANDLE db;
Marc Kupietzc630c152025-01-23 11:17:47 +0100652 Options options;
Marc Kupietz39887082024-11-22 18:06:20 +0100653
Marc Kupietzc630c152025-01-23 11:17:47 +0100654 int max_cores = static_cast<int>(std::thread::hardware_concurrency());
655
Marc Kupietzc630c152025-01-23 11:17:47 +0100656 options.create_if_missing = true;
657 options.merge_operator = std::make_shared<CountMergeOperator>();
Marc Kupietzc630c152025-01-23 11:17:47 +0100658
Marc Kupietz2f65bcf2026-08-02 14:04:37 +0200659 /* Indexing a corpus is one long stream of merge operands for the same
660 keys. rocksdb only collapses them when memtables are merged and when
661 files are compacted, so the settings aim at doing that early and often -
662 every collapsed operand is one less to write, to read and to merge
663 again later.
Marc Kupietzc630c152025-01-23 11:17:47 +0100664
Marc Kupietz2f65bcf2026-08-02 14:04:37 +0200665 All of it can be overridden from the environment, to be able to tune a
666 run that takes days without recompiling. */
667 options.write_buffer_size = env_size("COLLOCATORDB_WRITE_BUFFER_MB", 256) << 20;
668 options.max_write_buffer_number = (int)env_size("COLLOCATORDB_WRITE_BUFFERS", 8);
669 /* collapses the operands of several memtables before they are written */
670 options.min_write_buffer_number_to_merge =
671 (int)env_size("COLLOCATORDB_WRITE_BUFFERS_TO_MERGE", 4);
672
673 /* Compaction has to keep up with the writer, otherwise the level 0 files
674 pile up and every read has to merge through all of them. */
675 options.max_background_jobs =
676 (int)env_size("COLLOCATORDB_BACKGROUND_JOBS",
677 max_cores > 4 ? (max_cores < 32 ? max_cores : 32) : 4);
678 options.max_subcompactions = (int)env_size("COLLOCATORDB_SUBCOMPACTIONS", 4);
679 options.level0_file_num_compaction_trigger = 4;
680 options.level0_slowdown_writes_trigger = 20;
681 options.level0_stop_writes_trigger = 36;
682
683 /* Merge operands are inserted one writer at a time, rocksdb does not
684 support concurrent memtable writes for them, which is why more threads
685 in the indexer do not help beyond a certain point. */
686 options.allow_concurrent_memtable_write = false;
Marc Kupietzc630c152025-01-23 11:17:47 +0100687 options.enable_write_thread_adaptive_yield = true;
Marc Kupietzc630c152025-01-23 11:17:47 +0100688 options.allow_mmap_reads = true;
689
Marc Kupietzc630c152025-01-23 11:17:47 +0100690 BlockBasedTableOptions table_options;
Marc Kupietz2f65bcf2026-08-02 14:04:37 +0200691 table_options.block_cache =
692 NewLRUCache(env_size("COLLOCATORDB_BLOCK_CACHE_MB", 512) << 20);
Marc Kupietzc630c152025-01-23 11:17:47 +0100693 options.table_factory.reset(NewBlockBasedTableFactory(table_options));
694
Marc Kupietz2f65bcf2026-08-02 14:04:37 +0200695 /* No write ahead log: an interrupted indexing run is repeated, and the log
696 would double the amount written. Everything that has not been flushed is
697 lost when the process dies, which is what close_collocatordb() is for. */
698 merge_option_.disableWAL = true;
699 merge_option_.sync = false;
700 /* Let rocksdb slow the writer down when compaction falls behind, instead
701 of cancelling the write. It used to be told to do neither, which threw
702 counts away. merge_one() repeats a cancelled write, whatever the
703 settings are. */
704 merge_option_.low_pri = false;
705 merge_option_.no_slowdown = false;
706 blocking_merge_option_ = merge_option_;
707 blocking_merge_option_.low_pri = false;
708 blocking_merge_option_.no_slowdown = false;
Marc Kupietzc630c152025-01-23 11:17:47 +0100709
710 Status s = DB::Open(options, dbname, &db);
711 if (!s.ok()) {
712 std::cerr << s.ToString() << std::endl;
713 assert(false);
714 }
715 total = 1000;
Marc Kupietz65d44792026-07-31 09:46:34 +0900716 return std::shared_ptr<DB>(ROCKSDB_DB_RELEASE(db));
Marc Kupietz39887082024-11-22 18:06:20 +0100717 }
Marc Kupietz39887082024-11-22 18:06:20 +0100718
719CollocatorIterator *
720CollocatorDB::SeekIterator(uint64_t w1, uint64_t w2, int8_t dist) const {
Marc Kupietz122ba3c2026-08-09 17:13:24 +0200721 flush();
Marc Kupietz39887082024-11-22 18:06:20 +0100722 ReadOptions options;
723 options.prefix_same_as_start = true;
724 char prefixc[sizeof(uint64_t)];
725 EncodeFixed64(prefixc, encodeCollocation(w1, w2, dist));
726 Iterator *it = db_->NewIterator(options);
727 auto *cit = new CollocatorIterator(it);
728 if (w2 > 0)
729 cit->Seek(std::string(prefixc, 6));
730 else
731 cit->Seek(std::string(prefixc, 3));
732 cit->setPrefix(prefixc);
733 return cit;
734}
735
736void CollocatorDB::dump(uint32_t w1, uint32_t w2, int8_t dist) const {
737 auto it = std::unique_ptr<CollocatorIterator>(SeekIterator(w1, w2, dist));
738 for (; it->isValid(); it->Next()) {
739 uint64_t value = it->intValue();
740 uint64_t key = it->intKey();
741 std::cout << "w1:" << W1(key) << ", w2:" << W2(key)
742 << ", dist:" << (int32_t)DIST(key) << " - count:" << value
743 << std::endl;
744 }
745 std::cout << "ready dumping\n";
746}
747
748bool sortByNpmi(const Collocator &lhs, const Collocator &rhs) {
749 return lhs.npmi > rhs.npmi;
750}
751
752bool sortByLfmd(const Collocator &lhs, const Collocator &rhs) {
753 return lhs.lfmd > rhs.lfmd;
754}
755
756bool sortByLlr(const Collocator &lhs, const Collocator &rhs) {
757 return lhs.llr > rhs.llr;
758}
759
760bool sortByLogDice(const Collocator &lhs, const Collocator &rhs) {
761 return lhs.logdice > rhs.logdice;
762}
763
764bool sortByLogDiceAF(const Collocator &lhs, const Collocator &rhs) {
765 return lhs.ldaf > rhs.ldaf;
766}
767
768void CollocatorDB::applyCAMeasures(
769 const uint32_t w1, const uint32_t w2, uint64_t *sumWindow,
770 const uint64_t sum, const int usedPositions, int true_window_size,
771 Collocator *result) const {
772 uint64_t f1 = _vocab[w1].freq, f2 = _vocab[w2].freq;
773 double o = sum, r1 = f1 * true_window_size, c1 = f2, e = r1 * c1 / total,
774 pmi = log2(o / e), md = log2(o * o / e), lfmd = log2(o * o * o / e),
Marc Kupietze889cec2024-11-23 12:08:42 +0100775 llr = ca_ll(f1, f2, sum, total, true_window_size),
776 md_nws = ca_md(f1, f2, sum, total, 2 * WINDOW_SIZE),
777 ld = ca_logdice(f1, f2, sum, total, true_window_size);
Marc Kupietz39887082024-11-22 18:06:20 +0100778
779 int bestWindow = usedPositions;
780 double bestAF = ld;
781 // if(f1<75000000)
782 // #pragma omp parallel for reduction(max:bestAF)
783 // #pragma omp target teams distribute parallel for reduction(max:bestAF)
784 // map(tofrom:bestAF,currentAF,bestWindow,usedPositions)
785 for (int bitmask = 1; bitmask < (1 << (2 * WINDOW_SIZE)); bitmask++) {
786 if ((bitmask & usedPositions) == 0 || (bitmask & ~usedPositions) > 0)
787 continue;
788 uint64_t currentWindowSum = 0;
789 // #pragma omp target teams distribute parallel for
790 // reduction(+:currentWindowSum) map(tofrom:bitmask,usedPositions)
791 for (int pos = 0; pos < 2 * WINDOW_SIZE; pos++) {
792 if (((1 << pos) & bitmask & usedPositions) != 0)
793 currentWindowSum += sumWindow[pos];
794 }
795 double currentAF = ca_logdice(f1, f2, currentWindowSum, total,
796 __builtin_popcount(bitmask));
797 if (currentAF > bestAF) {
798 bestAF = currentAF;
799 bestWindow = bitmask;
800 }
801 }
802
803 *result = {w2,
804 f2,
805 sum,
806 pmi,
807 pmi / (-log2(o / total / true_window_size)),
808 llr,
809 lfmd,
810 md,
Marc Kupietze889cec2024-11-23 12:08:42 +0100811 md_nws,
Marc Kupietz39887082024-11-22 18:06:20 +0100812 sumWindow[WINDOW_SIZE],
813 sumWindow[WINDOW_SIZE - 1],
814 ca_pmi(f1, f2, sumWindow[WINDOW_SIZE], total, 1),
815 ca_pmi(f1, f2, sumWindow[WINDOW_SIZE - 1], total, 1),
816 ca_dice(f1, f2, sum, total, true_window_size),
817 ld,
818 bestAF,
819 usedPositions,
820 bestWindow};
821}
822
823std::vector<Collocator>
824CollocatorDB::get_collocators(uint32_t w1, uint32_t min_w2,
825 uint32_t max_w2) {
826 std::vector<Collocator> collocators;
827 uint64_t w2, last_w2 = 0xffffffffffffffff;
828 uint64_t maxv = 0, sum = 0;
Marc Kupietzaa354d82026-07-31 09:17:57 +0900829 /* fixed size, so it lives on the stack and cannot be leaked */
830 uint64_t sumWindow[2 * WINDOW_SIZE];
831 memset(sumWindow, 0, sizeof(sumWindow));
Marc Kupietz39887082024-11-22 18:06:20 +0100832 int true_window_size = 1;
833 int usedPositions = 0;
834
835 if (w1 > _vocab.size()) {
836 std::cout << w1 << "> vocabulary size " << _vocab.size() << "\n";
837 w1 -= _vocab.size();
838 }
839#ifdef DEBUG
840 std::cout << "Searching for collocates of " << _vocab[w1].word << "\n";
841#endif
842 // #pragma omp parallel num_threads(40)
843 // #pragma omp single
844 for (auto it =
845 std::unique_ptr<CollocatorIterator>(SeekIterator(w1, min_w2, 0));
846 it->isValid(); it->Next()) {
847 uint64_t value = it->intValue(), key = it->intKey();
848 if ((w2 = W2(key)) > max_w2)
849 continue;
850 if (last_w2 == 0xffffffffffffffff)
851 last_w2 = w2;
852 if (w2 != last_w2) {
853 if (sum >= FREQUENCY_THRESHOLD) {
854 collocators.push_back({});
855 Collocator *result = &(collocators[collocators.size() - 1]);
856 // #pragma omp task firstprivate(last_w2, sumWindow, sum, usedPositions,
857 // true_window_size) shared(w1, result) if(sum > 1000000)
858 {
859 // uint64_t *nsw = (uint64_t *)malloc(sizeof(uint64_t) * 2
860 // *WINDOW_SIZE); memcpy(nsw, sumWindow, sizeof(uint64_t) * 2
861 // *WINDOW_SIZE);
862 applyCAMeasures(w1, last_w2, sumWindow, sum, usedPositions,
863 true_window_size, result);
864 // free(nsw);
865 }
866 }
867 memset(sumWindow, 0, 2 * WINDOW_SIZE * sizeof(uint64_t));
868 usedPositions = 1 << (-DIST(key) + WINDOW_SIZE - (DIST(key) < 0 ? 1 : 0));
869 sumWindow[-DIST(key) + WINDOW_SIZE - (DIST(key) < 0 ? 1 : 0)] = value;
870 last_w2 = w2;
871 maxv = value;
872 sum = value;
873 true_window_size = 1;
874 if (min_w2 == max_w2 && w2 != min_w2)
875 break;
876 } else {
877 sum += value;
878 if (value > maxv)
879 maxv = value;
880 usedPositions |=
881 1 << (-DIST(key) + WINDOW_SIZE - (DIST(key) < 0 ? 1 : 0));
882 sumWindow[-DIST(key) + WINDOW_SIZE - (DIST(key) < 0 ? 1 : 0)] = value;
883 true_window_size++;
884 }
885 }
886
Marc Kupietz2f65bcf2026-08-02 14:04:37 +0200887 /* A collocate is only finished when the next one begins, so the last one is
888 still in sumWindow when the iterator is through. It used to be dropped,
889 which cost every word one of its collocates. */
890 if (last_w2 != 0xffffffffffffffff && sum >= FREQUENCY_THRESHOLD) {
891 collocators.push_back({});
892 applyCAMeasures(w1, last_w2, sumWindow, sum, usedPositions,
893 true_window_size, &(collocators[collocators.size() - 1]));
894 }
895
Marc Kupietz39887082024-11-22 18:06:20 +0100896 // #pragma omp taskwait
897 sort(collocators.begin(), collocators.end(), sortByLogDiceAF);
898
899#ifdef DEBUG
900 int i = 0;
901 for (Collocator c : collocators) {
902 if (i++ > 10)
903 break;
904 std::cout << "w1:" << _vocab[w1].word << ", w2: *" << _vocab[c.w2].word
905 << "*"
906 << "\t f(w1):" << _vocab[w1].freq
907 << "\t f(w2):" << _vocab[c.w2].freq << "\t f(w1, w2):" << c.raw
908 << "\t pmi:" << c.pmi << "\t npmi:" << c.npmi
909 << "\t llr:" << c.llr << "\t md:" << c.md << "\t lfmd:" << c.lfmd
910 << "\t total:" << total << std::endl;
911 }
912#endif
913
914 return collocators;
915}
916
917std::vector<Collocator>
918CollocatorDB::get_collocation_scores(uint32_t w1, uint32_t w2) {
919 return get_collocators(w1, w2, w2);
920}
921
922std::vector<Collocator> CollocatorDB::get_collocators(uint32_t w1) {
923 return get_collocators(w1, 0, UINT32_MAX);
924}
925
926void CollocatorDB::dumpSparseLlr(uint32_t w1, uint32_t min_cooccur) {
927 std::vector<Collocator> collocators;
928 std::stringstream stream;
929 uint64_t w2, last_w2 = 0xffffffffffffffff;
930 uint64_t maxv = 0, total_w1 = 0;
931 bool first = true;
932 for (auto it = std::unique_ptr<CollocatorIterator>(SeekIterator(w1, 0, 0));
933 it->isValid(); it->Next()) {
934 uint64_t value = it->intValue(), key = it->intKey();
935 w2 = W2(key);
936 total_w1 += value;
937 if (last_w2 == 0xffffffffffffffff)
938 last_w2 = w2;
939 if (w2 != last_w2) {
940 if (maxv >= min_cooccur) {
941 double llr =
942 ca_ll(_vocab[w1].freq, _vocab[last_w2].freq, maxv, total, 1);
943 if (first)
944 first = false;
945 else
946 stream << " ";
947 stream << w2 << " " << llr;
948 }
949 last_w2 = w2;
950 maxv = value;
951 } else {
952 if (value > maxv)
953 maxv = value;
954 }
955 }
956 if (first)
957 stream << "1 0.0";
958 stream << "\n";
959 std::cout << stream.str();
960}
961
962Slice CollocatorIterator::key() const {
963 return base_iterator_->key();
964}
965
966Slice CollocatorIterator::value() const {
967 return base_iterator_->value();
968}
969
970Status CollocatorIterator::status() const {
971 return base_iterator_->status();
972}
973
974}; // namespace rocksdb
975
976string CollocatorDB::getWord(uint32_t w1) { return _vocab[w1].word; }
977
978uint64_t CollocatorDB::getWordId(const char *word) const {
Marc Kupietz979580e2024-11-21 18:05:07 +0100979 for (uint64_t i = 0; i < _vocab.size(); i++) {
980 if (strcmp(_vocab[i].word.c_str(), word) == 0)
981 return i;
982 }
983 return 0;
984}
985
Marc Kupietzd26b1052024-12-10 16:56:39 +0100986uint64_t CollocatorDB::getCorpusSize() const {
987 return total;
988}
989
Marc Kupietz21b964c2024-12-10 17:10:50 +0100990uint64_t CollocatorDB::getWordFrequency(uint64_t w1) {
991 return _vocab[w1].freq;
992}
993
Marc Kupietz39887082024-11-22 18:06:20 +0100994string CollocatorDB::collocators2json(uint32_t w1,
995 const vector<Collocator>& collocators) {
Marc Kupietzc8ddf452018-01-07 21:33:12 +0100996 ostringstream s;
Marc Kupietz0dd86ef2018-01-11 22:23:17 +0100997 int i = 0;
Marc Kupietz39887082024-11-22 18:06:20 +0100998 s << " { \"f1\": " << _vocab[w1].freq << "," << R"("w1":")"
999 << string(_vocab[w1].word) << "\", " << "\"N\": " << total << ", "
1000 << "\"collocates\": [";
Marc Kupietzc8ddf452018-01-07 21:33:12 +01001001 bool first = true;
1002 for (Collocator c : collocators) {
Marc Kupietz39887082024-11-22 18:06:20 +01001003 if (strncmp(_vocab[c.w2].word.c_str(), "quot", 4) == 0)
1004 continue;
Marc Kupietz0dd86ef2018-01-11 22:23:17 +01001005 if (i++ > 200)
1006 break;
Marc Kupietz12af0192021-03-13 18:05:14 +01001007 if (!first)
Marc Kupietzc8ddf452018-01-07 21:33:12 +01001008 s << ",\n";
1009 else
1010 first = false;
1011 s << "{"
Marc Kupietz39887082024-11-22 18:06:20 +01001012 "\"word\":\""
1013 << (string(_vocab[c.w2].word) == "<num>"
1014 ? string("###")
1015 : string(_vocab[c.w2].word))
1016 << "\"," << "\"f2\":" << c.f2 << "," << "\"f\":" << c.raw << ","
1017 << "\"npmi\":" << c.npmi << "," << "\"pmi\":" << c.pmi << ","
1018 << "\"llr\":" << c.llr << "," << "\"lfmd\":" << c.lfmd << ","
Marc Kupietze889cec2024-11-23 12:08:42 +01001019 << "\"md\":" << c.md << "," << "\"md_nws\":" << c.md_nws << "," << "\"dice\":" << c.dice << ","
Marc Kupietz39887082024-11-22 18:06:20 +01001020 << "\"ld\":" << c.logdice << "," << "\"ln_count\":" << c.left_raw << ","
1021 << "\"rn_count\":" << c.right_raw << "," << "\"ln_pmi\":" << c.left_pmi
1022 << "," << "\"rn_pmi\":" << c.right_pmi << "," << "\"ldaf\":" << c.ldaf
1023 << "," << "\"win\":" << c.window << "," << "\"afwin\":" << c.af_window
1024 << "}";
Marc Kupietzc8ddf452018-01-07 21:33:12 +01001025 }
Marc Kupietze9627152019-02-04 12:32:12 +01001026 s << "]}\n";
Marc Kupietz0421d092021-03-13 18:05:14 +01001027 // std::cout << s.str();
Marc Kupietzc8ddf452018-01-07 21:33:12 +01001028 return s.str();
1029}
1030
Marc Kupietz39887082024-11-22 18:06:20 +01001031typedef CollocatorDB COLLOCATORS;
Marc Kupietz06c9a9f2018-01-02 16:56:43 +01001032
1033extern "C" {
Marc Kupietz12af0192021-03-13 18:05:14 +01001034#ifdef __clang__
1035#pragma clang diagnostic push
1036#pragma ide diagnostic ignored "OCUnusedGlobalDeclarationInspection"
1037#endif
Marc Kupietz39887082024-11-22 18:06:20 +01001038DLL_EXPORT COLLOCATORS *open_collocatordb_for_write(char *dbname) {
1039 return new CollocatorDB(dbname, false);
1040}
Marc Kupietz12af0192021-03-13 18:05:14 +01001041
Marc Kupietz2f65bcf2026-08-02 14:04:37 +02001042DLL_EXPORT void close_collocatordb(COLLOCATORS *db) { delete db; }
1043
Marc Kupietz39887082024-11-22 18:06:20 +01001044DLL_EXPORT COLLOCATORS *open_collocatordb(char *dbname) {
1045 return new CollocatorDB(dbname, true);
1046}
Marc Kupietz06c9a9f2018-01-02 16:56:43 +01001047
Marc Kupietz39887082024-11-22 18:06:20 +01001048DLL_EXPORT void inc_collocator(COLLOCATORS *db, uint32_t w1, uint32_t w2,
1049 int8_t dist) {
1050 db->inc(w1, w2, dist);
1051}
Marc Kupietzc8ddf452018-01-07 21:33:12 +01001052
Marc Kupietz39887082024-11-22 18:06:20 +01001053DLL_EXPORT void dump_collocators(COLLOCATORS *db, uint32_t w1, uint32_t w2,
1054 int8_t dist) {
1055 db->dump(w1, w2, dist);
1056}
Marc Kupietzc8ddf452018-01-07 21:33:12 +01001057
Marc Kupietz39887082024-11-22 18:06:20 +01001058DLL_EXPORT COLLOCATORS *get_collocators(COLLOCATORS *db, uint32_t w1) {
1059 std::vector<Collocator> c = db->get_collocators(w1);
1060 if (c.empty())
1061 return nullptr;
Marc Kupietz2f65bcf2026-08-02 14:04:37 +02001062 /* one entry more than there are collocators, terminated with w2 == 0 and
1063 raw == 0, so that callers can tell where the array ends */
1064 uint64_t size = (c.size() + 1) * sizeof c[0];
Marc Kupietz39887082024-11-22 18:06:20 +01001065 auto *p = (COLLOCATORS *)malloc(size);
Marc Kupietz2f65bcf2026-08-02 14:04:37 +02001066 memset(p, 0, size);
1067 memcpy(p, c.data(), c.size() * sizeof c[0]);
Marc Kupietz39887082024-11-22 18:06:20 +01001068 return p;
1069}
Marc Kupietz88d116b2021-03-13 18:05:14 +01001070
Marc Kupietz39887082024-11-22 18:06:20 +01001071DLL_EXPORT COLLOCATORS *get_collocation_scores(COLLOCATORS *db, uint32_t w1,
1072 uint32_t w2) {
1073 std::vector<Collocator> c = db->get_collocation_scores(w1, w2);
1074 if (c.empty())
1075 return nullptr;
Marc Kupietz2f65bcf2026-08-02 14:04:37 +02001076 /* one entry more than there are collocators, terminated with w2 == 0 and
1077 raw == 0, so that callers can tell where the array ends */
1078 uint64_t size = (c.size() + 1) * sizeof c[0];
Marc Kupietz39887082024-11-22 18:06:20 +01001079 auto *p = (COLLOCATORS *)malloc(size);
Marc Kupietz2f65bcf2026-08-02 14:04:37 +02001080 memset(p, 0, size);
1081 memcpy(p, c.data(), c.size() * sizeof c[0]);
Marc Kupietz39887082024-11-22 18:06:20 +01001082 return p;
1083}
Marc Kupietzca3a52e2018-06-05 14:16:23 +02001084
Marc Kupietz39887082024-11-22 18:06:20 +01001085DLL_EXPORT char *get_word(COLLOCATORS *db, uint32_t w) {
1086 return strdup(db->getWord(w).c_str());
1087}
Marc Kupietz979580e2024-11-21 18:05:07 +01001088
Marc Kupietz39887082024-11-22 18:06:20 +01001089DLL_EXPORT uint64_t get_word_id(COLLOCATORS *db, char *word) {
1090 return db->getWordId(word);
1091}
Marc Kupietzb4a683c2021-03-14 09:19:44 +01001092
Marc Kupietz39887082024-11-22 18:06:20 +01001093DLL_EXPORT void read_vocab(COLLOCATORS *db, char *fname) {
1094 std::string fName(fname);
1095 db->readVocab(fName);
1096}
Marc Kupietz88d116b2021-03-13 18:05:14 +01001097
Marc Kupietz39887082024-11-22 18:06:20 +01001098DLL_EXPORT const char *get_collocators_as_json(COLLOCATORS *db, uint32_t w1) {
1099 return strdup(db->collocators2json(w1, db->get_collocators(w1)).c_str());
1100}
Marc Kupietzb4a683c2021-03-14 09:19:44 +01001101
Marc Kupietz39887082024-11-22 18:06:20 +01001102DLL_EXPORT const char *
1103get_collocation_scores_as_json(COLLOCATORS *db, uint32_t w1, uint32_t w2) {
1104 return strdup(
1105 db->collocators2json(w1, db->get_collocation_scores(w1, w2)).c_str());
1106}
1107
1108DLL_EXPORT const char *get_version() { return PROJECT_VERSION; }
Marc Kupietz6208fd72024-11-15 15:46:19 +01001109
Marc Kupietzd26b1052024-12-10 16:56:39 +01001110DLL_EXPORT uint64_t get_corpus_size(COLLOCATORS *db) { return db->getCorpusSize(); };
1111
Marc Kupietz21b964c2024-12-10 17:10:50 +01001112DLL_EXPORT uint64_t get_word_frequency(COLLOCATORS *db, uint64_t w1) {
1113 return db->getWordFrequency(w1);
1114}
1115
Marc Kupietz12af0192021-03-13 18:05:14 +01001116#ifdef __clang__
1117#pragma clang diagnostic push
1118#endif
Marc Kupietz06c9a9f2018-01-02 16:56:43 +01001119}