blob: 79d038dacbd4a0420d5fa7794ab2aa29369f7b2a [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 Kupietz28cc53e2017-12-23 17:24:55 +010018#include <rocksdb/merge_operator.h>
Marc Kupietzc8ddf452018-01-07 21:33:12 +010019#include <rocksdb/slice_transform.h>
Marc Kupietz65d44792026-07-31 09:46:34 +090020#include <rocksdb/version.h>
Marc Kupietz39887082024-11-22 18:06:20 +010021#include <sstream> // for ostringstream
22#include <string>
Marc Kupietzc630c152025-01-23 11:17:47 +010023#include <thread>
Marc Kupietz65d44792026-07-31 09:46:34 +090024#include <utility>
Marc Kupietz39887082024-11-22 18:06:20 +010025#include <vector>
Marc Kupietz28cc53e2017-12-23 17:24:55 +010026
Marc Kupietz65d44792026-07-31 09:46:34 +090027/* Since rocksdb 11 DB::Open() and DB::OpenForReadOnly() hand the database back
28 as a unique_ptr instead of a raw pointer. */
29#if ROCKSDB_MAJOR >= 11
30#define ROCKSDB_DB_HANDLE std::unique_ptr<rocksdb::DB>
31#define ROCKSDB_DB_RELEASE(handle) (handle).release()
32#else
33#define ROCKSDB_DB_HANDLE rocksdb::DB *
34#define ROCKSDB_DB_RELEASE(handle) (handle)
35#endif
36
Marc Kupietz75af60f2019-01-22 22:34:29 +010037#define WINDOW_SIZE 5
Marc Kupietz98cbcdc2019-01-21 17:11:27 +010038#define FREQUENCY_THRESHOLD 5
Marc Kupietz28cc53e2017-12-23 17:24:55 +010039#define IS_BIG_ENDIAN (*(uint16_t *)"\0\xff" < 0x100)
Marc Kupietz39887082024-11-22 18:06:20 +010040#define encodeCollocation(w1, w2, dist) \
41 (((uint64_t)dist << 56) | ((uint64_t)w2 << 24) | w1)
Marc Kupietz18375e12017-12-24 10:11:18 +010042#define W1(key) (uint64_t)(key & 0xffffff)
43#define W2(key) (uint64_t)((key >> 24) & 0xffffff)
44#define DIST(key) (int8_t)((uint64_t)((key >> 56) & 0xff))
Marc Kupietzc8ddf452018-01-07 21:33:12 +010045
46typedef struct {
47 uint64_t freq;
48 char *word;
Marc Kupietz12af0192021-03-13 18:05:14 +010049} vocab_entry;
Marc Kupietzc8ddf452018-01-07 21:33:12 +010050
51// typedef struct Collocator {
52// uint64_t w2;
53// uint64_t sum;
54// };
55
Marc Kupietz28cc53e2017-12-23 17:24:55 +010056using namespace rocksdb;
Marc Kupietzc8ddf452018-01-07 21:33:12 +010057using namespace std;
Marc Kupietz28cc53e2017-12-23 17:24:55 +010058
Marc Kupietz4b799e92018-01-02 11:04:56 +010059namespace rocksdb {
Marc Kupietz39887082024-11-22 18:06:20 +010060class Collocator {
61public:
62 uint32_t w2;
63 uint64_t f2;
64 uint64_t raw;
65 double pmi;
66 double npmi;
67 double llr;
68 double lfmd;
69 double md;
Marc Kupietze889cec2024-11-23 12:08:42 +010070 double md_nws;
Marc Kupietz39887082024-11-22 18:06:20 +010071 uint64_t left_raw;
72 uint64_t right_raw;
73 double left_pmi;
74 double right_pmi;
75 double dice;
76 double logdice;
77 double ldaf;
78 int window;
79 int af_window;
Marc Kupietz28cc53e2017-12-23 17:24:55 +010080};
Marc Kupietz06c9a9f2018-01-02 16:56:43 +010081
Marc Kupietz39887082024-11-22 18:06:20 +010082size_t num_merge_operator_calls;
83
84void resetNumMergeOperatorCalls() { num_merge_operator_calls = 0; }
85
86size_t num_partial_merge_calls;
87
88void resetNumPartialMergeCalls() { num_partial_merge_calls = 0; }
89
90inline void EncodeFixed64(char *buf, uint64_t value) {
91 if (!IS_BIG_ENDIAN) {
92 memcpy(buf, &value, sizeof(value));
93 } else {
94 buf[0] = value & 0xff;
95 buf[1] = (value >> 8) & 0xff;
96 buf[2] = (value >> 16) & 0xff;
97 buf[3] = (value >> 24) & 0xff;
98 buf[4] = (value >> 32) & 0xff;
99 buf[5] = (value >> 40) & 0xff;
100 buf[6] = (value >> 48) & 0xff;
101 buf[7] = (value >> 56) & 0xff;
102 }
Marc Kupietz4a5e08a2018-06-05 11:07:11 +0200103}
104
Marc Kupietz39887082024-11-22 18:06:20 +0100105inline uint32_t DecodeFixed32(const char *ptr) {
106 if (!IS_BIG_ENDIAN) {
107 // Load the raw bytes
108 uint32_t result;
109 memcpy(&result, ptr, sizeof(result)); // gcc optimizes this to a plain load
110 return result;
111 } else {
112 return ((static_cast<uint32_t>(static_cast<unsigned char>(ptr[0]))) |
113 (static_cast<uint32_t>(static_cast<unsigned char>(ptr[1])) << 8) |
114 (static_cast<uint32_t>(static_cast<unsigned char>(ptr[2])) << 16) |
115 (static_cast<uint32_t>(static_cast<unsigned char>(ptr[3])) << 24));
116 }
117}
118
119inline uint64_t DecodeFixed64(const char *ptr) {
120 if (!IS_BIG_ENDIAN) {
121 // Load the raw bytes
122 uint64_t result;
123 memcpy(&result, ptr, sizeof(result)); // gcc optimizes this to a plain load
124 return result;
125 } else {
126 uint64_t lo = DecodeFixed32(ptr);
127 uint64_t hi = DecodeFixed32(ptr + 4);
128 return (hi << 32) | lo;
129 }
130}
131
132static inline double ca_pmi(uint64_t f1, uint64_t f2, uint64_t f12,
133 uint64_t total, double window_size) {
134 double r1 = f1 * window_size, c1 = f2, e = r1 * c1 / total, o = f12;
135 if (f12 < FREQUENCY_THRESHOLD)
136 return -1.0;
137 else
138 return log2(o / e);
139}
140
141// Bouma, Gerlof (2009): <a
142// href="https://svn.spraakdata.gu.se/repos/gerlof/pub/www/Docs/npmi-pfd.pdf">
143// Normalized (pointwise) mutual information in collocation extraction</a>. In
144// Proceedings of GSCL.
145static double ca_npmi(uint64_t f1, uint64_t f2, uint64_t f12,
146 uint64_t total, double window_size) {
147 double r1 = f1 * window_size, c1 = f2, e = r1 * c1 / total, o = f12;
148 if (f12 < FREQUENCY_THRESHOLD)
149 return -1.0;
150 else
151 return log2(o / e) / (-log2(o / total / window_size));
152}
153
154// Thanopoulos, A., Fakotakis, N., Kokkinakis, G.: Comparative evaluation of
155// collocation extraction metrics. In: International Conference on Language
156// Resources and Evaluation (LREC-2002). (2002) 620–625 double md =
157// log2(pow((double)max * window_size / total, 2) / (window_size *
158// ((double)_vocab[w1].freq/total) * ((double)_vocab[last_w2].freq/total)));
159static double ca_md(uint64_t f1, uint64_t f2, uint64_t f12,
160 uint64_t total, double window_size) {
161 const double r1 = f1 * window_size;
162 const double c1 = f2;
163 const double e = r1 * c1 / total;
164 const double o = f12;
165 return log2(o * o / e);
166}
167
168static double ca_lfmd(uint64_t f1, uint64_t f2, uint64_t f12,
169 uint64_t total, double window_size) {
170 double r1 = f1 * window_size, c1 = f2, e = r1 * c1 / total, o = f12;
171 if (f12 == 0)
172 return 0;
173 return log2(o * o * o / e);
174}
175
176// Evert, Stefan (2004): The Statistics of Word Cooccurrences: Word Pairs and
177// Collocations. PhD dissertation, IMS, University of Stuttgart. Published in
178// 2005, URN urn:nbn:de:bsz:93-opus-23714. Free PDF available from
179// http://purl.org/stefan.evert/PUB/Evert2004phd.pdf
180static double ca_ll(uint64_t w1, uint64_t w2, uint64_t w12, uint64_t n,
181 uint64_t window_size) {
182 double r1 = (double)w1 * window_size, r2 = (double)n - r1, c1 = w2,
183 c2 = n - c1, o11 = w12, o12 = r1 - o11, o21 = c1 - w12, o22 = r2 - o21,
184 e11 = r1 * c1 / n, e12 = r1 * c2 / n, e21 = r2 * c1 / n,
185 e22 = r2 * c2 / n;
186 return (2 * ((o11 > 0 ? o11 * log(o11 / e11) : 0) +
187 (o12 > 0 ? o12 * log(o12 / e12) : 0) +
188 (o21 > 0 ? o21 * log(o21 / e21) : 0) +
189 (o22 > 0 ? o22 * log(o22 / e22) : 0)));
190}
191
192static double ca_dice(uint64_t w1, uint64_t w2, uint64_t w12, uint64_t n,
193 uint64_t window_size) {
194 double r1 = (double)w1 * window_size, c1 = w2;
195 return 2 * w12 / (c1 + r1);
196}
197
198// Rychlý, Pavel (2008): <a
199// href="http://www.fi.muni.cz/usr/sojka/download/raslan2008/13.pdf">A
200// lexicographer-friendly association score.</a> In Proceedings of Recent
201// Advances in Slavonic Natural Language Processing, RASLAN, 6–9.
202static double ca_logdice(uint64_t w1, uint64_t w2, uint64_t w12,
203 uint64_t n, uint64_t window_size) {
204 double r1 = (double)w1 * window_size, c1 = w2;
205 return 14 + log2(2 * w12 / (c1 + r1));
206}
207
208class CountMergeOperator : public AssociativeMergeOperator {
209public:
210 CountMergeOperator() {
211 mergeOperator_ = MergeOperators::CreateUInt64AddOperator();
212 }
213
214 bool Merge(const Slice &key, const Slice *existing_value,
215 const Slice &value, std::string *new_value,
216 Logger *logger) const override {
217 assert(new_value->empty());
218 ++num_merge_operator_calls;
219 if (existing_value == nullptr) {
220 new_value->assign(value.data(), value.size());
221 return true;
222 }
223
224 return mergeOperator_->PartialMerge(key, *existing_value, value, new_value,
225 logger);
226 }
227
228 const char *Name() const override { return "UInt64AddOperator"; }
229
230private:
231 std::shared_ptr<MergeOperator> mergeOperator_;
232};
233
234class CollocatorIterator : public Iterator {
235 char prefixc[sizeof(uint64_t)]{};
236 Iterator *base_iterator_;
237
238public:
239 explicit CollocatorIterator(Iterator *base_iterator) : base_iterator_(base_iterator) {}
240
Marc Kupietzaa354d82026-07-31 09:17:57 +0900241 /* Takes ownership of the iterator handed in by SeekIterator(). Without this
242 every seek leaked a rocksdb iterator and the resources it pins. */
243 ~CollocatorIterator() override { delete base_iterator_; }
244
Marc Kupietz39887082024-11-22 18:06:20 +0100245 void setPrefix(char *prefix) { memcpy(prefixc, prefix, sizeof(uint64_t)); }
246
247 void SeekToFirst() override { base_iterator_->SeekToFirst(); }
248
249 void SeekToLast() override { base_iterator_->SeekToLast(); }
250
251 void Seek(const rocksdb::Slice &s) override { base_iterator_->Seek(s); }
252
253 void SeekForPrev(const rocksdb::Slice &s) override {
254 base_iterator_->SeekForPrev(s);
255 }
256
257 void Prev() override { base_iterator_->Prev(); }
258
259 void Next() override { base_iterator_->Next(); }
260
261 Slice key() const override;
262
263 Slice value() const override;
264
265 Status status() const override;
266
267 bool Valid() const override;
268
269 bool isValid();
270
271 uint64_t intValue();
272
273 uint64_t intKey();
274};
275
276// rocksdb::CollocatorIterator::CollocatorIterator(Iterator* base_iterator) {}
277
278bool CollocatorIterator::Valid() const {
279 return base_iterator_->Valid() && key().starts_with(std::string(prefixc, 3));
280}
281
282bool CollocatorIterator::isValid() {
283 return base_iterator_->Valid() && key().starts_with(std::string(prefixc, 3));
284 // return key().starts_with(std::string(prefixc,3));
285}
286
287uint64_t CollocatorIterator::intKey() {
288 return DecodeFixed64(base_iterator_->key().data());
289}
290
291uint64_t CollocatorIterator::intValue() {
292 return DecodeFixed64(base_iterator_->value().data());
293}
294
295class VocabEntry {
296public:
297 string word;
298 uint64_t freq;
299};
300
301class CollocatorDB {
302 WriteOptions merge_option_; // for merge
Marc Kupietz2f65bcf2026-08-02 14:04:37 +0200303 // to repeat a write that rocksdb cancelled, rather than lose the count
304 WriteOptions blocking_merge_option_;
305 std::atomic<uint64_t> stalled_writes_{0};
306 std::atomic<uint64_t> failed_writes_{0};
Marc Kupietz39887082024-11-22 18:06:20 +0100307 char _one[sizeof(uint64_t)]{};
308 Slice _one_slice;
309 vector<VocabEntry> _vocab;
310 uint64_t total = 0;
311 uint64_t sentences = 0;
312 float avg_window_size = 8.0;
313
314protected:
315 std::shared_ptr<DB> db_;
316
317 WriteOptions put_option_;
318 ReadOptions get_option_;
319 WriteOptions delete_option_;
320
321 uint64_t default_{};
322
323 std::shared_ptr<DB> OpenDb(const char *dbname);
324
325 std::shared_ptr<DB> OpenDbForRead(const char *dbname);
326
327public:
Marc Kupietz2f65bcf2026-08-02 14:04:37 +0200328 virtual ~CollocatorDB(); // flushes what is still in memory, see close()
Marc Kupietz39887082024-11-22 18:06:20 +0100329 void readVocab(const string& fname);
330 string getWord(uint32_t w1);
331
332 uint64_t getWordId(const char *word) const;
333
Marc Kupietzd26b1052024-12-10 16:56:39 +0100334 uint64_t getCorpusSize() const;
335
Marc Kupietz21b964c2024-12-10 17:10:50 +0100336 uint64_t getWordFrequency(uint64_t w1);
337
Marc Kupietz39887082024-11-22 18:06:20 +0100338 CollocatorDB(const char *db_name, bool read_only);
339
340 // public interface of CollocatorDB.
341 // All four functions return false
342 // if the underlying level db operation failed.
343
344 // mapped to a levedb Put
345 bool set(const std::string &key, uint64_t value) {
346 // just treat the internal rep of int64 as the string
347 char buf[sizeof(value)];
348 EncodeFixed64(buf, value);
349 Slice slice(buf, sizeof(value));
350 auto s = db_->Put(put_option_, key, slice);
351
352 if (s.ok()) {
353 return true;
354 } else {
355 std::cerr << s.ToString() << std::endl;
356 return false;
357 }
358 }
359
360 DB *getDb() { return db_.get(); }
361
362 // mapped to a rocksdb Delete
363 bool remove(const std::string &key) {
364 auto s = db_->Delete(delete_option_, key);
365
366 if (s.ok()) {
367 return true;
368 } else {
369 std::cerr << s.ToString() << std::endl;
370 return false;
371 }
372 }
373
374 // mapped to a rocksdb Get
375 bool get(const std::string &key, uint64_t *value) {
376 std::string str;
377 auto s = db_->Get(get_option_, key, &str);
378
379 if (s.IsNotFound()) {
380 // return default value if not found;
381 *value = default_;
382 return true;
383 } else if (s.ok()) {
384 // deserialization
385 if (str.size() != sizeof(uint64_t)) {
386 std::cerr << "value corruption\n";
387 return false;
388 }
389 *value = DecodeFixed64(&str[0]);
390 return true;
391 } else {
392 std::cerr << s.ToString() << std::endl;
393 return false;
394 }
395 }
396
397 uint64_t get(const uint32_t w1, const uint32_t w2, const int8_t dist) {
398 char encoded_key[sizeof(uint64_t)];
399 EncodeFixed64(encoded_key, encodeCollocation(w1, w2, dist));
400 uint64_t value = default_;
401 get(std::string(encoded_key, 8), &value);
402 return value;
403 }
404
Marc Kupietz2f65bcf2026-08-02 14:04:37 +0200405 /* A merge that does not lose the count. rocksdb cancels a write with
406 Status::Incomplete instead of waiting when it is asked not to slow down,
407 and then the increment is simply gone. Such a write was not applied, so
408 repeating it blocking cannot count twice. */
409 void merge_one(const Slice &key) {
410 Status s = db_->Merge(merge_option_, key, _one_slice);
411 if (s.ok())
412 return;
413 if (s.IsIncomplete()) {
414 ++stalled_writes_;
415 s = db_->Merge(blocking_merge_option_, key, _one_slice);
416 if (s.ok())
417 return;
418 }
419 if (failed_writes_++ == 0)
420 std::cerr << "collocatordb: cannot write, counts are lost: " << s.ToString()
421 << std::endl;
422 }
423
Marc Kupietz39887082024-11-22 18:06:20 +0100424 virtual void inc(const std::string &key) {
Marc Kupietz2f65bcf2026-08-02 14:04:37 +0200425 merge_one(Slice(key));
Marc Kupietz39887082024-11-22 18:06:20 +0100426 }
427
428 void inc(const uint64_t key) {
429 char encoded_key[sizeof(uint64_t)];
430 EncodeFixed64(encoded_key, key);
Marc Kupietz2f65bcf2026-08-02 14:04:37 +0200431 merge_one(Slice(encoded_key, sizeof(uint64_t)));
Marc Kupietz39887082024-11-22 18:06:20 +0100432 }
433
434 virtual void inc(uint32_t w1, uint32_t w2, uint8_t dist);
435
436 void dump(uint32_t w1, uint32_t w2, int8_t dist) const;
437
438 vector<Collocator> get_collocators(uint32_t w1);
439
440 vector<Collocator> get_collocators(uint32_t w1, uint32_t max_w2);
441
442 vector<Collocator> get_collocation_scores(uint32_t w1, uint32_t w2);
443
444 vector<Collocator> get_collocators(uint32_t w1, uint32_t min_w2,
445 uint32_t max_w2);
446
447 void applyCAMeasures(uint32_t w1, uint32_t w2,
448 uint64_t *sumWindow, uint64_t sum,
449 int usedPositions, int true_window_size,
450 Collocator *result) const;
451
452 void dumpSparseLlr(uint32_t w1, uint32_t min_cooccur);
453
454 string collocators2json(uint32_t w1, const vector<Collocator>& collocators);
455
456 // mapped to a rocksdb Merge operation
457 virtual bool add(const std::string &key, uint64_t value) {
458 char encoded[sizeof(uint64_t)];
459 EncodeFixed64(encoded, value);
460 Slice slice(encoded, sizeof(uint64_t));
461 auto s = db_->Merge(merge_option_, key, slice);
462
463 if (s.ok()) {
464 return true;
465 } else {
466 std::cerr << s.ToString() << std::endl;
467 return false;
468 }
469 }
470
471 CollocatorIterator *SeekIterator(uint64_t w1, uint64_t w2, int8_t dist) const;
Marc Kupietz2f65bcf2026-08-02 14:04:37 +0200472
473 /* Writes what is still in memory and closes the database. Without this
474 everything that has not been flushed yet is lost when the process ends,
475 because the write ahead log is switched off for speed. */
476 void close() {
477 if (!db_)
478 return;
479 if (stalled_writes_ > 0)
480 std::cerr << "collocatordb: repeated " << stalled_writes_
481 << " writes that rocksdb had cancelled" << std::endl;
482 if (failed_writes_ > 0)
483 std::cerr << "collocatordb: " << failed_writes_
484 << " writes failed, the database is missing counts" << std::endl;
485 FlushOptions flush_options;
486 flush_options.wait = true;
487 Status s = db_->Flush(flush_options);
488 if (!s.ok() && !s.IsNotSupported())
489 std::cerr << "collocatordb: cannot write what is still in memory: "
490 << s.ToString() << std::endl;
491 db_.reset();
492 }
493
Marc Kupietz39887082024-11-22 18:06:20 +0100494};
495
Marc Kupietz2f65bcf2026-08-02 14:04:37 +0200496CollocatorDB::~CollocatorDB() { close(); }
497
Marc Kupietz39887082024-11-22 18:06:20 +0100498CollocatorDB::CollocatorDB(const char *db_name,
499 bool read_only = false) {
500 // merge_option_.sync = true;
501 if (read_only)
502 db_ = OpenDbForRead(strdup(db_name));
503 else
504 db_ = OpenDb(db_name);
505 assert(db_);
506 uint64_t one = 1;
507 EncodeFixed64(_one, one);
508 _one_slice = Slice(_one, sizeof(uint64_t));
509}
510
511void CollocatorDB::inc(const uint32_t w1, const uint32_t w2,
512 const uint8_t dist) {
513 inc(encodeCollocation(w1, w2, dist));
514}
515
516void CollocatorDB::readVocab(const string& fname) {
517 char strbuf[2048];
518 uint64_t freq;
519 FILE *fin = fopen(fname.c_str(), "rb");
520 if (fin == nullptr) {
521 cout << "Vocabulary file " << fname << " not found\n";
522 exit(1);
523 }
524 uint64_t i = 0;
525 while (fscanf(fin, "%s %lu", strbuf, &freq) == 2) {
526 _vocab.push_back({strbuf, freq});
527 total += freq;
528 i++;
529 }
530 fclose(fin);
531
532 char size_fname[256];
533 strcpy(size_fname, fname.c_str());
534 char *pos = strstr(size_fname, ".vocab");
535 if (pos) {
536 *pos = 0;
537 strcat(size_fname, ".size");
538 FILE *fp = fopen(size_fname, "r");
539 if (fp != nullptr) {
540 fscanf(fp, "%lu", &sentences);
541 fscanf(fp, "%lu", &total);
542 float sl = (float)total / (float)sentences;
543 float w = WINDOW_SIZE;
544 avg_window_size =
545 ((sl > 2 * w ? (sl - 2 * w) * 2 * w : 0) + (double)w * (3 * w - 1)) /
546 sl;
547 fprintf(stdout,
548 "Size corrections found: corpus size: %lu tokens in %lu "
549 "sentences, avg. sentence size: %f, avg. window size: %f\n",
550 total, sentences, sl, avg_window_size);
551 fclose(fp);
552 } else {
553 // std::cout << "size file " << size_fname << " not found\n";
554 }
555 } else {
556 std::cout << "cannot determine size file " << size_fname << "\n";
557 }
558}
559
560std::shared_ptr<DB> CollocatorDB::OpenDbForRead(const char *name) {
Marc Kupietz65d44792026-07-31 09:46:34 +0900561 ROCKSDB_DB_HANDLE db;
Marc Kupietz39887082024-11-22 18:06:20 +0100562 Options options;
563 options.env->SetBackgroundThreads(4);
564 options.create_if_missing = true;
565 options.merge_operator = std::make_shared<CountMergeOperator>();
566 options.max_successive_merges = 0;
567 // options.prefix_extractor.reset(NewFixedPrefixTransform(8));
568 options.IncreaseParallelism();
569 options.OptimizeLevelStyleCompaction();
570 options.prefix_extractor.reset(NewFixedPrefixTransform(3));
571 ostringstream dbname, vocabname;
572 dbname << name << ".rocksdb";
573 auto s = DB::OpenForReadOnly(options, dbname.str(), &db);
574 if (!s.ok()) {
575 std::cerr << s.ToString() << std::endl;
576 assert(false);
577 }
578 vocabname << name << ".vocab";
579 readVocab(vocabname.str());
Marc Kupietz65d44792026-07-31 09:46:34 +0900580 return std::shared_ptr<DB>(ROCKSDB_DB_RELEASE(db));
Marc Kupietz39887082024-11-22 18:06:20 +0100581}
582
Marc Kupietz2f65bcf2026-08-02 14:04:37 +0200583 /* Reads a size from the environment, so that a long indexing run can be
584 tuned without recompiling. Returns the default when unset or unusable. */
585 static uint64_t env_size(const char *name, uint64_t fallback) {
586 const char *value = getenv(name);
587 if (value == nullptr)
588 return fallback;
589 char *end = nullptr;
590 unsigned long long parsed = strtoull(value, &end, 10);
591 if (end == value || parsed == 0) {
592 std::cerr << "collocatordb: ignoring " << name << "=" << value << std::endl;
593 return fallback;
594 }
595 return (uint64_t)parsed;
596 }
597
Marc Kupietzc630c152025-01-23 11:17:47 +0100598 std::shared_ptr<DB> CollocatorDB::OpenDb(const char *dbname) {
Marc Kupietz65d44792026-07-31 09:46:34 +0900599 ROCKSDB_DB_HANDLE db;
Marc Kupietzc630c152025-01-23 11:17:47 +0100600 Options options;
Marc Kupietz39887082024-11-22 18:06:20 +0100601
Marc Kupietzc630c152025-01-23 11:17:47 +0100602 int max_cores = static_cast<int>(std::thread::hardware_concurrency());
603
Marc Kupietzc630c152025-01-23 11:17:47 +0100604 options.create_if_missing = true;
605 options.merge_operator = std::make_shared<CountMergeOperator>();
Marc Kupietzc630c152025-01-23 11:17:47 +0100606
Marc Kupietz2f65bcf2026-08-02 14:04:37 +0200607 /* Indexing a corpus is one long stream of merge operands for the same
608 keys. rocksdb only collapses them when memtables are merged and when
609 files are compacted, so the settings aim at doing that early and often -
610 every collapsed operand is one less to write, to read and to merge
611 again later.
Marc Kupietzc630c152025-01-23 11:17:47 +0100612
Marc Kupietz2f65bcf2026-08-02 14:04:37 +0200613 All of it can be overridden from the environment, to be able to tune a
614 run that takes days without recompiling. */
615 options.write_buffer_size = env_size("COLLOCATORDB_WRITE_BUFFER_MB", 256) << 20;
616 options.max_write_buffer_number = (int)env_size("COLLOCATORDB_WRITE_BUFFERS", 8);
617 /* collapses the operands of several memtables before they are written */
618 options.min_write_buffer_number_to_merge =
619 (int)env_size("COLLOCATORDB_WRITE_BUFFERS_TO_MERGE", 4);
620
621 /* Compaction has to keep up with the writer, otherwise the level 0 files
622 pile up and every read has to merge through all of them. */
623 options.max_background_jobs =
624 (int)env_size("COLLOCATORDB_BACKGROUND_JOBS",
625 max_cores > 4 ? (max_cores < 32 ? max_cores : 32) : 4);
626 options.max_subcompactions = (int)env_size("COLLOCATORDB_SUBCOMPACTIONS", 4);
627 options.level0_file_num_compaction_trigger = 4;
628 options.level0_slowdown_writes_trigger = 20;
629 options.level0_stop_writes_trigger = 36;
630
631 /* Merge operands are inserted one writer at a time, rocksdb does not
632 support concurrent memtable writes for them, which is why more threads
633 in the indexer do not help beyond a certain point. */
634 options.allow_concurrent_memtable_write = false;
Marc Kupietzc630c152025-01-23 11:17:47 +0100635 options.enable_write_thread_adaptive_yield = true;
Marc Kupietzc630c152025-01-23 11:17:47 +0100636 options.allow_mmap_reads = true;
637
Marc Kupietzc630c152025-01-23 11:17:47 +0100638 BlockBasedTableOptions table_options;
Marc Kupietz2f65bcf2026-08-02 14:04:37 +0200639 table_options.block_cache =
640 NewLRUCache(env_size("COLLOCATORDB_BLOCK_CACHE_MB", 512) << 20);
Marc Kupietzc630c152025-01-23 11:17:47 +0100641 options.table_factory.reset(NewBlockBasedTableFactory(table_options));
642
Marc Kupietz2f65bcf2026-08-02 14:04:37 +0200643 /* No write ahead log: an interrupted indexing run is repeated, and the log
644 would double the amount written. Everything that has not been flushed is
645 lost when the process dies, which is what close_collocatordb() is for. */
646 merge_option_.disableWAL = true;
647 merge_option_.sync = false;
648 /* Let rocksdb slow the writer down when compaction falls behind, instead
649 of cancelling the write. It used to be told to do neither, which threw
650 counts away. merge_one() repeats a cancelled write, whatever the
651 settings are. */
652 merge_option_.low_pri = false;
653 merge_option_.no_slowdown = false;
654 blocking_merge_option_ = merge_option_;
655 blocking_merge_option_.low_pri = false;
656 blocking_merge_option_.no_slowdown = false;
Marc Kupietzc630c152025-01-23 11:17:47 +0100657
658 Status s = DB::Open(options, dbname, &db);
659 if (!s.ok()) {
660 std::cerr << s.ToString() << std::endl;
661 assert(false);
662 }
663 total = 1000;
Marc Kupietz65d44792026-07-31 09:46:34 +0900664 return std::shared_ptr<DB>(ROCKSDB_DB_RELEASE(db));
Marc Kupietz39887082024-11-22 18:06:20 +0100665 }
Marc Kupietz39887082024-11-22 18:06:20 +0100666
667CollocatorIterator *
668CollocatorDB::SeekIterator(uint64_t w1, uint64_t w2, int8_t dist) const {
669 ReadOptions options;
670 options.prefix_same_as_start = true;
671 char prefixc[sizeof(uint64_t)];
672 EncodeFixed64(prefixc, encodeCollocation(w1, w2, dist));
673 Iterator *it = db_->NewIterator(options);
674 auto *cit = new CollocatorIterator(it);
675 if (w2 > 0)
676 cit->Seek(std::string(prefixc, 6));
677 else
678 cit->Seek(std::string(prefixc, 3));
679 cit->setPrefix(prefixc);
680 return cit;
681}
682
683void CollocatorDB::dump(uint32_t w1, uint32_t w2, int8_t dist) const {
684 auto it = std::unique_ptr<CollocatorIterator>(SeekIterator(w1, w2, dist));
685 for (; it->isValid(); it->Next()) {
686 uint64_t value = it->intValue();
687 uint64_t key = it->intKey();
688 std::cout << "w1:" << W1(key) << ", w2:" << W2(key)
689 << ", dist:" << (int32_t)DIST(key) << " - count:" << value
690 << std::endl;
691 }
692 std::cout << "ready dumping\n";
693}
694
695bool sortByNpmi(const Collocator &lhs, const Collocator &rhs) {
696 return lhs.npmi > rhs.npmi;
697}
698
699bool sortByLfmd(const Collocator &lhs, const Collocator &rhs) {
700 return lhs.lfmd > rhs.lfmd;
701}
702
703bool sortByLlr(const Collocator &lhs, const Collocator &rhs) {
704 return lhs.llr > rhs.llr;
705}
706
707bool sortByLogDice(const Collocator &lhs, const Collocator &rhs) {
708 return lhs.logdice > rhs.logdice;
709}
710
711bool sortByLogDiceAF(const Collocator &lhs, const Collocator &rhs) {
712 return lhs.ldaf > rhs.ldaf;
713}
714
715void CollocatorDB::applyCAMeasures(
716 const uint32_t w1, const uint32_t w2, uint64_t *sumWindow,
717 const uint64_t sum, const int usedPositions, int true_window_size,
718 Collocator *result) const {
719 uint64_t f1 = _vocab[w1].freq, f2 = _vocab[w2].freq;
720 double o = sum, r1 = f1 * true_window_size, c1 = f2, e = r1 * c1 / total,
721 pmi = log2(o / e), md = log2(o * o / e), lfmd = log2(o * o * o / e),
Marc Kupietze889cec2024-11-23 12:08:42 +0100722 llr = ca_ll(f1, f2, sum, total, true_window_size),
723 md_nws = ca_md(f1, f2, sum, total, 2 * WINDOW_SIZE),
724 ld = ca_logdice(f1, f2, sum, total, true_window_size);
Marc Kupietz39887082024-11-22 18:06:20 +0100725
726 int bestWindow = usedPositions;
727 double bestAF = ld;
728 // if(f1<75000000)
729 // #pragma omp parallel for reduction(max:bestAF)
730 // #pragma omp target teams distribute parallel for reduction(max:bestAF)
731 // map(tofrom:bestAF,currentAF,bestWindow,usedPositions)
732 for (int bitmask = 1; bitmask < (1 << (2 * WINDOW_SIZE)); bitmask++) {
733 if ((bitmask & usedPositions) == 0 || (bitmask & ~usedPositions) > 0)
734 continue;
735 uint64_t currentWindowSum = 0;
736 // #pragma omp target teams distribute parallel for
737 // reduction(+:currentWindowSum) map(tofrom:bitmask,usedPositions)
738 for (int pos = 0; pos < 2 * WINDOW_SIZE; pos++) {
739 if (((1 << pos) & bitmask & usedPositions) != 0)
740 currentWindowSum += sumWindow[pos];
741 }
742 double currentAF = ca_logdice(f1, f2, currentWindowSum, total,
743 __builtin_popcount(bitmask));
744 if (currentAF > bestAF) {
745 bestAF = currentAF;
746 bestWindow = bitmask;
747 }
748 }
749
750 *result = {w2,
751 f2,
752 sum,
753 pmi,
754 pmi / (-log2(o / total / true_window_size)),
755 llr,
756 lfmd,
757 md,
Marc Kupietze889cec2024-11-23 12:08:42 +0100758 md_nws,
Marc Kupietz39887082024-11-22 18:06:20 +0100759 sumWindow[WINDOW_SIZE],
760 sumWindow[WINDOW_SIZE - 1],
761 ca_pmi(f1, f2, sumWindow[WINDOW_SIZE], total, 1),
762 ca_pmi(f1, f2, sumWindow[WINDOW_SIZE - 1], total, 1),
763 ca_dice(f1, f2, sum, total, true_window_size),
764 ld,
765 bestAF,
766 usedPositions,
767 bestWindow};
768}
769
770std::vector<Collocator>
771CollocatorDB::get_collocators(uint32_t w1, uint32_t min_w2,
772 uint32_t max_w2) {
773 std::vector<Collocator> collocators;
774 uint64_t w2, last_w2 = 0xffffffffffffffff;
775 uint64_t maxv = 0, sum = 0;
Marc Kupietzaa354d82026-07-31 09:17:57 +0900776 /* fixed size, so it lives on the stack and cannot be leaked */
777 uint64_t sumWindow[2 * WINDOW_SIZE];
778 memset(sumWindow, 0, sizeof(sumWindow));
Marc Kupietz39887082024-11-22 18:06:20 +0100779 int true_window_size = 1;
780 int usedPositions = 0;
781
782 if (w1 > _vocab.size()) {
783 std::cout << w1 << "> vocabulary size " << _vocab.size() << "\n";
784 w1 -= _vocab.size();
785 }
786#ifdef DEBUG
787 std::cout << "Searching for collocates of " << _vocab[w1].word << "\n";
788#endif
789 // #pragma omp parallel num_threads(40)
790 // #pragma omp single
791 for (auto it =
792 std::unique_ptr<CollocatorIterator>(SeekIterator(w1, min_w2, 0));
793 it->isValid(); it->Next()) {
794 uint64_t value = it->intValue(), key = it->intKey();
795 if ((w2 = W2(key)) > max_w2)
796 continue;
797 if (last_w2 == 0xffffffffffffffff)
798 last_w2 = w2;
799 if (w2 != last_w2) {
800 if (sum >= FREQUENCY_THRESHOLD) {
801 collocators.push_back({});
802 Collocator *result = &(collocators[collocators.size() - 1]);
803 // #pragma omp task firstprivate(last_w2, sumWindow, sum, usedPositions,
804 // true_window_size) shared(w1, result) if(sum > 1000000)
805 {
806 // uint64_t *nsw = (uint64_t *)malloc(sizeof(uint64_t) * 2
807 // *WINDOW_SIZE); memcpy(nsw, sumWindow, sizeof(uint64_t) * 2
808 // *WINDOW_SIZE);
809 applyCAMeasures(w1, last_w2, sumWindow, sum, usedPositions,
810 true_window_size, result);
811 // free(nsw);
812 }
813 }
814 memset(sumWindow, 0, 2 * WINDOW_SIZE * sizeof(uint64_t));
815 usedPositions = 1 << (-DIST(key) + WINDOW_SIZE - (DIST(key) < 0 ? 1 : 0));
816 sumWindow[-DIST(key) + WINDOW_SIZE - (DIST(key) < 0 ? 1 : 0)] = value;
817 last_w2 = w2;
818 maxv = value;
819 sum = value;
820 true_window_size = 1;
821 if (min_w2 == max_w2 && w2 != min_w2)
822 break;
823 } else {
824 sum += value;
825 if (value > maxv)
826 maxv = value;
827 usedPositions |=
828 1 << (-DIST(key) + WINDOW_SIZE - (DIST(key) < 0 ? 1 : 0));
829 sumWindow[-DIST(key) + WINDOW_SIZE - (DIST(key) < 0 ? 1 : 0)] = value;
830 true_window_size++;
831 }
832 }
833
Marc Kupietz2f65bcf2026-08-02 14:04:37 +0200834 /* A collocate is only finished when the next one begins, so the last one is
835 still in sumWindow when the iterator is through. It used to be dropped,
836 which cost every word one of its collocates. */
837 if (last_w2 != 0xffffffffffffffff && sum >= FREQUENCY_THRESHOLD) {
838 collocators.push_back({});
839 applyCAMeasures(w1, last_w2, sumWindow, sum, usedPositions,
840 true_window_size, &(collocators[collocators.size() - 1]));
841 }
842
Marc Kupietz39887082024-11-22 18:06:20 +0100843 // #pragma omp taskwait
844 sort(collocators.begin(), collocators.end(), sortByLogDiceAF);
845
846#ifdef DEBUG
847 int i = 0;
848 for (Collocator c : collocators) {
849 if (i++ > 10)
850 break;
851 std::cout << "w1:" << _vocab[w1].word << ", w2: *" << _vocab[c.w2].word
852 << "*"
853 << "\t f(w1):" << _vocab[w1].freq
854 << "\t f(w2):" << _vocab[c.w2].freq << "\t f(w1, w2):" << c.raw
855 << "\t pmi:" << c.pmi << "\t npmi:" << c.npmi
856 << "\t llr:" << c.llr << "\t md:" << c.md << "\t lfmd:" << c.lfmd
857 << "\t total:" << total << std::endl;
858 }
859#endif
860
861 return collocators;
862}
863
864std::vector<Collocator>
865CollocatorDB::get_collocation_scores(uint32_t w1, uint32_t w2) {
866 return get_collocators(w1, w2, w2);
867}
868
869std::vector<Collocator> CollocatorDB::get_collocators(uint32_t w1) {
870 return get_collocators(w1, 0, UINT32_MAX);
871}
872
873void CollocatorDB::dumpSparseLlr(uint32_t w1, uint32_t min_cooccur) {
874 std::vector<Collocator> collocators;
875 std::stringstream stream;
876 uint64_t w2, last_w2 = 0xffffffffffffffff;
877 uint64_t maxv = 0, total_w1 = 0;
878 bool first = true;
879 for (auto it = std::unique_ptr<CollocatorIterator>(SeekIterator(w1, 0, 0));
880 it->isValid(); it->Next()) {
881 uint64_t value = it->intValue(), key = it->intKey();
882 w2 = W2(key);
883 total_w1 += value;
884 if (last_w2 == 0xffffffffffffffff)
885 last_w2 = w2;
886 if (w2 != last_w2) {
887 if (maxv >= min_cooccur) {
888 double llr =
889 ca_ll(_vocab[w1].freq, _vocab[last_w2].freq, maxv, total, 1);
890 if (first)
891 first = false;
892 else
893 stream << " ";
894 stream << w2 << " " << llr;
895 }
896 last_w2 = w2;
897 maxv = value;
898 } else {
899 if (value > maxv)
900 maxv = value;
901 }
902 }
903 if (first)
904 stream << "1 0.0";
905 stream << "\n";
906 std::cout << stream.str();
907}
908
909Slice CollocatorIterator::key() const {
910 return base_iterator_->key();
911}
912
913Slice CollocatorIterator::value() const {
914 return base_iterator_->value();
915}
916
917Status CollocatorIterator::status() const {
918 return base_iterator_->status();
919}
920
921}; // namespace rocksdb
922
923string CollocatorDB::getWord(uint32_t w1) { return _vocab[w1].word; }
924
925uint64_t CollocatorDB::getWordId(const char *word) const {
Marc Kupietz979580e2024-11-21 18:05:07 +0100926 for (uint64_t i = 0; i < _vocab.size(); i++) {
927 if (strcmp(_vocab[i].word.c_str(), word) == 0)
928 return i;
929 }
930 return 0;
931}
932
Marc Kupietzd26b1052024-12-10 16:56:39 +0100933uint64_t CollocatorDB::getCorpusSize() const {
934 return total;
935}
936
Marc Kupietz21b964c2024-12-10 17:10:50 +0100937uint64_t CollocatorDB::getWordFrequency(uint64_t w1) {
938 return _vocab[w1].freq;
939}
940
Marc Kupietz39887082024-11-22 18:06:20 +0100941string CollocatorDB::collocators2json(uint32_t w1,
942 const vector<Collocator>& collocators) {
Marc Kupietzc8ddf452018-01-07 21:33:12 +0100943 ostringstream s;
Marc Kupietz0dd86ef2018-01-11 22:23:17 +0100944 int i = 0;
Marc Kupietz39887082024-11-22 18:06:20 +0100945 s << " { \"f1\": " << _vocab[w1].freq << "," << R"("w1":")"
946 << string(_vocab[w1].word) << "\", " << "\"N\": " << total << ", "
947 << "\"collocates\": [";
Marc Kupietzc8ddf452018-01-07 21:33:12 +0100948 bool first = true;
949 for (Collocator c : collocators) {
Marc Kupietz39887082024-11-22 18:06:20 +0100950 if (strncmp(_vocab[c.w2].word.c_str(), "quot", 4) == 0)
951 continue;
Marc Kupietz0dd86ef2018-01-11 22:23:17 +0100952 if (i++ > 200)
953 break;
Marc Kupietz12af0192021-03-13 18:05:14 +0100954 if (!first)
Marc Kupietzc8ddf452018-01-07 21:33:12 +0100955 s << ",\n";
956 else
957 first = false;
958 s << "{"
Marc Kupietz39887082024-11-22 18:06:20 +0100959 "\"word\":\""
960 << (string(_vocab[c.w2].word) == "<num>"
961 ? string("###")
962 : string(_vocab[c.w2].word))
963 << "\"," << "\"f2\":" << c.f2 << "," << "\"f\":" << c.raw << ","
964 << "\"npmi\":" << c.npmi << "," << "\"pmi\":" << c.pmi << ","
965 << "\"llr\":" << c.llr << "," << "\"lfmd\":" << c.lfmd << ","
Marc Kupietze889cec2024-11-23 12:08:42 +0100966 << "\"md\":" << c.md << "," << "\"md_nws\":" << c.md_nws << "," << "\"dice\":" << c.dice << ","
Marc Kupietz39887082024-11-22 18:06:20 +0100967 << "\"ld\":" << c.logdice << "," << "\"ln_count\":" << c.left_raw << ","
968 << "\"rn_count\":" << c.right_raw << "," << "\"ln_pmi\":" << c.left_pmi
969 << "," << "\"rn_pmi\":" << c.right_pmi << "," << "\"ldaf\":" << c.ldaf
970 << "," << "\"win\":" << c.window << "," << "\"afwin\":" << c.af_window
971 << "}";
Marc Kupietzc8ddf452018-01-07 21:33:12 +0100972 }
Marc Kupietze9627152019-02-04 12:32:12 +0100973 s << "]}\n";
Marc Kupietz0421d092021-03-13 18:05:14 +0100974 // std::cout << s.str();
Marc Kupietzc8ddf452018-01-07 21:33:12 +0100975 return s.str();
976}
977
Marc Kupietz39887082024-11-22 18:06:20 +0100978typedef CollocatorDB COLLOCATORS;
Marc Kupietz06c9a9f2018-01-02 16:56:43 +0100979
980extern "C" {
Marc Kupietz12af0192021-03-13 18:05:14 +0100981#ifdef __clang__
982#pragma clang diagnostic push
983#pragma ide diagnostic ignored "OCUnusedGlobalDeclarationInspection"
984#endif
Marc Kupietz39887082024-11-22 18:06:20 +0100985DLL_EXPORT COLLOCATORS *open_collocatordb_for_write(char *dbname) {
986 return new CollocatorDB(dbname, false);
987}
Marc Kupietz12af0192021-03-13 18:05:14 +0100988
Marc Kupietz2f65bcf2026-08-02 14:04:37 +0200989DLL_EXPORT void close_collocatordb(COLLOCATORS *db) { delete db; }
990
Marc Kupietz39887082024-11-22 18:06:20 +0100991DLL_EXPORT COLLOCATORS *open_collocatordb(char *dbname) {
992 return new CollocatorDB(dbname, true);
993}
Marc Kupietz06c9a9f2018-01-02 16:56:43 +0100994
Marc Kupietz39887082024-11-22 18:06:20 +0100995DLL_EXPORT void inc_collocator(COLLOCATORS *db, uint32_t w1, uint32_t w2,
996 int8_t dist) {
997 db->inc(w1, w2, dist);
998}
Marc Kupietzc8ddf452018-01-07 21:33:12 +0100999
Marc Kupietz39887082024-11-22 18:06:20 +01001000DLL_EXPORT void dump_collocators(COLLOCATORS *db, uint32_t w1, uint32_t w2,
1001 int8_t dist) {
1002 db->dump(w1, w2, dist);
1003}
Marc Kupietzc8ddf452018-01-07 21:33:12 +01001004
Marc Kupietz39887082024-11-22 18:06:20 +01001005DLL_EXPORT COLLOCATORS *get_collocators(COLLOCATORS *db, uint32_t w1) {
1006 std::vector<Collocator> c = db->get_collocators(w1);
1007 if (c.empty())
1008 return nullptr;
Marc Kupietz2f65bcf2026-08-02 14:04:37 +02001009 /* one entry more than there are collocators, terminated with w2 == 0 and
1010 raw == 0, so that callers can tell where the array ends */
1011 uint64_t size = (c.size() + 1) * sizeof c[0];
Marc Kupietz39887082024-11-22 18:06:20 +01001012 auto *p = (COLLOCATORS *)malloc(size);
Marc Kupietz2f65bcf2026-08-02 14:04:37 +02001013 memset(p, 0, size);
1014 memcpy(p, c.data(), c.size() * sizeof c[0]);
Marc Kupietz39887082024-11-22 18:06:20 +01001015 return p;
1016}
Marc Kupietz88d116b2021-03-13 18:05:14 +01001017
Marc Kupietz39887082024-11-22 18:06:20 +01001018DLL_EXPORT COLLOCATORS *get_collocation_scores(COLLOCATORS *db, uint32_t w1,
1019 uint32_t w2) {
1020 std::vector<Collocator> c = db->get_collocation_scores(w1, w2);
1021 if (c.empty())
1022 return nullptr;
Marc Kupietz2f65bcf2026-08-02 14:04:37 +02001023 /* one entry more than there are collocators, terminated with w2 == 0 and
1024 raw == 0, so that callers can tell where the array ends */
1025 uint64_t size = (c.size() + 1) * sizeof c[0];
Marc Kupietz39887082024-11-22 18:06:20 +01001026 auto *p = (COLLOCATORS *)malloc(size);
Marc Kupietz2f65bcf2026-08-02 14:04:37 +02001027 memset(p, 0, size);
1028 memcpy(p, c.data(), c.size() * sizeof c[0]);
Marc Kupietz39887082024-11-22 18:06:20 +01001029 return p;
1030}
Marc Kupietzca3a52e2018-06-05 14:16:23 +02001031
Marc Kupietz39887082024-11-22 18:06:20 +01001032DLL_EXPORT char *get_word(COLLOCATORS *db, uint32_t w) {
1033 return strdup(db->getWord(w).c_str());
1034}
Marc Kupietz979580e2024-11-21 18:05:07 +01001035
Marc Kupietz39887082024-11-22 18:06:20 +01001036DLL_EXPORT uint64_t get_word_id(COLLOCATORS *db, char *word) {
1037 return db->getWordId(word);
1038}
Marc Kupietzb4a683c2021-03-14 09:19:44 +01001039
Marc Kupietz39887082024-11-22 18:06:20 +01001040DLL_EXPORT void read_vocab(COLLOCATORS *db, char *fname) {
1041 std::string fName(fname);
1042 db->readVocab(fName);
1043}
Marc Kupietz88d116b2021-03-13 18:05:14 +01001044
Marc Kupietz39887082024-11-22 18:06:20 +01001045DLL_EXPORT const char *get_collocators_as_json(COLLOCATORS *db, uint32_t w1) {
1046 return strdup(db->collocators2json(w1, db->get_collocators(w1)).c_str());
1047}
Marc Kupietzb4a683c2021-03-14 09:19:44 +01001048
Marc Kupietz39887082024-11-22 18:06:20 +01001049DLL_EXPORT const char *
1050get_collocation_scores_as_json(COLLOCATORS *db, uint32_t w1, uint32_t w2) {
1051 return strdup(
1052 db->collocators2json(w1, db->get_collocation_scores(w1, w2)).c_str());
1053}
1054
1055DLL_EXPORT const char *get_version() { return PROJECT_VERSION; }
Marc Kupietz6208fd72024-11-15 15:46:19 +01001056
Marc Kupietzd26b1052024-12-10 16:56:39 +01001057DLL_EXPORT uint64_t get_corpus_size(COLLOCATORS *db) { return db->getCorpusSize(); };
1058
Marc Kupietz21b964c2024-12-10 17:10:50 +01001059DLL_EXPORT uint64_t get_word_frequency(COLLOCATORS *db, uint64_t w1) {
1060 return db->getWordFrequency(w1);
1061}
1062
Marc Kupietz12af0192021-03-13 18:05:14 +01001063#ifdef __clang__
1064#pragma clang diagnostic push
1065#endif
Marc Kupietz06c9a9f2018-01-02 16:56:43 +01001066}