blob: 4da2e064cef925c896daf2adf61cd7b20f05888a [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
Marc Kupietz0fec0be2026-09-04 19:42:38 +0200194// The Dice coefficient relates the co-occurrence frequency to how often the two
195// words occur at all, so its denominator sums marginal word frequencies. It
196// therefore takes no window size factor, unlike the scores above, which need one
197// in their expected frequency: w1 * window_size would count window positions
198// rather than word tokens, and adding that to w2 would also make the coefficient
199// asymmetric.
Marc Kupietz39887082024-11-22 18:06:20 +0100200static double ca_dice(uint64_t w1, uint64_t w2, uint64_t w12, uint64_t n,
201 uint64_t window_size) {
Marc Kupietz0fec0be2026-09-04 19:42:38 +0200202 double r1 = (double)w1, c1 = w2;
Marc Kupietz39887082024-11-22 18:06:20 +0100203 return 2 * w12 / (c1 + r1);
204}
205
206// Rychlý, Pavel (2008): <a
207// href="http://www.fi.muni.cz/usr/sojka/download/raslan2008/13.pdf">A
208// lexicographer-friendly association score.</a> In Proceedings of Recent
209// Advances in Slavonic Natural Language Processing, RASLAN, 6–9.
210static double ca_logdice(uint64_t w1, uint64_t w2, uint64_t w12,
211 uint64_t n, uint64_t window_size) {
Marc Kupietz0fec0be2026-09-04 19:42:38 +0200212 double r1 = (double)w1, c1 = w2;
213 return 14 + log2(2 * w12 / (c1 + r1));
214}
215
216// The auto focus score, reported as LDaf: its maximum over all selections of
217// positions both picks the auto focus window and is the value shown for it. It
218// used to be computed by ca_logdice(), which is why that one carried a window
219// size factor.
220//
221// It is Dice-like, but it is not logDice and the two are not on a common scale:
222// multiplying f1 by the number of positions penalizes wide windows. That
223// penalty is the point. It makes the score sensitive to how concentrated a pair
224// is, which is what tells actual collocations from words that merely share
225// contexts, and is why LDaf orders collocates more usefully than the other
226// measures. logDice itself cannot serve here: its denominator does not depend
227// on the window while the co-occurrence count only grows as positions are
228// added, so the widest window would always win.
229static double ca_focus_score(uint64_t w1, uint64_t w2, uint64_t w12, uint64_t n,
230 uint64_t window_size) {
Marc Kupietz39887082024-11-22 18:06:20 +0100231 double r1 = (double)w1 * window_size, c1 = w2;
232 return 14 + log2(2 * w12 / (c1 + r1));
233}
234
235class CountMergeOperator : public AssociativeMergeOperator {
236public:
237 CountMergeOperator() {
238 mergeOperator_ = MergeOperators::CreateUInt64AddOperator();
239 }
240
241 bool Merge(const Slice &key, const Slice *existing_value,
242 const Slice &value, std::string *new_value,
243 Logger *logger) const override {
244 assert(new_value->empty());
245 ++num_merge_operator_calls;
246 if (existing_value == nullptr) {
247 new_value->assign(value.data(), value.size());
248 return true;
249 }
250
251 return mergeOperator_->PartialMerge(key, *existing_value, value, new_value,
252 logger);
253 }
254
255 const char *Name() const override { return "UInt64AddOperator"; }
256
257private:
258 std::shared_ptr<MergeOperator> mergeOperator_;
259};
260
261class CollocatorIterator : public Iterator {
262 char prefixc[sizeof(uint64_t)]{};
263 Iterator *base_iterator_;
264
265public:
266 explicit CollocatorIterator(Iterator *base_iterator) : base_iterator_(base_iterator) {}
267
Marc Kupietzaa354d82026-07-31 09:17:57 +0900268 /* Takes ownership of the iterator handed in by SeekIterator(). Without this
269 every seek leaked a rocksdb iterator and the resources it pins. */
270 ~CollocatorIterator() override { delete base_iterator_; }
271
Marc Kupietz39887082024-11-22 18:06:20 +0100272 void setPrefix(char *prefix) { memcpy(prefixc, prefix, sizeof(uint64_t)); }
273
274 void SeekToFirst() override { base_iterator_->SeekToFirst(); }
275
276 void SeekToLast() override { base_iterator_->SeekToLast(); }
277
278 void Seek(const rocksdb::Slice &s) override { base_iterator_->Seek(s); }
279
280 void SeekForPrev(const rocksdb::Slice &s) override {
281 base_iterator_->SeekForPrev(s);
282 }
283
284 void Prev() override { base_iterator_->Prev(); }
285
286 void Next() override { base_iterator_->Next(); }
287
288 Slice key() const override;
289
290 Slice value() const override;
291
292 Status status() const override;
293
294 bool Valid() const override;
295
296 bool isValid();
297
298 uint64_t intValue();
299
300 uint64_t intKey();
301};
302
303// rocksdb::CollocatorIterator::CollocatorIterator(Iterator* base_iterator) {}
304
305bool CollocatorIterator::Valid() const {
306 return base_iterator_->Valid() && key().starts_with(std::string(prefixc, 3));
307}
308
309bool CollocatorIterator::isValid() {
310 return base_iterator_->Valid() && key().starts_with(std::string(prefixc, 3));
311 // return key().starts_with(std::string(prefixc,3));
312}
313
314uint64_t CollocatorIterator::intKey() {
315 return DecodeFixed64(base_iterator_->key().data());
316}
317
318uint64_t CollocatorIterator::intValue() {
319 return DecodeFixed64(base_iterator_->value().data());
320}
321
322class VocabEntry {
323public:
324 string word;
325 uint64_t freq;
326};
327
Marc Kupietz122ba3c2026-08-09 17:13:24 +0200328static uint64_t env_size(const char *name, uint64_t fallback);
329
Marc Kupietz39887082024-11-22 18:06:20 +0100330class CollocatorDB {
331 WriteOptions merge_option_; // for merge
Marc Kupietz2f65bcf2026-08-02 14:04:37 +0200332 // to repeat a write that rocksdb cancelled, rather than lose the count
333 WriteOptions blocking_merge_option_;
Marc Kupietz122ba3c2026-08-09 17:13:24 +0200334 mutable std::atomic<uint64_t> stalled_writes_{0};
335 mutable std::atomic<uint64_t> failed_writes_{0};
Marc Kupietz39887082024-11-22 18:06:20 +0100336 char _one[sizeof(uint64_t)]{};
337 Slice _one_slice;
338 vector<VocabEntry> _vocab;
339 uint64_t total = 0;
340 uint64_t sentences = 0;
341 float avg_window_size = 8.0;
342
Marc Kupietz122ba3c2026-08-09 17:13:24 +0200343 /* Single-key merges are collected in a batch, so that a long indexing run
344 does one rocksdb write per batch instead of one per collocation pair.
345 rocksdb inserts the merge operands of a key in order anyway, which is why
346 the collection point has a mutex: it is the same serialization rocksdb
347 would impose on the writes.
348
349 They are mutable so that the const read methods can write out what is
350 still buffered: a read has to see every increment that came before it. */
351 mutable std::mutex batch_mutex_;
352 mutable WriteBatch batch_;
353 size_t batch_target_ = 65536;
354
355 /* Writes out a batch of merge operands, retrying a cancelled write the same
356 way merge_one() used to, before the merges were batched.
357
358 Takes a batch that merge_one() or flush() took out of the shared batch,
359 and is called without batch_mutex_: the write is the slow part, and the
360 other threads must be able to keep accumulating while it is in flight.
361 Holding the mutex across the write made the indexer run slower with many
362 threads than with one, because the threads queued up on the mutex and
363 rocksdb never saw concurrent writers to batch into one. */
364 void write_batch(WriteBatch &to_write) const {
365 if (to_write.Count() == 0)
366 return;
367 Status s = db_->Write(merge_option_, &to_write);
368 if (s.ok())
369 return;
370 if (s.IsIncomplete()) {
371 ++stalled_writes_;
372 s = db_->Write(blocking_merge_option_, &to_write);
373 if (s.ok())
374 return;
375 }
376 if (failed_writes_++ == 0)
377 std::cerr << "collocatordb: cannot write, counts are lost: "
378 << s.ToString() << std::endl;
379 }
380
381 /* A read has to see the increments that are still in the batch, so the read
382 methods call this first. */
383 void flush() const {
384 WriteBatch to_write;
385 {
386 std::lock_guard<std::mutex> lock(batch_mutex_);
387 std::swap(batch_, to_write);
388 }
389 write_batch(to_write);
390 }
391
Marc Kupietz39887082024-11-22 18:06:20 +0100392protected:
393 std::shared_ptr<DB> db_;
394
395 WriteOptions put_option_;
396 ReadOptions get_option_;
397 WriteOptions delete_option_;
398
399 uint64_t default_{};
400
401 std::shared_ptr<DB> OpenDb(const char *dbname);
402
403 std::shared_ptr<DB> OpenDbForRead(const char *dbname);
404
405public:
Marc Kupietz2f65bcf2026-08-02 14:04:37 +0200406 virtual ~CollocatorDB(); // flushes what is still in memory, see close()
Marc Kupietz39887082024-11-22 18:06:20 +0100407 void readVocab(const string& fname);
408 string getWord(uint32_t w1);
409
410 uint64_t getWordId(const char *word) const;
411
Marc Kupietzd26b1052024-12-10 16:56:39 +0100412 uint64_t getCorpusSize() const;
413
Marc Kupietz21b964c2024-12-10 17:10:50 +0100414 uint64_t getWordFrequency(uint64_t w1);
415
Marc Kupietz39887082024-11-22 18:06:20 +0100416 CollocatorDB(const char *db_name, bool read_only);
417
418 // public interface of CollocatorDB.
419 // All four functions return false
420 // if the underlying level db operation failed.
421
422 // mapped to a levedb Put
423 bool set(const std::string &key, uint64_t value) {
424 // just treat the internal rep of int64 as the string
425 char buf[sizeof(value)];
426 EncodeFixed64(buf, value);
427 Slice slice(buf, sizeof(value));
428 auto s = db_->Put(put_option_, key, slice);
429
430 if (s.ok()) {
431 return true;
432 } else {
433 std::cerr << s.ToString() << std::endl;
434 return false;
435 }
436 }
437
438 DB *getDb() { return db_.get(); }
439
440 // mapped to a rocksdb Delete
441 bool remove(const std::string &key) {
442 auto s = db_->Delete(delete_option_, key);
443
444 if (s.ok()) {
445 return true;
446 } else {
447 std::cerr << s.ToString() << std::endl;
448 return false;
449 }
450 }
451
452 // mapped to a rocksdb Get
453 bool get(const std::string &key, uint64_t *value) {
Marc Kupietz122ba3c2026-08-09 17:13:24 +0200454 flush();
Marc Kupietz39887082024-11-22 18:06:20 +0100455 std::string str;
456 auto s = db_->Get(get_option_, key, &str);
457
458 if (s.IsNotFound()) {
459 // return default value if not found;
460 *value = default_;
461 return true;
462 } else if (s.ok()) {
463 // deserialization
464 if (str.size() != sizeof(uint64_t)) {
465 std::cerr << "value corruption\n";
466 return false;
467 }
468 *value = DecodeFixed64(&str[0]);
469 return true;
470 } else {
471 std::cerr << s.ToString() << std::endl;
472 return false;
473 }
474 }
475
476 uint64_t get(const uint32_t w1, const uint32_t w2, const int8_t dist) {
477 char encoded_key[sizeof(uint64_t)];
478 EncodeFixed64(encoded_key, encodeCollocation(w1, w2, dist));
479 uint64_t value = default_;
480 get(std::string(encoded_key, 8), &value);
481 return value;
482 }
483
Marc Kupietz2f65bcf2026-08-02 14:04:37 +0200484 /* A merge that does not lose the count. rocksdb cancels a write with
485 Status::Incomplete instead of waiting when it is asked not to slow down,
486 and then the increment is simply gone. Such a write was not applied, so
487 repeating it blocking cannot count twice. */
488 void merge_one(const Slice &key) {
Marc Kupietz122ba3c2026-08-09 17:13:24 +0200489 WriteBatch to_write;
490 {
491 std::lock_guard<std::mutex> lock(batch_mutex_);
492 batch_.Merge(key, _one_slice);
493 if (batch_.Count() >= (int)batch_target_)
494 std::swap(batch_, to_write);
Marc Kupietz2f65bcf2026-08-02 14:04:37 +0200495 }
Marc Kupietz122ba3c2026-08-09 17:13:24 +0200496 write_batch(to_write);
Marc Kupietz2f65bcf2026-08-02 14:04:37 +0200497 }
498
Marc Kupietz39887082024-11-22 18:06:20 +0100499 virtual void inc(const std::string &key) {
Marc Kupietz2f65bcf2026-08-02 14:04:37 +0200500 merge_one(Slice(key));
Marc Kupietz39887082024-11-22 18:06:20 +0100501 }
502
503 void inc(const uint64_t key) {
504 char encoded_key[sizeof(uint64_t)];
505 EncodeFixed64(encoded_key, key);
Marc Kupietz2f65bcf2026-08-02 14:04:37 +0200506 merge_one(Slice(encoded_key, sizeof(uint64_t)));
Marc Kupietz39887082024-11-22 18:06:20 +0100507 }
508
509 virtual void inc(uint32_t w1, uint32_t w2, uint8_t dist);
510
511 void dump(uint32_t w1, uint32_t w2, int8_t dist) const;
512
513 vector<Collocator> get_collocators(uint32_t w1);
514
515 vector<Collocator> get_collocators(uint32_t w1, uint32_t max_w2);
516
517 vector<Collocator> get_collocation_scores(uint32_t w1, uint32_t w2);
518
519 vector<Collocator> get_collocators(uint32_t w1, uint32_t min_w2,
520 uint32_t max_w2);
521
522 void applyCAMeasures(uint32_t w1, uint32_t w2,
523 uint64_t *sumWindow, uint64_t sum,
524 int usedPositions, int true_window_size,
525 Collocator *result) const;
526
527 void dumpSparseLlr(uint32_t w1, uint32_t min_cooccur);
528
529 string collocators2json(uint32_t w1, const vector<Collocator>& collocators);
530
531 // mapped to a rocksdb Merge operation
532 virtual bool add(const std::string &key, uint64_t value) {
533 char encoded[sizeof(uint64_t)];
534 EncodeFixed64(encoded, value);
535 Slice slice(encoded, sizeof(uint64_t));
536 auto s = db_->Merge(merge_option_, key, slice);
537
538 if (s.ok()) {
539 return true;
540 } else {
541 std::cerr << s.ToString() << std::endl;
542 return false;
543 }
544 }
545
546 CollocatorIterator *SeekIterator(uint64_t w1, uint64_t w2, int8_t dist) const;
Marc Kupietz2f65bcf2026-08-02 14:04:37 +0200547
548 /* Writes what is still in memory and closes the database. Without this
549 everything that has not been flushed yet is lost when the process ends,
550 because the write ahead log is switched off for speed. */
551 void close() {
552 if (!db_)
553 return;
Marc Kupietz122ba3c2026-08-09 17:13:24 +0200554 flush();
Marc Kupietz2f65bcf2026-08-02 14:04:37 +0200555 if (stalled_writes_ > 0)
556 std::cerr << "collocatordb: repeated " << stalled_writes_
557 << " writes that rocksdb had cancelled" << std::endl;
558 if (failed_writes_ > 0)
559 std::cerr << "collocatordb: " << failed_writes_
560 << " writes failed, the database is missing counts" << std::endl;
561 FlushOptions flush_options;
562 flush_options.wait = true;
563 Status s = db_->Flush(flush_options);
564 if (!s.ok() && !s.IsNotSupported())
565 std::cerr << "collocatordb: cannot write what is still in memory: "
566 << s.ToString() << std::endl;
567 db_.reset();
568 }
569
Marc Kupietz39887082024-11-22 18:06:20 +0100570};
571
Marc Kupietz2f65bcf2026-08-02 14:04:37 +0200572CollocatorDB::~CollocatorDB() { close(); }
573
Marc Kupietz39887082024-11-22 18:06:20 +0100574CollocatorDB::CollocatorDB(const char *db_name,
575 bool read_only = false) {
Marc Kupietz122ba3c2026-08-09 17:13:24 +0200576 batch_target_ = (size_t)env_size("COLLOCATORDB_BATCH_SIZE", 65536);
Marc Kupietz39887082024-11-22 18:06:20 +0100577 // merge_option_.sync = true;
578 if (read_only)
579 db_ = OpenDbForRead(strdup(db_name));
580 else
581 db_ = OpenDb(db_name);
582 assert(db_);
583 uint64_t one = 1;
584 EncodeFixed64(_one, one);
585 _one_slice = Slice(_one, sizeof(uint64_t));
586}
587
588void CollocatorDB::inc(const uint32_t w1, const uint32_t w2,
589 const uint8_t dist) {
590 inc(encodeCollocation(w1, w2, dist));
591}
592
593void CollocatorDB::readVocab(const string& fname) {
594 char strbuf[2048];
595 uint64_t freq;
596 FILE *fin = fopen(fname.c_str(), "rb");
597 if (fin == nullptr) {
598 cout << "Vocabulary file " << fname << " not found\n";
599 exit(1);
600 }
601 uint64_t i = 0;
602 while (fscanf(fin, "%s %lu", strbuf, &freq) == 2) {
603 _vocab.push_back({strbuf, freq});
604 total += freq;
605 i++;
606 }
607 fclose(fin);
608
609 char size_fname[256];
610 strcpy(size_fname, fname.c_str());
611 char *pos = strstr(size_fname, ".vocab");
612 if (pos) {
613 *pos = 0;
614 strcat(size_fname, ".size");
615 FILE *fp = fopen(size_fname, "r");
616 if (fp != nullptr) {
617 fscanf(fp, "%lu", &sentences);
618 fscanf(fp, "%lu", &total);
619 float sl = (float)total / (float)sentences;
620 float w = WINDOW_SIZE;
621 avg_window_size =
622 ((sl > 2 * w ? (sl - 2 * w) * 2 * w : 0) + (double)w * (3 * w - 1)) /
623 sl;
624 fprintf(stdout,
625 "Size corrections found: corpus size: %lu tokens in %lu "
626 "sentences, avg. sentence size: %f, avg. window size: %f\n",
627 total, sentences, sl, avg_window_size);
628 fclose(fp);
629 } else {
630 // std::cout << "size file " << size_fname << " not found\n";
631 }
632 } else {
633 std::cout << "cannot determine size file " << size_fname << "\n";
634 }
635}
636
637std::shared_ptr<DB> CollocatorDB::OpenDbForRead(const char *name) {
Marc Kupietz65d44792026-07-31 09:46:34 +0900638 ROCKSDB_DB_HANDLE db;
Marc Kupietz39887082024-11-22 18:06:20 +0100639 Options options;
640 options.env->SetBackgroundThreads(4);
641 options.create_if_missing = true;
642 options.merge_operator = std::make_shared<CountMergeOperator>();
643 options.max_successive_merges = 0;
644 // options.prefix_extractor.reset(NewFixedPrefixTransform(8));
645 options.IncreaseParallelism();
646 options.OptimizeLevelStyleCompaction();
647 options.prefix_extractor.reset(NewFixedPrefixTransform(3));
648 ostringstream dbname, vocabname;
649 dbname << name << ".rocksdb";
650 auto s = DB::OpenForReadOnly(options, dbname.str(), &db);
651 if (!s.ok()) {
652 std::cerr << s.ToString() << std::endl;
653 assert(false);
654 }
655 vocabname << name << ".vocab";
656 readVocab(vocabname.str());
Marc Kupietz65d44792026-07-31 09:46:34 +0900657 return std::shared_ptr<DB>(ROCKSDB_DB_RELEASE(db));
Marc Kupietz39887082024-11-22 18:06:20 +0100658}
659
Marc Kupietz2f65bcf2026-08-02 14:04:37 +0200660 /* Reads a size from the environment, so that a long indexing run can be
661 tuned without recompiling. Returns the default when unset or unusable. */
662 static uint64_t env_size(const char *name, uint64_t fallback) {
663 const char *value = getenv(name);
664 if (value == nullptr)
665 return fallback;
666 char *end = nullptr;
667 unsigned long long parsed = strtoull(value, &end, 10);
668 if (end == value || parsed == 0) {
669 std::cerr << "collocatordb: ignoring " << name << "=" << value << std::endl;
670 return fallback;
671 }
672 return (uint64_t)parsed;
673 }
674
Marc Kupietzc630c152025-01-23 11:17:47 +0100675 std::shared_ptr<DB> CollocatorDB::OpenDb(const char *dbname) {
Marc Kupietz65d44792026-07-31 09:46:34 +0900676 ROCKSDB_DB_HANDLE db;
Marc Kupietzc630c152025-01-23 11:17:47 +0100677 Options options;
Marc Kupietz39887082024-11-22 18:06:20 +0100678
Marc Kupietzc630c152025-01-23 11:17:47 +0100679 int max_cores = static_cast<int>(std::thread::hardware_concurrency());
680
Marc Kupietzc630c152025-01-23 11:17:47 +0100681 options.create_if_missing = true;
682 options.merge_operator = std::make_shared<CountMergeOperator>();
Marc Kupietzc630c152025-01-23 11:17:47 +0100683
Marc Kupietz2f65bcf2026-08-02 14:04:37 +0200684 /* Indexing a corpus is one long stream of merge operands for the same
685 keys. rocksdb only collapses them when memtables are merged and when
686 files are compacted, so the settings aim at doing that early and often -
687 every collapsed operand is one less to write, to read and to merge
688 again later.
Marc Kupietzc630c152025-01-23 11:17:47 +0100689
Marc Kupietz2f65bcf2026-08-02 14:04:37 +0200690 All of it can be overridden from the environment, to be able to tune a
691 run that takes days without recompiling. */
692 options.write_buffer_size = env_size("COLLOCATORDB_WRITE_BUFFER_MB", 256) << 20;
693 options.max_write_buffer_number = (int)env_size("COLLOCATORDB_WRITE_BUFFERS", 8);
694 /* collapses the operands of several memtables before they are written */
695 options.min_write_buffer_number_to_merge =
696 (int)env_size("COLLOCATORDB_WRITE_BUFFERS_TO_MERGE", 4);
697
698 /* Compaction has to keep up with the writer, otherwise the level 0 files
699 pile up and every read has to merge through all of them. */
700 options.max_background_jobs =
701 (int)env_size("COLLOCATORDB_BACKGROUND_JOBS",
702 max_cores > 4 ? (max_cores < 32 ? max_cores : 32) : 4);
703 options.max_subcompactions = (int)env_size("COLLOCATORDB_SUBCOMPACTIONS", 4);
704 options.level0_file_num_compaction_trigger = 4;
705 options.level0_slowdown_writes_trigger = 20;
706 options.level0_stop_writes_trigger = 36;
707
708 /* Merge operands are inserted one writer at a time, rocksdb does not
709 support concurrent memtable writes for them, which is why more threads
710 in the indexer do not help beyond a certain point. */
711 options.allow_concurrent_memtable_write = false;
Marc Kupietzc630c152025-01-23 11:17:47 +0100712 options.enable_write_thread_adaptive_yield = true;
Marc Kupietzc630c152025-01-23 11:17:47 +0100713 options.allow_mmap_reads = true;
714
Marc Kupietzc630c152025-01-23 11:17:47 +0100715 BlockBasedTableOptions table_options;
Marc Kupietz2f65bcf2026-08-02 14:04:37 +0200716 table_options.block_cache =
717 NewLRUCache(env_size("COLLOCATORDB_BLOCK_CACHE_MB", 512) << 20);
Marc Kupietzc630c152025-01-23 11:17:47 +0100718 options.table_factory.reset(NewBlockBasedTableFactory(table_options));
719
Marc Kupietz2f65bcf2026-08-02 14:04:37 +0200720 /* No write ahead log: an interrupted indexing run is repeated, and the log
721 would double the amount written. Everything that has not been flushed is
722 lost when the process dies, which is what close_collocatordb() is for. */
723 merge_option_.disableWAL = true;
724 merge_option_.sync = false;
725 /* Let rocksdb slow the writer down when compaction falls behind, instead
726 of cancelling the write. It used to be told to do neither, which threw
727 counts away. merge_one() repeats a cancelled write, whatever the
728 settings are. */
729 merge_option_.low_pri = false;
730 merge_option_.no_slowdown = false;
731 blocking_merge_option_ = merge_option_;
732 blocking_merge_option_.low_pri = false;
733 blocking_merge_option_.no_slowdown = false;
Marc Kupietzc630c152025-01-23 11:17:47 +0100734
735 Status s = DB::Open(options, dbname, &db);
736 if (!s.ok()) {
737 std::cerr << s.ToString() << std::endl;
738 assert(false);
739 }
740 total = 1000;
Marc Kupietz65d44792026-07-31 09:46:34 +0900741 return std::shared_ptr<DB>(ROCKSDB_DB_RELEASE(db));
Marc Kupietz39887082024-11-22 18:06:20 +0100742 }
Marc Kupietz39887082024-11-22 18:06:20 +0100743
744CollocatorIterator *
745CollocatorDB::SeekIterator(uint64_t w1, uint64_t w2, int8_t dist) const {
Marc Kupietz122ba3c2026-08-09 17:13:24 +0200746 flush();
Marc Kupietz39887082024-11-22 18:06:20 +0100747 ReadOptions options;
748 options.prefix_same_as_start = true;
749 char prefixc[sizeof(uint64_t)];
750 EncodeFixed64(prefixc, encodeCollocation(w1, w2, dist));
751 Iterator *it = db_->NewIterator(options);
752 auto *cit = new CollocatorIterator(it);
753 if (w2 > 0)
754 cit->Seek(std::string(prefixc, 6));
755 else
756 cit->Seek(std::string(prefixc, 3));
757 cit->setPrefix(prefixc);
758 return cit;
759}
760
761void CollocatorDB::dump(uint32_t w1, uint32_t w2, int8_t dist) const {
762 auto it = std::unique_ptr<CollocatorIterator>(SeekIterator(w1, w2, dist));
763 for (; it->isValid(); it->Next()) {
764 uint64_t value = it->intValue();
765 uint64_t key = it->intKey();
766 std::cout << "w1:" << W1(key) << ", w2:" << W2(key)
767 << ", dist:" << (int32_t)DIST(key) << " - count:" << value
768 << std::endl;
769 }
770 std::cout << "ready dumping\n";
771}
772
773bool sortByNpmi(const Collocator &lhs, const Collocator &rhs) {
774 return lhs.npmi > rhs.npmi;
775}
776
777bool sortByLfmd(const Collocator &lhs, const Collocator &rhs) {
778 return lhs.lfmd > rhs.lfmd;
779}
780
781bool sortByLlr(const Collocator &lhs, const Collocator &rhs) {
782 return lhs.llr > rhs.llr;
783}
784
785bool sortByLogDice(const Collocator &lhs, const Collocator &rhs) {
786 return lhs.logdice > rhs.logdice;
787}
788
789bool sortByLogDiceAF(const Collocator &lhs, const Collocator &rhs) {
790 return lhs.ldaf > rhs.ldaf;
791}
792
793void CollocatorDB::applyCAMeasures(
794 const uint32_t w1, const uint32_t w2, uint64_t *sumWindow,
795 const uint64_t sum, const int usedPositions, int true_window_size,
796 Collocator *result) const {
797 uint64_t f1 = _vocab[w1].freq, f2 = _vocab[w2].freq;
798 double o = sum, r1 = f1 * true_window_size, c1 = f2, e = r1 * c1 / total,
799 pmi = log2(o / e), md = log2(o * o / e), lfmd = log2(o * o * o / e),
Marc Kupietze889cec2024-11-23 12:08:42 +0100800 llr = ca_ll(f1, f2, sum, total, true_window_size),
801 md_nws = ca_md(f1, f2, sum, total, 2 * WINDOW_SIZE),
802 ld = ca_logdice(f1, f2, sum, total, true_window_size);
Marc Kupietz39887082024-11-22 18:06:20 +0100803
Marc Kupietz0fec0be2026-09-04 19:42:38 +0200804 // LDaf is the auto focus score itself, i.e. the highest value that any
805 // selection of positions reaches. It is deliberately not logDice of the
806 // selected window: the width penalty is what makes the score sensitive to
807 // how concentrated a pair is, and that sensitivity is why LDaf orders
808 // collocates more usefully than the other measures.
Marc Kupietz39887082024-11-22 18:06:20 +0100809 int bestWindow = usedPositions;
Marc Kupietz0fec0be2026-09-04 19:42:38 +0200810 double bestFocus = ca_focus_score(f1, f2, sum, total, true_window_size);
Marc Kupietz39887082024-11-22 18:06:20 +0100811 // if(f1<75000000)
812 // #pragma omp parallel for reduction(max:bestAF)
813 // #pragma omp target teams distribute parallel for reduction(max:bestAF)
814 // map(tofrom:bestAF,currentAF,bestWindow,usedPositions)
815 for (int bitmask = 1; bitmask < (1 << (2 * WINDOW_SIZE)); bitmask++) {
816 if ((bitmask & usedPositions) == 0 || (bitmask & ~usedPositions) > 0)
817 continue;
818 uint64_t currentWindowSum = 0;
819 // #pragma omp target teams distribute parallel for
820 // reduction(+:currentWindowSum) map(tofrom:bitmask,usedPositions)
821 for (int pos = 0; pos < 2 * WINDOW_SIZE; pos++) {
822 if (((1 << pos) & bitmask & usedPositions) != 0)
823 currentWindowSum += sumWindow[pos];
824 }
Marc Kupietz0fec0be2026-09-04 19:42:38 +0200825 double currentAF = ca_focus_score(f1, f2, currentWindowSum, total,
826 __builtin_popcount(bitmask));
827 if (currentAF > bestFocus) {
828 bestFocus = currentAF;
Marc Kupietz39887082024-11-22 18:06:20 +0100829 bestWindow = bitmask;
830 }
831 }
832
833 *result = {w2,
834 f2,
835 sum,
836 pmi,
837 pmi / (-log2(o / total / true_window_size)),
838 llr,
839 lfmd,
840 md,
Marc Kupietze889cec2024-11-23 12:08:42 +0100841 md_nws,
Marc Kupietz39887082024-11-22 18:06:20 +0100842 sumWindow[WINDOW_SIZE],
843 sumWindow[WINDOW_SIZE - 1],
844 ca_pmi(f1, f2, sumWindow[WINDOW_SIZE], total, 1),
845 ca_pmi(f1, f2, sumWindow[WINDOW_SIZE - 1], total, 1),
846 ca_dice(f1, f2, sum, total, true_window_size),
847 ld,
Marc Kupietz0fec0be2026-09-04 19:42:38 +0200848 bestFocus,
Marc Kupietz39887082024-11-22 18:06:20 +0100849 usedPositions,
850 bestWindow};
851}
852
853std::vector<Collocator>
854CollocatorDB::get_collocators(uint32_t w1, uint32_t min_w2,
855 uint32_t max_w2) {
856 std::vector<Collocator> collocators;
857 uint64_t w2, last_w2 = 0xffffffffffffffff;
858 uint64_t maxv = 0, sum = 0;
Marc Kupietzaa354d82026-07-31 09:17:57 +0900859 /* fixed size, so it lives on the stack and cannot be leaked */
860 uint64_t sumWindow[2 * WINDOW_SIZE];
861 memset(sumWindow, 0, sizeof(sumWindow));
Marc Kupietz39887082024-11-22 18:06:20 +0100862 int true_window_size = 1;
863 int usedPositions = 0;
864
865 if (w1 > _vocab.size()) {
866 std::cout << w1 << "> vocabulary size " << _vocab.size() << "\n";
867 w1 -= _vocab.size();
868 }
869#ifdef DEBUG
870 std::cout << "Searching for collocates of " << _vocab[w1].word << "\n";
871#endif
872 // #pragma omp parallel num_threads(40)
873 // #pragma omp single
874 for (auto it =
875 std::unique_ptr<CollocatorIterator>(SeekIterator(w1, min_w2, 0));
876 it->isValid(); it->Next()) {
877 uint64_t value = it->intValue(), key = it->intKey();
878 if ((w2 = W2(key)) > max_w2)
879 continue;
880 if (last_w2 == 0xffffffffffffffff)
881 last_w2 = w2;
882 if (w2 != last_w2) {
883 if (sum >= FREQUENCY_THRESHOLD) {
884 collocators.push_back({});
885 Collocator *result = &(collocators[collocators.size() - 1]);
886 // #pragma omp task firstprivate(last_w2, sumWindow, sum, usedPositions,
887 // true_window_size) shared(w1, result) if(sum > 1000000)
888 {
889 // uint64_t *nsw = (uint64_t *)malloc(sizeof(uint64_t) * 2
890 // *WINDOW_SIZE); memcpy(nsw, sumWindow, sizeof(uint64_t) * 2
891 // *WINDOW_SIZE);
892 applyCAMeasures(w1, last_w2, sumWindow, sum, usedPositions,
893 true_window_size, result);
894 // free(nsw);
895 }
896 }
897 memset(sumWindow, 0, 2 * WINDOW_SIZE * sizeof(uint64_t));
898 usedPositions = 1 << (-DIST(key) + WINDOW_SIZE - (DIST(key) < 0 ? 1 : 0));
899 sumWindow[-DIST(key) + WINDOW_SIZE - (DIST(key) < 0 ? 1 : 0)] = value;
900 last_w2 = w2;
901 maxv = value;
902 sum = value;
903 true_window_size = 1;
904 if (min_w2 == max_w2 && w2 != min_w2)
905 break;
906 } else {
907 sum += value;
908 if (value > maxv)
909 maxv = value;
910 usedPositions |=
911 1 << (-DIST(key) + WINDOW_SIZE - (DIST(key) < 0 ? 1 : 0));
912 sumWindow[-DIST(key) + WINDOW_SIZE - (DIST(key) < 0 ? 1 : 0)] = value;
913 true_window_size++;
914 }
915 }
916
Marc Kupietz2f65bcf2026-08-02 14:04:37 +0200917 /* A collocate is only finished when the next one begins, so the last one is
918 still in sumWindow when the iterator is through. It used to be dropped,
919 which cost every word one of its collocates. */
920 if (last_w2 != 0xffffffffffffffff && sum >= FREQUENCY_THRESHOLD) {
921 collocators.push_back({});
922 applyCAMeasures(w1, last_w2, sumWindow, sum, usedPositions,
923 true_window_size, &(collocators[collocators.size() - 1]));
924 }
925
Marc Kupietz39887082024-11-22 18:06:20 +0100926 // #pragma omp taskwait
927 sort(collocators.begin(), collocators.end(), sortByLogDiceAF);
928
929#ifdef DEBUG
930 int i = 0;
931 for (Collocator c : collocators) {
932 if (i++ > 10)
933 break;
934 std::cout << "w1:" << _vocab[w1].word << ", w2: *" << _vocab[c.w2].word
935 << "*"
936 << "\t f(w1):" << _vocab[w1].freq
937 << "\t f(w2):" << _vocab[c.w2].freq << "\t f(w1, w2):" << c.raw
938 << "\t pmi:" << c.pmi << "\t npmi:" << c.npmi
939 << "\t llr:" << c.llr << "\t md:" << c.md << "\t lfmd:" << c.lfmd
940 << "\t total:" << total << std::endl;
941 }
942#endif
943
944 return collocators;
945}
946
947std::vector<Collocator>
948CollocatorDB::get_collocation_scores(uint32_t w1, uint32_t w2) {
949 return get_collocators(w1, w2, w2);
950}
951
952std::vector<Collocator> CollocatorDB::get_collocators(uint32_t w1) {
953 return get_collocators(w1, 0, UINT32_MAX);
954}
955
956void CollocatorDB::dumpSparseLlr(uint32_t w1, uint32_t min_cooccur) {
957 std::vector<Collocator> collocators;
958 std::stringstream stream;
959 uint64_t w2, last_w2 = 0xffffffffffffffff;
960 uint64_t maxv = 0, total_w1 = 0;
961 bool first = true;
962 for (auto it = std::unique_ptr<CollocatorIterator>(SeekIterator(w1, 0, 0));
963 it->isValid(); it->Next()) {
964 uint64_t value = it->intValue(), key = it->intKey();
965 w2 = W2(key);
966 total_w1 += value;
967 if (last_w2 == 0xffffffffffffffff)
968 last_w2 = w2;
969 if (w2 != last_w2) {
970 if (maxv >= min_cooccur) {
971 double llr =
972 ca_ll(_vocab[w1].freq, _vocab[last_w2].freq, maxv, total, 1);
973 if (first)
974 first = false;
975 else
976 stream << " ";
977 stream << w2 << " " << llr;
978 }
979 last_w2 = w2;
980 maxv = value;
981 } else {
982 if (value > maxv)
983 maxv = value;
984 }
985 }
986 if (first)
987 stream << "1 0.0";
988 stream << "\n";
989 std::cout << stream.str();
990}
991
992Slice CollocatorIterator::key() const {
993 return base_iterator_->key();
994}
995
996Slice CollocatorIterator::value() const {
997 return base_iterator_->value();
998}
999
1000Status CollocatorIterator::status() const {
1001 return base_iterator_->status();
1002}
1003
1004}; // namespace rocksdb
1005
1006string CollocatorDB::getWord(uint32_t w1) { return _vocab[w1].word; }
1007
1008uint64_t CollocatorDB::getWordId(const char *word) const {
Marc Kupietz979580e2024-11-21 18:05:07 +01001009 for (uint64_t i = 0; i < _vocab.size(); i++) {
1010 if (strcmp(_vocab[i].word.c_str(), word) == 0)
1011 return i;
1012 }
1013 return 0;
1014}
1015
Marc Kupietzd26b1052024-12-10 16:56:39 +01001016uint64_t CollocatorDB::getCorpusSize() const {
1017 return total;
1018}
1019
Marc Kupietz21b964c2024-12-10 17:10:50 +01001020uint64_t CollocatorDB::getWordFrequency(uint64_t w1) {
1021 return _vocab[w1].freq;
1022}
1023
Marc Kupietz39887082024-11-22 18:06:20 +01001024string CollocatorDB::collocators2json(uint32_t w1,
1025 const vector<Collocator>& collocators) {
Marc Kupietzc8ddf452018-01-07 21:33:12 +01001026 ostringstream s;
Marc Kupietz0dd86ef2018-01-11 22:23:17 +01001027 int i = 0;
Marc Kupietz39887082024-11-22 18:06:20 +01001028 s << " { \"f1\": " << _vocab[w1].freq << "," << R"("w1":")"
1029 << string(_vocab[w1].word) << "\", " << "\"N\": " << total << ", "
1030 << "\"collocates\": [";
Marc Kupietzc8ddf452018-01-07 21:33:12 +01001031 bool first = true;
1032 for (Collocator c : collocators) {
Marc Kupietz39887082024-11-22 18:06:20 +01001033 if (strncmp(_vocab[c.w2].word.c_str(), "quot", 4) == 0)
1034 continue;
Marc Kupietz0dd86ef2018-01-11 22:23:17 +01001035 if (i++ > 200)
1036 break;
Marc Kupietz12af0192021-03-13 18:05:14 +01001037 if (!first)
Marc Kupietzc8ddf452018-01-07 21:33:12 +01001038 s << ",\n";
1039 else
1040 first = false;
1041 s << "{"
Marc Kupietz39887082024-11-22 18:06:20 +01001042 "\"word\":\""
1043 << (string(_vocab[c.w2].word) == "<num>"
1044 ? string("###")
1045 : string(_vocab[c.w2].word))
1046 << "\"," << "\"f2\":" << c.f2 << "," << "\"f\":" << c.raw << ","
1047 << "\"npmi\":" << c.npmi << "," << "\"pmi\":" << c.pmi << ","
1048 << "\"llr\":" << c.llr << "," << "\"lfmd\":" << c.lfmd << ","
Marc Kupietze889cec2024-11-23 12:08:42 +01001049 << "\"md\":" << c.md << "," << "\"md_nws\":" << c.md_nws << "," << "\"dice\":" << c.dice << ","
Marc Kupietz39887082024-11-22 18:06:20 +01001050 << "\"ld\":" << c.logdice << "," << "\"ln_count\":" << c.left_raw << ","
1051 << "\"rn_count\":" << c.right_raw << "," << "\"ln_pmi\":" << c.left_pmi
1052 << "," << "\"rn_pmi\":" << c.right_pmi << "," << "\"ldaf\":" << c.ldaf
1053 << "," << "\"win\":" << c.window << "," << "\"afwin\":" << c.af_window
1054 << "}";
Marc Kupietzc8ddf452018-01-07 21:33:12 +01001055 }
Marc Kupietze9627152019-02-04 12:32:12 +01001056 s << "]}\n";
Marc Kupietz0421d092021-03-13 18:05:14 +01001057 // std::cout << s.str();
Marc Kupietzc8ddf452018-01-07 21:33:12 +01001058 return s.str();
1059}
1060
Marc Kupietz39887082024-11-22 18:06:20 +01001061typedef CollocatorDB COLLOCATORS;
Marc Kupietz06c9a9f2018-01-02 16:56:43 +01001062
1063extern "C" {
Marc Kupietz12af0192021-03-13 18:05:14 +01001064#ifdef __clang__
1065#pragma clang diagnostic push
1066#pragma ide diagnostic ignored "OCUnusedGlobalDeclarationInspection"
1067#endif
Marc Kupietz39887082024-11-22 18:06:20 +01001068DLL_EXPORT COLLOCATORS *open_collocatordb_for_write(char *dbname) {
1069 return new CollocatorDB(dbname, false);
1070}
Marc Kupietz12af0192021-03-13 18:05:14 +01001071
Marc Kupietz2f65bcf2026-08-02 14:04:37 +02001072DLL_EXPORT void close_collocatordb(COLLOCATORS *db) { delete db; }
1073
Marc Kupietz39887082024-11-22 18:06:20 +01001074DLL_EXPORT COLLOCATORS *open_collocatordb(char *dbname) {
1075 return new CollocatorDB(dbname, true);
1076}
Marc Kupietz06c9a9f2018-01-02 16:56:43 +01001077
Marc Kupietz39887082024-11-22 18:06:20 +01001078DLL_EXPORT void inc_collocator(COLLOCATORS *db, uint32_t w1, uint32_t w2,
1079 int8_t dist) {
1080 db->inc(w1, w2, dist);
1081}
Marc Kupietzc8ddf452018-01-07 21:33:12 +01001082
Marc Kupietz39887082024-11-22 18:06:20 +01001083DLL_EXPORT void dump_collocators(COLLOCATORS *db, uint32_t w1, uint32_t w2,
1084 int8_t dist) {
1085 db->dump(w1, w2, dist);
1086}
Marc Kupietzc8ddf452018-01-07 21:33:12 +01001087
Marc Kupietz39887082024-11-22 18:06:20 +01001088DLL_EXPORT COLLOCATORS *get_collocators(COLLOCATORS *db, uint32_t w1) {
1089 std::vector<Collocator> c = db->get_collocators(w1);
1090 if (c.empty())
1091 return nullptr;
Marc Kupietz2f65bcf2026-08-02 14:04:37 +02001092 /* one entry more than there are collocators, terminated with w2 == 0 and
1093 raw == 0, so that callers can tell where the array ends */
1094 uint64_t size = (c.size() + 1) * sizeof c[0];
Marc Kupietz39887082024-11-22 18:06:20 +01001095 auto *p = (COLLOCATORS *)malloc(size);
Marc Kupietz2f65bcf2026-08-02 14:04:37 +02001096 memset(p, 0, size);
1097 memcpy(p, c.data(), c.size() * sizeof c[0]);
Marc Kupietz39887082024-11-22 18:06:20 +01001098 return p;
1099}
Marc Kupietz88d116b2021-03-13 18:05:14 +01001100
Marc Kupietz39887082024-11-22 18:06:20 +01001101DLL_EXPORT COLLOCATORS *get_collocation_scores(COLLOCATORS *db, uint32_t w1,
1102 uint32_t w2) {
1103 std::vector<Collocator> c = db->get_collocation_scores(w1, w2);
1104 if (c.empty())
1105 return nullptr;
Marc Kupietz2f65bcf2026-08-02 14:04:37 +02001106 /* one entry more than there are collocators, terminated with w2 == 0 and
1107 raw == 0, so that callers can tell where the array ends */
1108 uint64_t size = (c.size() + 1) * sizeof c[0];
Marc Kupietz39887082024-11-22 18:06:20 +01001109 auto *p = (COLLOCATORS *)malloc(size);
Marc Kupietz2f65bcf2026-08-02 14:04:37 +02001110 memset(p, 0, size);
1111 memcpy(p, c.data(), c.size() * sizeof c[0]);
Marc Kupietz39887082024-11-22 18:06:20 +01001112 return p;
1113}
Marc Kupietzca3a52e2018-06-05 14:16:23 +02001114
Marc Kupietz39887082024-11-22 18:06:20 +01001115DLL_EXPORT char *get_word(COLLOCATORS *db, uint32_t w) {
1116 return strdup(db->getWord(w).c_str());
1117}
Marc Kupietz979580e2024-11-21 18:05:07 +01001118
Marc Kupietz39887082024-11-22 18:06:20 +01001119DLL_EXPORT uint64_t get_word_id(COLLOCATORS *db, char *word) {
1120 return db->getWordId(word);
1121}
Marc Kupietzb4a683c2021-03-14 09:19:44 +01001122
Marc Kupietz39887082024-11-22 18:06:20 +01001123DLL_EXPORT void read_vocab(COLLOCATORS *db, char *fname) {
1124 std::string fName(fname);
1125 db->readVocab(fName);
1126}
Marc Kupietz88d116b2021-03-13 18:05:14 +01001127
Marc Kupietz39887082024-11-22 18:06:20 +01001128DLL_EXPORT const char *get_collocators_as_json(COLLOCATORS *db, uint32_t w1) {
1129 return strdup(db->collocators2json(w1, db->get_collocators(w1)).c_str());
1130}
Marc Kupietzb4a683c2021-03-14 09:19:44 +01001131
Marc Kupietz39887082024-11-22 18:06:20 +01001132DLL_EXPORT const char *
1133get_collocation_scores_as_json(COLLOCATORS *db, uint32_t w1, uint32_t w2) {
1134 return strdup(
1135 db->collocators2json(w1, db->get_collocation_scores(w1, w2)).c_str());
1136}
1137
1138DLL_EXPORT const char *get_version() { return PROJECT_VERSION; }
Marc Kupietz6208fd72024-11-15 15:46:19 +01001139
Marc Kupietzd26b1052024-12-10 16:56:39 +01001140DLL_EXPORT uint64_t get_corpus_size(COLLOCATORS *db) { return db->getCorpusSize(); };
1141
Marc Kupietz21b964c2024-12-10 17:10:50 +01001142DLL_EXPORT uint64_t get_word_frequency(COLLOCATORS *db, uint64_t w1) {
1143 return db->getWordFrequency(w1);
1144}
1145
Marc Kupietz12af0192021-03-13 18:05:14 +01001146#ifdef __clang__
1147#pragma clang diagnostic push
1148#endif
Marc Kupietz06c9a9f2018-01-02 16:56:43 +01001149}