blob: 1e0a8ec0c86551503eb8d8f4ac3ccd39c8601cc9 [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>
12#include <cassert>
13#include <cmath>
14#include <cstdint>
15#include <iostream>
16#include <memory>
Marc Kupietz28cc53e2017-12-23 17:24:55 +010017#include <rocksdb/merge_operator.h>
Marc Kupietzc8ddf452018-01-07 21:33:12 +010018#include <rocksdb/slice_transform.h>
Marc Kupietz65d44792026-07-31 09:46:34 +090019#include <rocksdb/version.h>
Marc Kupietz39887082024-11-22 18:06:20 +010020#include <sstream> // for ostringstream
21#include <string>
Marc Kupietzc630c152025-01-23 11:17:47 +010022#include <thread>
Marc Kupietz65d44792026-07-31 09:46:34 +090023#include <utility>
Marc Kupietz39887082024-11-22 18:06:20 +010024#include <vector>
Marc Kupietz28cc53e2017-12-23 17:24:55 +010025
Marc Kupietz65d44792026-07-31 09:46:34 +090026/* Since rocksdb 11 DB::Open() and DB::OpenForReadOnly() hand the database back
27 as a unique_ptr instead of a raw pointer. */
28#if ROCKSDB_MAJOR >= 11
29#define ROCKSDB_DB_HANDLE std::unique_ptr<rocksdb::DB>
30#define ROCKSDB_DB_RELEASE(handle) (handle).release()
31#else
32#define ROCKSDB_DB_HANDLE rocksdb::DB *
33#define ROCKSDB_DB_RELEASE(handle) (handle)
34#endif
35
Marc Kupietz75af60f2019-01-22 22:34:29 +010036#define WINDOW_SIZE 5
Marc Kupietz98cbcdc2019-01-21 17:11:27 +010037#define FREQUENCY_THRESHOLD 5
Marc Kupietz28cc53e2017-12-23 17:24:55 +010038#define IS_BIG_ENDIAN (*(uint16_t *)"\0\xff" < 0x100)
Marc Kupietz39887082024-11-22 18:06:20 +010039#define encodeCollocation(w1, w2, dist) \
40 (((uint64_t)dist << 56) | ((uint64_t)w2 << 24) | w1)
Marc Kupietz18375e12017-12-24 10:11:18 +010041#define W1(key) (uint64_t)(key & 0xffffff)
42#define W2(key) (uint64_t)((key >> 24) & 0xffffff)
43#define DIST(key) (int8_t)((uint64_t)((key >> 56) & 0xff))
Marc Kupietzc8ddf452018-01-07 21:33:12 +010044
45typedef struct {
46 uint64_t freq;
47 char *word;
Marc Kupietz12af0192021-03-13 18:05:14 +010048} vocab_entry;
Marc Kupietzc8ddf452018-01-07 21:33:12 +010049
50// typedef struct Collocator {
51// uint64_t w2;
52// uint64_t sum;
53// };
54
Marc Kupietz28cc53e2017-12-23 17:24:55 +010055using namespace rocksdb;
Marc Kupietzc8ddf452018-01-07 21:33:12 +010056using namespace std;
Marc Kupietz28cc53e2017-12-23 17:24:55 +010057
Marc Kupietz4b799e92018-01-02 11:04:56 +010058namespace rocksdb {
Marc Kupietz39887082024-11-22 18:06:20 +010059class Collocator {
60public:
61 uint32_t w2;
62 uint64_t f2;
63 uint64_t raw;
64 double pmi;
65 double npmi;
66 double llr;
67 double lfmd;
68 double md;
Marc Kupietze889cec2024-11-23 12:08:42 +010069 double md_nws;
Marc Kupietz39887082024-11-22 18:06:20 +010070 uint64_t left_raw;
71 uint64_t right_raw;
72 double left_pmi;
73 double right_pmi;
74 double dice;
75 double logdice;
76 double ldaf;
77 int window;
78 int af_window;
Marc Kupietz28cc53e2017-12-23 17:24:55 +010079};
Marc Kupietz06c9a9f2018-01-02 16:56:43 +010080
Marc Kupietz39887082024-11-22 18:06:20 +010081size_t num_merge_operator_calls;
82
83void resetNumMergeOperatorCalls() { num_merge_operator_calls = 0; }
84
85size_t num_partial_merge_calls;
86
87void resetNumPartialMergeCalls() { num_partial_merge_calls = 0; }
88
89inline void EncodeFixed64(char *buf, uint64_t value) {
90 if (!IS_BIG_ENDIAN) {
91 memcpy(buf, &value, sizeof(value));
92 } else {
93 buf[0] = value & 0xff;
94 buf[1] = (value >> 8) & 0xff;
95 buf[2] = (value >> 16) & 0xff;
96 buf[3] = (value >> 24) & 0xff;
97 buf[4] = (value >> 32) & 0xff;
98 buf[5] = (value >> 40) & 0xff;
99 buf[6] = (value >> 48) & 0xff;
100 buf[7] = (value >> 56) & 0xff;
101 }
Marc Kupietz4a5e08a2018-06-05 11:07:11 +0200102}
103
Marc Kupietz39887082024-11-22 18:06:20 +0100104inline uint32_t DecodeFixed32(const char *ptr) {
105 if (!IS_BIG_ENDIAN) {
106 // Load the raw bytes
107 uint32_t result;
108 memcpy(&result, ptr, sizeof(result)); // gcc optimizes this to a plain load
109 return result;
110 } else {
111 return ((static_cast<uint32_t>(static_cast<unsigned char>(ptr[0]))) |
112 (static_cast<uint32_t>(static_cast<unsigned char>(ptr[1])) << 8) |
113 (static_cast<uint32_t>(static_cast<unsigned char>(ptr[2])) << 16) |
114 (static_cast<uint32_t>(static_cast<unsigned char>(ptr[3])) << 24));
115 }
116}
117
118inline uint64_t DecodeFixed64(const char *ptr) {
119 if (!IS_BIG_ENDIAN) {
120 // Load the raw bytes
121 uint64_t result;
122 memcpy(&result, ptr, sizeof(result)); // gcc optimizes this to a plain load
123 return result;
124 } else {
125 uint64_t lo = DecodeFixed32(ptr);
126 uint64_t hi = DecodeFixed32(ptr + 4);
127 return (hi << 32) | lo;
128 }
129}
130
131static inline double ca_pmi(uint64_t f1, uint64_t f2, uint64_t f12,
132 uint64_t total, double window_size) {
133 double r1 = f1 * window_size, c1 = f2, e = r1 * c1 / total, o = f12;
134 if (f12 < FREQUENCY_THRESHOLD)
135 return -1.0;
136 else
137 return log2(o / e);
138}
139
140// Bouma, Gerlof (2009): <a
141// href="https://svn.spraakdata.gu.se/repos/gerlof/pub/www/Docs/npmi-pfd.pdf">
142// Normalized (pointwise) mutual information in collocation extraction</a>. In
143// Proceedings of GSCL.
144static double ca_npmi(uint64_t f1, uint64_t f2, uint64_t f12,
145 uint64_t total, double window_size) {
146 double r1 = f1 * window_size, c1 = f2, e = r1 * c1 / total, o = f12;
147 if (f12 < FREQUENCY_THRESHOLD)
148 return -1.0;
149 else
150 return log2(o / e) / (-log2(o / total / window_size));
151}
152
153// Thanopoulos, A., Fakotakis, N., Kokkinakis, G.: Comparative evaluation of
154// collocation extraction metrics. In: International Conference on Language
155// Resources and Evaluation (LREC-2002). (2002) 620–625 double md =
156// log2(pow((double)max * window_size / total, 2) / (window_size *
157// ((double)_vocab[w1].freq/total) * ((double)_vocab[last_w2].freq/total)));
158static double ca_md(uint64_t f1, uint64_t f2, uint64_t f12,
159 uint64_t total, double window_size) {
160 const double r1 = f1 * window_size;
161 const double c1 = f2;
162 const double e = r1 * c1 / total;
163 const double o = f12;
164 return log2(o * o / e);
165}
166
167static double ca_lfmd(uint64_t f1, uint64_t f2, uint64_t f12,
168 uint64_t total, double window_size) {
169 double r1 = f1 * window_size, c1 = f2, e = r1 * c1 / total, o = f12;
170 if (f12 == 0)
171 return 0;
172 return log2(o * o * o / e);
173}
174
175// Evert, Stefan (2004): The Statistics of Word Cooccurrences: Word Pairs and
176// Collocations. PhD dissertation, IMS, University of Stuttgart. Published in
177// 2005, URN urn:nbn:de:bsz:93-opus-23714. Free PDF available from
178// http://purl.org/stefan.evert/PUB/Evert2004phd.pdf
179static double ca_ll(uint64_t w1, uint64_t w2, uint64_t w12, uint64_t n,
180 uint64_t window_size) {
181 double r1 = (double)w1 * window_size, r2 = (double)n - r1, c1 = w2,
182 c2 = n - c1, o11 = w12, o12 = r1 - o11, o21 = c1 - w12, o22 = r2 - o21,
183 e11 = r1 * c1 / n, e12 = r1 * c2 / n, e21 = r2 * c1 / n,
184 e22 = r2 * c2 / n;
185 return (2 * ((o11 > 0 ? o11 * log(o11 / e11) : 0) +
186 (o12 > 0 ? o12 * log(o12 / e12) : 0) +
187 (o21 > 0 ? o21 * log(o21 / e21) : 0) +
188 (o22 > 0 ? o22 * log(o22 / e22) : 0)));
189}
190
191static double ca_dice(uint64_t w1, uint64_t w2, uint64_t w12, uint64_t n,
192 uint64_t window_size) {
193 double r1 = (double)w1 * window_size, c1 = w2;
194 return 2 * w12 / (c1 + r1);
195}
196
197// Rychlý, Pavel (2008): <a
198// href="http://www.fi.muni.cz/usr/sojka/download/raslan2008/13.pdf">A
199// lexicographer-friendly association score.</a> In Proceedings of Recent
200// Advances in Slavonic Natural Language Processing, RASLAN, 6–9.
201static double ca_logdice(uint64_t w1, uint64_t w2, uint64_t w12,
202 uint64_t n, uint64_t window_size) {
203 double r1 = (double)w1 * window_size, c1 = w2;
204 return 14 + log2(2 * w12 / (c1 + r1));
205}
206
207class CountMergeOperator : public AssociativeMergeOperator {
208public:
209 CountMergeOperator() {
210 mergeOperator_ = MergeOperators::CreateUInt64AddOperator();
211 }
212
213 bool Merge(const Slice &key, const Slice *existing_value,
214 const Slice &value, std::string *new_value,
215 Logger *logger) const override {
216 assert(new_value->empty());
217 ++num_merge_operator_calls;
218 if (existing_value == nullptr) {
219 new_value->assign(value.data(), value.size());
220 return true;
221 }
222
223 return mergeOperator_->PartialMerge(key, *existing_value, value, new_value,
224 logger);
225 }
226
227 const char *Name() const override { return "UInt64AddOperator"; }
228
229private:
230 std::shared_ptr<MergeOperator> mergeOperator_;
231};
232
233class CollocatorIterator : public Iterator {
234 char prefixc[sizeof(uint64_t)]{};
235 Iterator *base_iterator_;
236
237public:
238 explicit CollocatorIterator(Iterator *base_iterator) : base_iterator_(base_iterator) {}
239
Marc Kupietzaa354d82026-07-31 09:17:57 +0900240 /* Takes ownership of the iterator handed in by SeekIterator(). Without this
241 every seek leaked a rocksdb iterator and the resources it pins. */
242 ~CollocatorIterator() override { delete base_iterator_; }
243
Marc Kupietz39887082024-11-22 18:06:20 +0100244 void setPrefix(char *prefix) { memcpy(prefixc, prefix, sizeof(uint64_t)); }
245
246 void SeekToFirst() override { base_iterator_->SeekToFirst(); }
247
248 void SeekToLast() override { base_iterator_->SeekToLast(); }
249
250 void Seek(const rocksdb::Slice &s) override { base_iterator_->Seek(s); }
251
252 void SeekForPrev(const rocksdb::Slice &s) override {
253 base_iterator_->SeekForPrev(s);
254 }
255
256 void Prev() override { base_iterator_->Prev(); }
257
258 void Next() override { base_iterator_->Next(); }
259
260 Slice key() const override;
261
262 Slice value() const override;
263
264 Status status() const override;
265
266 bool Valid() const override;
267
268 bool isValid();
269
270 uint64_t intValue();
271
272 uint64_t intKey();
273};
274
275// rocksdb::CollocatorIterator::CollocatorIterator(Iterator* base_iterator) {}
276
277bool CollocatorIterator::Valid() const {
278 return base_iterator_->Valid() && key().starts_with(std::string(prefixc, 3));
279}
280
281bool CollocatorIterator::isValid() {
282 return base_iterator_->Valid() && key().starts_with(std::string(prefixc, 3));
283 // return key().starts_with(std::string(prefixc,3));
284}
285
286uint64_t CollocatorIterator::intKey() {
287 return DecodeFixed64(base_iterator_->key().data());
288}
289
290uint64_t CollocatorIterator::intValue() {
291 return DecodeFixed64(base_iterator_->value().data());
292}
293
294class VocabEntry {
295public:
296 string word;
297 uint64_t freq;
298};
299
300class CollocatorDB {
301 WriteOptions merge_option_; // for merge
302 char _one[sizeof(uint64_t)]{};
303 Slice _one_slice;
304 vector<VocabEntry> _vocab;
305 uint64_t total = 0;
306 uint64_t sentences = 0;
307 float avg_window_size = 8.0;
308
309protected:
310 std::shared_ptr<DB> db_;
311
312 WriteOptions put_option_;
313 ReadOptions get_option_;
314 WriteOptions delete_option_;
315
316 uint64_t default_{};
317
318 std::shared_ptr<DB> OpenDb(const char *dbname);
319
320 std::shared_ptr<DB> OpenDbForRead(const char *dbname);
321
322public:
323 virtual ~CollocatorDB() = default;
324 void readVocab(const string& fname);
325 string getWord(uint32_t w1);
326
327 uint64_t getWordId(const char *word) const;
328
Marc Kupietzd26b1052024-12-10 16:56:39 +0100329 uint64_t getCorpusSize() const;
330
Marc Kupietz21b964c2024-12-10 17:10:50 +0100331 uint64_t getWordFrequency(uint64_t w1);
332
Marc Kupietz39887082024-11-22 18:06:20 +0100333 CollocatorDB(const char *db_name, bool read_only);
334
335 // public interface of CollocatorDB.
336 // All four functions return false
337 // if the underlying level db operation failed.
338
339 // mapped to a levedb Put
340 bool set(const std::string &key, uint64_t value) {
341 // just treat the internal rep of int64 as the string
342 char buf[sizeof(value)];
343 EncodeFixed64(buf, value);
344 Slice slice(buf, sizeof(value));
345 auto s = db_->Put(put_option_, key, slice);
346
347 if (s.ok()) {
348 return true;
349 } else {
350 std::cerr << s.ToString() << std::endl;
351 return false;
352 }
353 }
354
355 DB *getDb() { return db_.get(); }
356
357 // mapped to a rocksdb Delete
358 bool remove(const std::string &key) {
359 auto s = db_->Delete(delete_option_, key);
360
361 if (s.ok()) {
362 return true;
363 } else {
364 std::cerr << s.ToString() << std::endl;
365 return false;
366 }
367 }
368
369 // mapped to a rocksdb Get
370 bool get(const std::string &key, uint64_t *value) {
371 std::string str;
372 auto s = db_->Get(get_option_, key, &str);
373
374 if (s.IsNotFound()) {
375 // return default value if not found;
376 *value = default_;
377 return true;
378 } else if (s.ok()) {
379 // deserialization
380 if (str.size() != sizeof(uint64_t)) {
381 std::cerr << "value corruption\n";
382 return false;
383 }
384 *value = DecodeFixed64(&str[0]);
385 return true;
386 } else {
387 std::cerr << s.ToString() << std::endl;
388 return false;
389 }
390 }
391
392 uint64_t get(const uint32_t w1, const uint32_t w2, const int8_t dist) {
393 char encoded_key[sizeof(uint64_t)];
394 EncodeFixed64(encoded_key, encodeCollocation(w1, w2, dist));
395 uint64_t value = default_;
396 get(std::string(encoded_key, 8), &value);
397 return value;
398 }
399
400 virtual void inc(const std::string &key) {
401 db_->Merge(merge_option_, key, _one_slice);
402 }
403
404 void inc(const uint64_t key) {
405 char encoded_key[sizeof(uint64_t)];
406 EncodeFixed64(encoded_key, key);
407 db_->Merge(merge_option_, std::string(encoded_key, 8), _one_slice);
408 }
409
410 virtual void inc(uint32_t w1, uint32_t w2, uint8_t dist);
411
412 void dump(uint32_t w1, uint32_t w2, int8_t dist) const;
413
414 vector<Collocator> get_collocators(uint32_t w1);
415
416 vector<Collocator> get_collocators(uint32_t w1, uint32_t max_w2);
417
418 vector<Collocator> get_collocation_scores(uint32_t w1, uint32_t w2);
419
420 vector<Collocator> get_collocators(uint32_t w1, uint32_t min_w2,
421 uint32_t max_w2);
422
423 void applyCAMeasures(uint32_t w1, uint32_t w2,
424 uint64_t *sumWindow, uint64_t sum,
425 int usedPositions, int true_window_size,
426 Collocator *result) const;
427
428 void dumpSparseLlr(uint32_t w1, uint32_t min_cooccur);
429
430 string collocators2json(uint32_t w1, const vector<Collocator>& collocators);
431
432 // mapped to a rocksdb Merge operation
433 virtual bool add(const std::string &key, uint64_t value) {
434 char encoded[sizeof(uint64_t)];
435 EncodeFixed64(encoded, value);
436 Slice slice(encoded, sizeof(uint64_t));
437 auto s = db_->Merge(merge_option_, key, slice);
438
439 if (s.ok()) {
440 return true;
441 } else {
442 std::cerr << s.ToString() << std::endl;
443 return false;
444 }
445 }
446
447 CollocatorIterator *SeekIterator(uint64_t w1, uint64_t w2, int8_t dist) const;
448};
449
450CollocatorDB::CollocatorDB(const char *db_name,
451 bool read_only = false) {
452 // merge_option_.sync = true;
453 if (read_only)
454 db_ = OpenDbForRead(strdup(db_name));
455 else
456 db_ = OpenDb(db_name);
457 assert(db_);
458 uint64_t one = 1;
459 EncodeFixed64(_one, one);
460 _one_slice = Slice(_one, sizeof(uint64_t));
461}
462
463void CollocatorDB::inc(const uint32_t w1, const uint32_t w2,
464 const uint8_t dist) {
465 inc(encodeCollocation(w1, w2, dist));
466}
467
468void CollocatorDB::readVocab(const string& fname) {
469 char strbuf[2048];
470 uint64_t freq;
471 FILE *fin = fopen(fname.c_str(), "rb");
472 if (fin == nullptr) {
473 cout << "Vocabulary file " << fname << " not found\n";
474 exit(1);
475 }
476 uint64_t i = 0;
477 while (fscanf(fin, "%s %lu", strbuf, &freq) == 2) {
478 _vocab.push_back({strbuf, freq});
479 total += freq;
480 i++;
481 }
482 fclose(fin);
483
484 char size_fname[256];
485 strcpy(size_fname, fname.c_str());
486 char *pos = strstr(size_fname, ".vocab");
487 if (pos) {
488 *pos = 0;
489 strcat(size_fname, ".size");
490 FILE *fp = fopen(size_fname, "r");
491 if (fp != nullptr) {
492 fscanf(fp, "%lu", &sentences);
493 fscanf(fp, "%lu", &total);
494 float sl = (float)total / (float)sentences;
495 float w = WINDOW_SIZE;
496 avg_window_size =
497 ((sl > 2 * w ? (sl - 2 * w) * 2 * w : 0) + (double)w * (3 * w - 1)) /
498 sl;
499 fprintf(stdout,
500 "Size corrections found: corpus size: %lu tokens in %lu "
501 "sentences, avg. sentence size: %f, avg. window size: %f\n",
502 total, sentences, sl, avg_window_size);
503 fclose(fp);
504 } else {
505 // std::cout << "size file " << size_fname << " not found\n";
506 }
507 } else {
508 std::cout << "cannot determine size file " << size_fname << "\n";
509 }
510}
511
512std::shared_ptr<DB> CollocatorDB::OpenDbForRead(const char *name) {
Marc Kupietz65d44792026-07-31 09:46:34 +0900513 ROCKSDB_DB_HANDLE db;
Marc Kupietz39887082024-11-22 18:06:20 +0100514 Options options;
515 options.env->SetBackgroundThreads(4);
516 options.create_if_missing = true;
517 options.merge_operator = std::make_shared<CountMergeOperator>();
518 options.max_successive_merges = 0;
519 // options.prefix_extractor.reset(NewFixedPrefixTransform(8));
520 options.IncreaseParallelism();
521 options.OptimizeLevelStyleCompaction();
522 options.prefix_extractor.reset(NewFixedPrefixTransform(3));
523 ostringstream dbname, vocabname;
524 dbname << name << ".rocksdb";
525 auto s = DB::OpenForReadOnly(options, dbname.str(), &db);
526 if (!s.ok()) {
527 std::cerr << s.ToString() << std::endl;
528 assert(false);
529 }
530 vocabname << name << ".vocab";
531 readVocab(vocabname.str());
Marc Kupietz65d44792026-07-31 09:46:34 +0900532 return std::shared_ptr<DB>(ROCKSDB_DB_RELEASE(db));
Marc Kupietz39887082024-11-22 18:06:20 +0100533}
534
Marc Kupietzc630c152025-01-23 11:17:47 +0100535 std::shared_ptr<DB> CollocatorDB::OpenDb(const char *dbname) {
Marc Kupietz65d44792026-07-31 09:46:34 +0900536 ROCKSDB_DB_HANDLE db;
Marc Kupietzc630c152025-01-23 11:17:47 +0100537 Options options;
Marc Kupietz39887082024-11-22 18:06:20 +0100538
Marc Kupietzc630c152025-01-23 11:17:47 +0100539 int max_cores = static_cast<int>(std::thread::hardware_concurrency());
540
541 // options.env->SetBackgroundThreads(32, Env::Priority::HIGH); // Increase background threads for high priority
542 // options.env->SetBackgroundThreads(16, Env::Priority::LOW); // Increase background threads for low priority
543 options.create_if_missing = true;
544 options.merge_operator = std::make_shared<CountMergeOperator>();
545 //options.max_successive_merges = 0;
546 // options.IncreaseParallelism(max_cores); // Utilize all available cores
547 // options.OptimizeLevelStyleCompaction();
548
549 // Increase write buffer size and number of write buffers
550 // options.write_buffer_size = 512 * 1024 * 1024; // 512MB
551 // options.max_write_buffer_number = max_cores;
552 // options.min_write_buffer_number_to_merge = max_cores / 2;
553
554 // Enable concurrent memtable writes
555 options.allow_concurrent_memtable_write = true;
556 options.enable_write_thread_adaptive_yield = true;
557 options.allow_mmap_writes = true;
558 options.allow_mmap_reads = true;
559
560 // Optimize block cache size
561 BlockBasedTableOptions table_options;
562 table_options.block_cache = NewLRUCache(8 * 1024 * 1024 * 1024L); // 8GB block cache
563 options.table_factory.reset(NewBlockBasedTableFactory(table_options));
564
565 // Adjust compaction settings
566 options.level0_file_num_compaction_trigger = 100;
567 options.level0_slowdown_writes_trigger = 200;
568 options.level0_stop_writes_trigger = 400;
569 options.max_background_compactions = max_cores / 2;
570 options.max_background_flushes = max_cores / 4;
571 // options.disableWA
572 // Tune write options
573 merge_option_.low_pri = true; // Use low priority for compactions
574 merge_option_.disableWAL = true; // Disable Write-Ahead Logging for faster writes
575 merge_option_.sync = false; // Disable sync for faster writes
576 merge_option_.no_slowdown = true; // Disable write slowdown for faster writes
577
578 Status s = DB::Open(options, dbname, &db);
579 if (!s.ok()) {
580 std::cerr << s.ToString() << std::endl;
581 assert(false);
582 }
583 total = 1000;
Marc Kupietz65d44792026-07-31 09:46:34 +0900584 return std::shared_ptr<DB>(ROCKSDB_DB_RELEASE(db));
Marc Kupietz39887082024-11-22 18:06:20 +0100585 }
Marc Kupietz39887082024-11-22 18:06:20 +0100586
587CollocatorIterator *
588CollocatorDB::SeekIterator(uint64_t w1, uint64_t w2, int8_t dist) const {
589 ReadOptions options;
590 options.prefix_same_as_start = true;
591 char prefixc[sizeof(uint64_t)];
592 EncodeFixed64(prefixc, encodeCollocation(w1, w2, dist));
593 Iterator *it = db_->NewIterator(options);
594 auto *cit = new CollocatorIterator(it);
595 if (w2 > 0)
596 cit->Seek(std::string(prefixc, 6));
597 else
598 cit->Seek(std::string(prefixc, 3));
599 cit->setPrefix(prefixc);
600 return cit;
601}
602
603void CollocatorDB::dump(uint32_t w1, uint32_t w2, int8_t dist) const {
604 auto it = std::unique_ptr<CollocatorIterator>(SeekIterator(w1, w2, dist));
605 for (; it->isValid(); it->Next()) {
606 uint64_t value = it->intValue();
607 uint64_t key = it->intKey();
608 std::cout << "w1:" << W1(key) << ", w2:" << W2(key)
609 << ", dist:" << (int32_t)DIST(key) << " - count:" << value
610 << std::endl;
611 }
612 std::cout << "ready dumping\n";
613}
614
615bool sortByNpmi(const Collocator &lhs, const Collocator &rhs) {
616 return lhs.npmi > rhs.npmi;
617}
618
619bool sortByLfmd(const Collocator &lhs, const Collocator &rhs) {
620 return lhs.lfmd > rhs.lfmd;
621}
622
623bool sortByLlr(const Collocator &lhs, const Collocator &rhs) {
624 return lhs.llr > rhs.llr;
625}
626
627bool sortByLogDice(const Collocator &lhs, const Collocator &rhs) {
628 return lhs.logdice > rhs.logdice;
629}
630
631bool sortByLogDiceAF(const Collocator &lhs, const Collocator &rhs) {
632 return lhs.ldaf > rhs.ldaf;
633}
634
635void CollocatorDB::applyCAMeasures(
636 const uint32_t w1, const uint32_t w2, uint64_t *sumWindow,
637 const uint64_t sum, const int usedPositions, int true_window_size,
638 Collocator *result) const {
639 uint64_t f1 = _vocab[w1].freq, f2 = _vocab[w2].freq;
640 double o = sum, r1 = f1 * true_window_size, c1 = f2, e = r1 * c1 / total,
641 pmi = log2(o / e), md = log2(o * o / e), lfmd = log2(o * o * o / e),
Marc Kupietze889cec2024-11-23 12:08:42 +0100642 llr = ca_ll(f1, f2, sum, total, true_window_size),
643 md_nws = ca_md(f1, f2, sum, total, 2 * WINDOW_SIZE),
644 ld = ca_logdice(f1, f2, sum, total, true_window_size);
Marc Kupietz39887082024-11-22 18:06:20 +0100645
646 int bestWindow = usedPositions;
647 double bestAF = ld;
648 // if(f1<75000000)
649 // #pragma omp parallel for reduction(max:bestAF)
650 // #pragma omp target teams distribute parallel for reduction(max:bestAF)
651 // map(tofrom:bestAF,currentAF,bestWindow,usedPositions)
652 for (int bitmask = 1; bitmask < (1 << (2 * WINDOW_SIZE)); bitmask++) {
653 if ((bitmask & usedPositions) == 0 || (bitmask & ~usedPositions) > 0)
654 continue;
655 uint64_t currentWindowSum = 0;
656 // #pragma omp target teams distribute parallel for
657 // reduction(+:currentWindowSum) map(tofrom:bitmask,usedPositions)
658 for (int pos = 0; pos < 2 * WINDOW_SIZE; pos++) {
659 if (((1 << pos) & bitmask & usedPositions) != 0)
660 currentWindowSum += sumWindow[pos];
661 }
662 double currentAF = ca_logdice(f1, f2, currentWindowSum, total,
663 __builtin_popcount(bitmask));
664 if (currentAF > bestAF) {
665 bestAF = currentAF;
666 bestWindow = bitmask;
667 }
668 }
669
670 *result = {w2,
671 f2,
672 sum,
673 pmi,
674 pmi / (-log2(o / total / true_window_size)),
675 llr,
676 lfmd,
677 md,
Marc Kupietze889cec2024-11-23 12:08:42 +0100678 md_nws,
Marc Kupietz39887082024-11-22 18:06:20 +0100679 sumWindow[WINDOW_SIZE],
680 sumWindow[WINDOW_SIZE - 1],
681 ca_pmi(f1, f2, sumWindow[WINDOW_SIZE], total, 1),
682 ca_pmi(f1, f2, sumWindow[WINDOW_SIZE - 1], total, 1),
683 ca_dice(f1, f2, sum, total, true_window_size),
684 ld,
685 bestAF,
686 usedPositions,
687 bestWindow};
688}
689
690std::vector<Collocator>
691CollocatorDB::get_collocators(uint32_t w1, uint32_t min_w2,
692 uint32_t max_w2) {
693 std::vector<Collocator> collocators;
694 uint64_t w2, last_w2 = 0xffffffffffffffff;
695 uint64_t maxv = 0, sum = 0;
Marc Kupietzaa354d82026-07-31 09:17:57 +0900696 /* fixed size, so it lives on the stack and cannot be leaked */
697 uint64_t sumWindow[2 * WINDOW_SIZE];
698 memset(sumWindow, 0, sizeof(sumWindow));
Marc Kupietz39887082024-11-22 18:06:20 +0100699 int true_window_size = 1;
700 int usedPositions = 0;
701
702 if (w1 > _vocab.size()) {
703 std::cout << w1 << "> vocabulary size " << _vocab.size() << "\n";
704 w1 -= _vocab.size();
705 }
706#ifdef DEBUG
707 std::cout << "Searching for collocates of " << _vocab[w1].word << "\n";
708#endif
709 // #pragma omp parallel num_threads(40)
710 // #pragma omp single
711 for (auto it =
712 std::unique_ptr<CollocatorIterator>(SeekIterator(w1, min_w2, 0));
713 it->isValid(); it->Next()) {
714 uint64_t value = it->intValue(), key = it->intKey();
715 if ((w2 = W2(key)) > max_w2)
716 continue;
717 if (last_w2 == 0xffffffffffffffff)
718 last_w2 = w2;
719 if (w2 != last_w2) {
720 if (sum >= FREQUENCY_THRESHOLD) {
721 collocators.push_back({});
722 Collocator *result = &(collocators[collocators.size() - 1]);
723 // #pragma omp task firstprivate(last_w2, sumWindow, sum, usedPositions,
724 // true_window_size) shared(w1, result) if(sum > 1000000)
725 {
726 // uint64_t *nsw = (uint64_t *)malloc(sizeof(uint64_t) * 2
727 // *WINDOW_SIZE); memcpy(nsw, sumWindow, sizeof(uint64_t) * 2
728 // *WINDOW_SIZE);
729 applyCAMeasures(w1, last_w2, sumWindow, sum, usedPositions,
730 true_window_size, result);
731 // free(nsw);
732 }
733 }
734 memset(sumWindow, 0, 2 * WINDOW_SIZE * sizeof(uint64_t));
735 usedPositions = 1 << (-DIST(key) + WINDOW_SIZE - (DIST(key) < 0 ? 1 : 0));
736 sumWindow[-DIST(key) + WINDOW_SIZE - (DIST(key) < 0 ? 1 : 0)] = value;
737 last_w2 = w2;
738 maxv = value;
739 sum = value;
740 true_window_size = 1;
741 if (min_w2 == max_w2 && w2 != min_w2)
742 break;
743 } else {
744 sum += value;
745 if (value > maxv)
746 maxv = value;
747 usedPositions |=
748 1 << (-DIST(key) + WINDOW_SIZE - (DIST(key) < 0 ? 1 : 0));
749 sumWindow[-DIST(key) + WINDOW_SIZE - (DIST(key) < 0 ? 1 : 0)] = value;
750 true_window_size++;
751 }
752 }
753
754 // #pragma omp taskwait
755 sort(collocators.begin(), collocators.end(), sortByLogDiceAF);
756
757#ifdef DEBUG
758 int i = 0;
759 for (Collocator c : collocators) {
760 if (i++ > 10)
761 break;
762 std::cout << "w1:" << _vocab[w1].word << ", w2: *" << _vocab[c.w2].word
763 << "*"
764 << "\t f(w1):" << _vocab[w1].freq
765 << "\t f(w2):" << _vocab[c.w2].freq << "\t f(w1, w2):" << c.raw
766 << "\t pmi:" << c.pmi << "\t npmi:" << c.npmi
767 << "\t llr:" << c.llr << "\t md:" << c.md << "\t lfmd:" << c.lfmd
768 << "\t total:" << total << std::endl;
769 }
770#endif
771
772 return collocators;
773}
774
775std::vector<Collocator>
776CollocatorDB::get_collocation_scores(uint32_t w1, uint32_t w2) {
777 return get_collocators(w1, w2, w2);
778}
779
780std::vector<Collocator> CollocatorDB::get_collocators(uint32_t w1) {
781 return get_collocators(w1, 0, UINT32_MAX);
782}
783
784void CollocatorDB::dumpSparseLlr(uint32_t w1, uint32_t min_cooccur) {
785 std::vector<Collocator> collocators;
786 std::stringstream stream;
787 uint64_t w2, last_w2 = 0xffffffffffffffff;
788 uint64_t maxv = 0, total_w1 = 0;
789 bool first = true;
790 for (auto it = std::unique_ptr<CollocatorIterator>(SeekIterator(w1, 0, 0));
791 it->isValid(); it->Next()) {
792 uint64_t value = it->intValue(), key = it->intKey();
793 w2 = W2(key);
794 total_w1 += value;
795 if (last_w2 == 0xffffffffffffffff)
796 last_w2 = w2;
797 if (w2 != last_w2) {
798 if (maxv >= min_cooccur) {
799 double llr =
800 ca_ll(_vocab[w1].freq, _vocab[last_w2].freq, maxv, total, 1);
801 if (first)
802 first = false;
803 else
804 stream << " ";
805 stream << w2 << " " << llr;
806 }
807 last_w2 = w2;
808 maxv = value;
809 } else {
810 if (value > maxv)
811 maxv = value;
812 }
813 }
814 if (first)
815 stream << "1 0.0";
816 stream << "\n";
817 std::cout << stream.str();
818}
819
820Slice CollocatorIterator::key() const {
821 return base_iterator_->key();
822}
823
824Slice CollocatorIterator::value() const {
825 return base_iterator_->value();
826}
827
828Status CollocatorIterator::status() const {
829 return base_iterator_->status();
830}
831
832}; // namespace rocksdb
833
834string CollocatorDB::getWord(uint32_t w1) { return _vocab[w1].word; }
835
836uint64_t CollocatorDB::getWordId(const char *word) const {
Marc Kupietz979580e2024-11-21 18:05:07 +0100837 for (uint64_t i = 0; i < _vocab.size(); i++) {
838 if (strcmp(_vocab[i].word.c_str(), word) == 0)
839 return i;
840 }
841 return 0;
842}
843
Marc Kupietzd26b1052024-12-10 16:56:39 +0100844uint64_t CollocatorDB::getCorpusSize() const {
845 return total;
846}
847
Marc Kupietz21b964c2024-12-10 17:10:50 +0100848uint64_t CollocatorDB::getWordFrequency(uint64_t w1) {
849 return _vocab[w1].freq;
850}
851
Marc Kupietz39887082024-11-22 18:06:20 +0100852string CollocatorDB::collocators2json(uint32_t w1,
853 const vector<Collocator>& collocators) {
Marc Kupietzc8ddf452018-01-07 21:33:12 +0100854 ostringstream s;
Marc Kupietz0dd86ef2018-01-11 22:23:17 +0100855 int i = 0;
Marc Kupietz39887082024-11-22 18:06:20 +0100856 s << " { \"f1\": " << _vocab[w1].freq << "," << R"("w1":")"
857 << string(_vocab[w1].word) << "\", " << "\"N\": " << total << ", "
858 << "\"collocates\": [";
Marc Kupietzc8ddf452018-01-07 21:33:12 +0100859 bool first = true;
860 for (Collocator c : collocators) {
Marc Kupietz39887082024-11-22 18:06:20 +0100861 if (strncmp(_vocab[c.w2].word.c_str(), "quot", 4) == 0)
862 continue;
Marc Kupietz0dd86ef2018-01-11 22:23:17 +0100863 if (i++ > 200)
864 break;
Marc Kupietz12af0192021-03-13 18:05:14 +0100865 if (!first)
Marc Kupietzc8ddf452018-01-07 21:33:12 +0100866 s << ",\n";
867 else
868 first = false;
869 s << "{"
Marc Kupietz39887082024-11-22 18:06:20 +0100870 "\"word\":\""
871 << (string(_vocab[c.w2].word) == "<num>"
872 ? string("###")
873 : string(_vocab[c.w2].word))
874 << "\"," << "\"f2\":" << c.f2 << "," << "\"f\":" << c.raw << ","
875 << "\"npmi\":" << c.npmi << "," << "\"pmi\":" << c.pmi << ","
876 << "\"llr\":" << c.llr << "," << "\"lfmd\":" << c.lfmd << ","
Marc Kupietze889cec2024-11-23 12:08:42 +0100877 << "\"md\":" << c.md << "," << "\"md_nws\":" << c.md_nws << "," << "\"dice\":" << c.dice << ","
Marc Kupietz39887082024-11-22 18:06:20 +0100878 << "\"ld\":" << c.logdice << "," << "\"ln_count\":" << c.left_raw << ","
879 << "\"rn_count\":" << c.right_raw << "," << "\"ln_pmi\":" << c.left_pmi
880 << "," << "\"rn_pmi\":" << c.right_pmi << "," << "\"ldaf\":" << c.ldaf
881 << "," << "\"win\":" << c.window << "," << "\"afwin\":" << c.af_window
882 << "}";
Marc Kupietzc8ddf452018-01-07 21:33:12 +0100883 }
Marc Kupietze9627152019-02-04 12:32:12 +0100884 s << "]}\n";
Marc Kupietz0421d092021-03-13 18:05:14 +0100885 // std::cout << s.str();
Marc Kupietzc8ddf452018-01-07 21:33:12 +0100886 return s.str();
887}
888
Marc Kupietz39887082024-11-22 18:06:20 +0100889typedef CollocatorDB COLLOCATORS;
Marc Kupietz06c9a9f2018-01-02 16:56:43 +0100890
891extern "C" {
Marc Kupietz12af0192021-03-13 18:05:14 +0100892#ifdef __clang__
893#pragma clang diagnostic push
894#pragma ide diagnostic ignored "OCUnusedGlobalDeclarationInspection"
895#endif
Marc Kupietz39887082024-11-22 18:06:20 +0100896DLL_EXPORT COLLOCATORS *open_collocatordb_for_write(char *dbname) {
897 return new CollocatorDB(dbname, false);
898}
Marc Kupietz12af0192021-03-13 18:05:14 +0100899
Marc Kupietz39887082024-11-22 18:06:20 +0100900DLL_EXPORT COLLOCATORS *open_collocatordb(char *dbname) {
901 return new CollocatorDB(dbname, true);
902}
Marc Kupietz06c9a9f2018-01-02 16:56:43 +0100903
Marc Kupietz39887082024-11-22 18:06:20 +0100904DLL_EXPORT void inc_collocator(COLLOCATORS *db, uint32_t w1, uint32_t w2,
905 int8_t dist) {
906 db->inc(w1, w2, dist);
907}
Marc Kupietzc8ddf452018-01-07 21:33:12 +0100908
Marc Kupietz39887082024-11-22 18:06:20 +0100909DLL_EXPORT void dump_collocators(COLLOCATORS *db, uint32_t w1, uint32_t w2,
910 int8_t dist) {
911 db->dump(w1, w2, dist);
912}
Marc Kupietzc8ddf452018-01-07 21:33:12 +0100913
Marc Kupietz39887082024-11-22 18:06:20 +0100914DLL_EXPORT COLLOCATORS *get_collocators(COLLOCATORS *db, uint32_t w1) {
915 std::vector<Collocator> c = db->get_collocators(w1);
916 if (c.empty())
917 return nullptr;
918 uint64_t size = c.size() + sizeof c[0];
919 auto *p = (COLLOCATORS *)malloc(size);
920 memcpy(p, c.data(), size);
921 return p;
922}
Marc Kupietz88d116b2021-03-13 18:05:14 +0100923
Marc Kupietz39887082024-11-22 18:06:20 +0100924DLL_EXPORT COLLOCATORS *get_collocation_scores(COLLOCATORS *db, uint32_t w1,
925 uint32_t w2) {
926 std::vector<Collocator> c = db->get_collocation_scores(w1, w2);
927 if (c.empty())
928 return nullptr;
929 uint64_t size = c.size() + sizeof c[0];
930 auto *p = (COLLOCATORS *)malloc(size);
931 memcpy(p, c.data(), size);
932 return p;
933}
Marc Kupietzca3a52e2018-06-05 14:16:23 +0200934
Marc Kupietz39887082024-11-22 18:06:20 +0100935DLL_EXPORT char *get_word(COLLOCATORS *db, uint32_t w) {
936 return strdup(db->getWord(w).c_str());
937}
Marc Kupietz979580e2024-11-21 18:05:07 +0100938
Marc Kupietz39887082024-11-22 18:06:20 +0100939DLL_EXPORT uint64_t get_word_id(COLLOCATORS *db, char *word) {
940 return db->getWordId(word);
941}
Marc Kupietzb4a683c2021-03-14 09:19:44 +0100942
Marc Kupietz39887082024-11-22 18:06:20 +0100943DLL_EXPORT void read_vocab(COLLOCATORS *db, char *fname) {
944 std::string fName(fname);
945 db->readVocab(fName);
946}
Marc Kupietz88d116b2021-03-13 18:05:14 +0100947
Marc Kupietz39887082024-11-22 18:06:20 +0100948DLL_EXPORT const char *get_collocators_as_json(COLLOCATORS *db, uint32_t w1) {
949 return strdup(db->collocators2json(w1, db->get_collocators(w1)).c_str());
950}
Marc Kupietzb4a683c2021-03-14 09:19:44 +0100951
Marc Kupietz39887082024-11-22 18:06:20 +0100952DLL_EXPORT const char *
953get_collocation_scores_as_json(COLLOCATORS *db, uint32_t w1, uint32_t w2) {
954 return strdup(
955 db->collocators2json(w1, db->get_collocation_scores(w1, w2)).c_str());
956}
957
958DLL_EXPORT const char *get_version() { return PROJECT_VERSION; }
Marc Kupietz6208fd72024-11-15 15:46:19 +0100959
Marc Kupietzd26b1052024-12-10 16:56:39 +0100960DLL_EXPORT uint64_t get_corpus_size(COLLOCATORS *db) { return db->getCorpusSize(); };
961
Marc Kupietz21b964c2024-12-10 17:10:50 +0100962DLL_EXPORT uint64_t get_word_frequency(COLLOCATORS *db, uint64_t w1) {
963 return db->getWordFrequency(w1);
964}
965
Marc Kupietz12af0192021-03-13 18:05:14 +0100966#ifdef __clang__
967#pragma clang diagnostic push
968#endif
Marc Kupietz06c9a9f2018-01-02 16:56:43 +0100969}