blob: 5a08486b5f6ff769ee3e75267e2a483f23326dda [file] [log] [blame]
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001#include <collocatordb.h>
Marc Kupietz969cab92019-08-05 11:13:42 +02002#include <math.h>
3#include <pthread.h>
4#include <stdio.h>
Marc Kupietzc0d41872021-02-25 16:33:22 +01005#include <stdlib.h>
Marc Kupietz969cab92019-08-05 11:13:42 +02006#include <string.h>
7#include <sys/mman.h>
Marc Kupietze288d8e2024-11-15 16:18:50 +01008#include <fcntl.h>
9#include <unistd.h>
10#include <perl.h>
Marc Kupietzf11d20c2019-08-02 15:42:04 +020011
12#define max_size 2000
13#define max_w 50
14#define MAX_NEIGHBOURS 1000
Marc Kupietz565274e2026-09-06 13:17:17 +020015#define MAX_TARGET_WORDS 100
Marc Kupietzf11d20c2019-08-02 15:42:04 +020016#define MAX_WORDS -1
17#define MAX_THREADS 100
18#define MAX_CC 50
19#define EXP_TABLE_SIZE 1000
20#define MAX_EXP 6
21#define MIN_RESP 0.50
22
Marc Kupietzc48d3902026-08-21 12:13:45 +020023/* Per request diagnostics. Every neighbourhood request used to write a few
24 hundred lines - one per window position, one per vocabulary lookup, the
25 whole JSON of a similar profile - and a busy instance turned that into a
26 million lines a day, which is what filled the log partition of the
27 production machine. They are off unless DEREKOVECS_DEBUG is set to
28 something other than 0 or the empty string. Startup messages and error
29 messages are not affected. */
30static int derekovecs_debug(void) {
31 static int enabled = -1;
32 if (enabled < 0) {
33 const char *e = getenv("DEREKOVECS_DEBUG");
34 enabled = (e && *e && strcmp(e, "0") != 0) ? 1 : 0;
35 }
36 return enabled;
37}
38
39#define DEBUG_PRINTF(...) do { if (derekovecs_debug()) printf(__VA_ARGS__); } while (0)
40#define DEBUG_EPRINTF(...) do { if (derekovecs_debug()) fprintf(stderr, __VA_ARGS__); } while (0)
41#define DEBUG_FFLUSH() do { if (derekovecs_debug()) fflush(stdout); } while (0)
42
Marc Kupietzf11d20c2019-08-02 15:42:04 +020043//the thread function
44void *connection_handler(void *);
45
46typedef struct {
Marc Kupietz969cab92019-08-05 11:13:42 +020047 long long wordi;
48 long position;
49 float activation;
50 float average;
51 float cprobability; // column wise probability
52 float cprobability_sum;
53 float probability;
54 float activation_sum;
55 float max_activation;
56 float heat[16];
Marc Kupietzf11d20c2019-08-02 15:42:04 +020057} collocator;
58
59typedef struct {
Marc Kupietz969cab92019-08-05 11:13:42 +020060 collocator *best;
61 int length;
Marc Kupietzf11d20c2019-08-02 15:42:04 +020062} knn;
Marc Kupietz969cab92019-08-05 11:13:42 +020063
Marc Kupietzf11d20c2019-08-02 15:42:04 +020064typedef struct {
Marc Kupietz565274e2026-09-06 13:17:17 +020065 long long wordi[MAX_TARGET_WORDS];
66 /* '+' or '-': the sign with which wordi[i] enters the query vector */
67 char sep[MAX_TARGET_WORDS];
68 /* blank separated tokens of the query that are not in the vocabulary */
69 char oov[max_size];
70 int length; /* number of tokens found in the vocabulary */
71 int subtractions; /* how many of them enter with a '-' */
Marc Kupietzf11d20c2019-08-02 15:42:04 +020072} wordlist;
73
74typedef struct {
75 long cutoff;
76 wordlist *wl;
Marc Kupietz969cab92019-08-05 11:13:42 +020077 char *token;
78 int N;
79 long from;
80 unsigned long upto;
Marc Kupietzf11d20c2019-08-02 15:42:04 +020081 collocator *best;
82 float *target_sums;
83 float *window_sums;
84 float threshold;
85} knnpars;
86
87typedef struct {
88 uint32_t index;
89 float value;
90} sparse_t;
91
92typedef struct {
93 uint32_t len;
94 sparse_t nbr[100];
95} profile_t;
96
Marc Kupietz969cab92019-08-05 11:13:42 +020097float *M, *M2 = 0L, *syn1neg_window, *expTable;
Marc Kupietzf11d20c2019-08-02 15:42:04 +020098char *vocab;
99char *garbage = NULL;
100COLLOCATORDB *cdb = NULL;
101profile_t *sprofiles = NULL;
102size_t sprofiles_qty = 0;
103
104long long words, size, merged_end;
105long long merge_words = 0;
Marc Kupietz969cab92019-08-05 11:13:42 +0200106int num_threads = 20;
107int latin_enc = 0;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200108int window;
109
110/* load collocation profiles if file exists */
111int load_sprofiles(char *vecsname) {
112 char *basename = strdup(vecsname);
113 char *pos = strstr(basename, ".vecs");
Marc Kupietz969cab92019-08-05 11:13:42 +0200114 if (pos)
115 *pos = 0;
116
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200117 char binsprofiles_fname[256];
118 strcpy(binsprofiles_fname, basename);
Marc Kupietz969cab92019-08-05 11:13:42 +0200119 strcat(binsprofiles_fname, ".sprofiles.bin");
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200120 FILE *fp = fopen(binsprofiles_fname, "rb");
121 if (fp == NULL) {
122 printf("Collocation profiles %s not found. No problem.\n", binsprofiles_fname);
123 return 0;
124 }
125 fseek(fp, 0L, SEEK_END);
126 size_t sz = ftell(fp);
127 fclose(fp);
128
129 int fd = open(binsprofiles_fname, O_RDONLY);
Marc Kupietz969cab92019-08-05 11:13:42 +0200130 sprofiles = mmap(0, sz, PROT_READ, MAP_SHARED, fd, 0);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200131 if (sprofiles == MAP_FAILED) {
132 close(fd);
133 fprintf(stderr, "Cannot mmap %s\n", binsprofiles_fname);
134 sprofiles = NULL;
135 return 0;
Marc Kupietz969cab92019-08-05 11:13:42 +0200136 } else {
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200137 sprofiles_qty = sz / sizeof(profile_t);
138 fprintf(stderr, "Successfully mmaped %s containing similar profiles for %ld word forms.\n", binsprofiles_fname, sprofiles_qty);
139 }
140 return 1;
141}
142
Marc Kupietzc0d41872021-02-25 16:33:22 +0100143char *removeExtension(char* myStr) {
144 char *retStr;
145 char *lastExt;
146 if (myStr == NULL) return NULL;
147 if ((retStr = malloc (strlen (myStr) + 1)) == NULL) return NULL;
148 strcpy (retStr, myStr);
149 lastExt = strrchr (retStr, '.');
150 if (lastExt != NULL)
151 *lastExt = '\0';
152 return retStr;
153}
154
Marc Kupietz0efe49b2020-04-06 18:30:22 +0200155int init_net(char *file_name, char *net_name, int latin, int do_open_cdb) {
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200156 FILE *f, *binvecs, *binwords;
Marc Kupietz969cab92019-08-05 11:13:42 +0200157 int binwords_fd, binvecs_fd, net_fd, i;
Marc Kupietz59865a92021-03-11 17:16:51 +0100158 long long a, b;
Marc Kupietz969cab92019-08-05 11:13:42 +0200159 float len;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200160 double val;
161
Marc Kupietzc0d41872021-02-25 16:33:22 +0100162 char binvecs_fname[1024], binwords_fname[1024];
163
164 if (strstr(file_name, ".txt")) {
165 strcpy(binwords_fname, removeExtension(file_name));
166 } else {
167 strcpy(binwords_fname, file_name);
168 }
Marc Kupietz969cab92019-08-05 11:13:42 +0200169 strcat(binwords_fname, ".words");
170 strcpy(binvecs_fname, file_name);
171 strcat(binvecs_fname, ".vecs");
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200172
173 latin_enc = latin;
174 f = fopen(file_name, "rb");
175 if (f == NULL) {
176 printf("Input file %s not found\n", file_name);
177 return -1;
178 }
179 fscanf(f, "%lld", &words);
Marc Kupietz969cab92019-08-05 11:13:42 +0200180 if (MAX_WORDS > 0 && words > MAX_WORDS) words = MAX_WORDS;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200181 fscanf(f, "%lld", &size);
Marc Kupietz969cab92019-08-05 11:13:42 +0200182 if ((binvecs_fd = open(binvecs_fname, O_RDONLY)) < 0 || (binwords_fd = open(binwords_fname, O_RDONLY)) < 0) {
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200183 printf("Converting %s to memory mappable structures\n", file_name);
Marc Kupietz969cab92019-08-05 11:13:42 +0200184 vocab = (char *)malloc((long long)words * max_w * sizeof(char));
185 M = (float *)malloc((long long)words * (long long)size * sizeof(float));
186 if (M == NULL) {
187 printf("Cannot allocate memory: %lld MB %lld %lld\n", (long long)words * size * sizeof(float) / 1048576, words, size);
188 return -1;
189 }
190 if (strstr(file_name, ".txt")) {
Marc Kupietzc0d41872021-02-25 16:33:22 +0100191 printf("%lld words in ascii vector file with vector size %lld\n", words, size);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200192 for (b = 0; b < words; b++) {
193 a = 0;
194 while (1) {
195 vocab[b * max_w + a] = fgetc(f);
196 if (feof(f) || (vocab[b * max_w + a] == ' ')) break;
197 if ((a < max_w) && (vocab[b * max_w + a] != '\n')) a++;
198 }
199 vocab[b * max_w + a] = 0;
200 len = 0;
201 for (a = 0; a < size; a++) {
202 fscanf(f, "%lf", &val);
203 M[a + b * size] = val;
204 len += val * val;
Marc Kupietz969cab92019-08-05 11:13:42 +0200205 }
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200206 len = sqrt(len);
207 for (a = 0; a < size; a++) M[a + b * size] /= len;
208 }
209 } else {
210 for (b = 0; b < words; b++) {
211 a = 0;
212 while (1) {
213 vocab[b * max_w + a] = fgetc(f);
214 if (feof(f) || (vocab[b * max_w + a] == ' ')) break;
215 if ((a < max_w) && (vocab[b * max_w + a] != '\n')) a++;
216 }
217 vocab[b * max_w + a] = 0;
218 fread(&M[b * size], sizeof(float), size, f);
219 len = 0;
220 for (a = 0; a < size; a++) len += M[a + b * size] * M[a + b * size];
221 len = sqrt(len);
222 for (a = 0; a < size; a++) M[a + b * size] /= len;
223 }
224 }
Marc Kupietz969cab92019-08-05 11:13:42 +0200225 if ((binvecs = fopen(binvecs_fname, "wb")) != NULL && (binwords = fopen(binwords_fname, "wb")) != NULL) {
226 fwrite(M, sizeof(float), (long long)words * (long long)size, binvecs);
227 fclose(binvecs);
228 fwrite(vocab, sizeof(char), (long long)words * max_w, binwords);
229 fclose(binwords);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200230 }
Marc Kupietz969cab92019-08-05 11:13:42 +0200231 }
232 if ((binvecs_fd = open(binvecs_fname, O_RDONLY)) >= 0 && (binwords_fd = open(binwords_fname, O_RDONLY)) >= 0) {
233 M = mmap(0, sizeof(float) * (long long)words * (long long)size, PROT_READ, MAP_SHARED, binvecs_fd, 0);
234 vocab = mmap(0, sizeof(char) * (long long)words * max_w, PROT_READ, MAP_SHARED, binwords_fd, 0);
235 if (M == MAP_FAILED || vocab == MAP_FAILED) {
236 close(binvecs_fd);
237 close(binwords_fd);
238 fprintf(stderr, "Cannot mmap %s or %s\n", binwords_fname, binvecs_fname);
239 exit(-1);
240 }
241 } else {
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200242 fprintf(stderr, "Cannot open %s or %s\n", binwords_fname, binvecs_fname);
243 exit(-1);
Marc Kupietz969cab92019-08-05 11:13:42 +0200244 }
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200245 fclose(f);
246
Marc Kupietz969cab92019-08-05 11:13:42 +0200247 if (net_name && strlen(net_name) > 0) {
248 if ((net_fd = open(net_name, O_RDONLY)) >= 0) {
249 window = (lseek(net_fd, 0, SEEK_END) - sizeof(float) * words * size) / words / size / sizeof(float) / 2;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200250 // lseek(net_fd, sizeof(float) * words * size, SEEK_SET);
251 // munmap(M, sizeof(float) * words * size);
252 M2 = mmap(0, sizeof(float) * words * size + sizeof(float) * 2 * window * size * words, PROT_READ, MAP_SHARED, net_fd, 0);
253 if (M2 == MAP_FAILED) {
254 close(net_fd);
255 fprintf(stderr, "Cannot mmap %s\n", net_name);
256 exit(-1);
257 }
Marc Kupietz969cab92019-08-05 11:13:42 +0200258 syn1neg_window = M2 + words * size;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200259 } else {
260 fprintf(stderr, "Cannot open %s\n", net_name);
261 exit(-1);
262 }
263 fprintf(stderr, "Successfully memmaped %s. Determined window size: %d\n", net_name, window);
264
Marc Kupietz0efe49b2020-04-06 18:30:22 +0200265 if (do_open_cdb) {
266 char collocatordb_name[2048];
267 strcpy(collocatordb_name, net_name);
268 char *ext = rindex(collocatordb_name, '.');
269 if (ext) {
270 strcpy(ext, ".rocksdb");
271 if (access(collocatordb_name, R_OK) == 0) {
272 *ext = 0;
273 fprintf(stderr, "Opening collocator DB %s\n", collocatordb_name);
274 cdb = open_collocatordb(collocatordb_name);
Marc Kupietzc0d41872021-02-25 16:33:22 +0100275 } else {
276 fprintf(stderr, "Cannot open collocator DB %s\n", collocatordb_name);
Marc Kupietz0efe49b2020-04-06 18:30:22 +0200277 }
Marc Kupietz969cab92019-08-05 11:13:42 +0200278 }
279 }
280 }
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200281
Marc Kupietz969cab92019-08-05 11:13:42 +0200282 expTable = (float *)malloc((EXP_TABLE_SIZE + 1) * sizeof(float));
283 for (i = 0; i < EXP_TABLE_SIZE; i++) {
284 expTable[i] = exp((i / (float)EXP_TABLE_SIZE * 2 - 1) * MAX_EXP); // Precompute the exp() table
285 expTable[i] = expTable[i] / (expTable[i] + 1); // Precompute f(x) = x / (x + 1)
286 }
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200287
288 return 0;
289}
290
Marc Kupietzd6a163c2026-07-30 14:48:10 +0900291/* The collocator threads of one request accumulate their activation sums per
292 window position here. This must not be shared between requests, so every
293 request allocates its own array. Only valid once init_net() has determined
294 the window size, i.e. if M2 is set. */
295float *new_window_sums() {
296 return calloc((window + 1) * 2, sizeof(float));
297}
298
Marc Kupietz969cab92019-08-05 11:13:42 +0200299long mergeVectors(char *file_name) {
Marc Kupietz59865a92021-03-11 17:16:51 +0100300 FILE *f;
301 int binwords_fd, binvecs_fd;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200302 float *merge_vecs;
303 char *merge_vocab;
Marc Kupietz969cab92019-08-05 11:13:42 +0200304 /* long long merge_words, merge_size; */
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200305 long long merge_size;
306
Marc Kupietz969cab92019-08-05 11:13:42 +0200307 char binvecs_fname[256], binwords_fname[256];
Marc Kupietzc0d41872021-02-25 16:33:22 +0100308
309
Marc Kupietz969cab92019-08-05 11:13:42 +0200310 strcpy(binwords_fname, file_name);
311 strcat(binwords_fname, ".words");
312 strcpy(binvecs_fname, file_name);
313 strcat(binvecs_fname, ".vecs");
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200314
315 f = fopen(file_name, "rb");
316 if (f == NULL) {
317 printf("Input file %s not found\n", file_name);
Marc Kupietz59865a92021-03-11 17:16:51 +0100318 exit(-1);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200319 }
320 fscanf(f, "%lld", &merge_words);
321 fscanf(f, "%lld", &merge_size);
Marc Kupietz969cab92019-08-05 11:13:42 +0200322 if (merge_size != size) {
323 fprintf(stderr, "vectors must have the same length\n");
324 exit(-1);
325 }
326 if ((binvecs_fd = open(binvecs_fname, O_RDONLY)) >= 0 && (binwords_fd = open(binwords_fname, O_RDONLY)) >= 0) {
327 merge_vecs = malloc(sizeof(float) * (words + merge_words) * size);
328 merge_vocab = malloc(sizeof(char) * (words + merge_words) * max_w);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200329 if (merge_vecs == NULL || merge_vocab == NULL) {
Marc Kupietz969cab92019-08-05 11:13:42 +0200330 close(binvecs_fd);
331 close(binwords_fd);
332 fprintf(stderr, "Cannot reserve memory for %s or %s\n", binwords_fname, binvecs_fname);
333 exit(-1);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200334 }
335 read(binvecs_fd, merge_vecs, merge_words * size * sizeof(float));
336 read(binwords_fd, merge_vocab, merge_words * max_w);
Marc Kupietz969cab92019-08-05 11:13:42 +0200337 } else {
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200338 fprintf(stderr, "Cannot open %s or %s\n", binwords_fname, binvecs_fname);
339 exit(-1);
Marc Kupietz969cab92019-08-05 11:13:42 +0200340 }
341 printf("Successfully reallocated memory\nMerging...\n");
342 fflush(stdout);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200343 memcpy(merge_vecs + merge_words * size, M, words * size * sizeof(float));
344 memcpy(merge_vocab + merge_words * max_w, vocab, words * max_w);
345 munmap(M, words * size * sizeof(float));
346 munmap(vocab, words * max_w);
347 M = merge_vecs;
348 vocab = merge_vocab;
349 merged_end = merge_words;
350 words += merge_words;
351 fclose(f);
Marc Kupietz969cab92019-08-05 11:13:42 +0200352 printf("merged_end: %lld, words: %lld\n", merged_end, words);
353 //printBiggestMergedDifferences();
354 return ((long)merged_end);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200355}
356
357void filter_garbage() {
358 long i;
359 unsigned char *w, previous, c;
360 garbage = malloc(words);
361 memset(garbage, 0, words);
362 for (i = 0; i < words; i++) {
Marc Kupietz59865a92021-03-11 17:16:51 +0100363 w = (unsigned char *) vocab + i * max_w;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200364 previous = 0;
Marc Kupietz59865a92021-03-11 17:16:51 +0100365 if (strncmp("quot", (const char *)w, 4) == 0) {
Marc Kupietz969cab92019-08-05 11:13:42 +0200366 garbage[i] = 1;
367 // printf("Gargabe: %s\n", vocab + i * max_w);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200368 } else {
Marc Kupietz969cab92019-08-05 11:13:42 +0200369 while ((c = *w++) && !garbage[i]) {
370 if (((c <= 90 && c >= 65) && (previous >= 97 && previous <= 122)) ||
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200371 (previous == '-' && (c & 32)) ||
Marc Kupietz969cab92019-08-05 11:13:42 +0200372 (previous == 0xc2 && (c == 0xa4 || c == 0xb6)) ||
373 (previous == 'q' && c == 'u' && *(w) == 'o' && *(w + 1) == 't') || /* quot */
374 c == '<') {
375 garbage[i] = 1;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200376 continue;
377 }
378 previous = c;
379 }
380 }
381 }
382 return;
383}
384
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200385knn *simpleGetCollocators(int word, int number, long cutoff, int *result) {
386 knnpars *pars = calloc(sizeof(knnpars), 1);
Marc Kupietz59865a92021-03-11 17:16:51 +0100387 float *target_sums = NULL;
388 float *my_window_sums = malloc(sizeof(float) * (window + 1) * 2);
Marc Kupietz969cab92019-08-05 11:13:42 +0200389 pars->cutoff = (cutoff ? cutoff : 300000);
Marc Kupietz59865a92021-03-11 17:16:51 +0100390 long a;
Marc Kupietz969cab92019-08-05 11:13:42 +0200391 for (a = 0; a < cutoff; a++)
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200392 target_sums[a] = 0;
393 pars->target_sums = target_sums;
Marc Kupietz59865a92021-03-11 17:16:51 +0100394 pars->window_sums = my_window_sums;
Marc Kupietz969cab92019-08-05 11:13:42 +0200395 pars->N = (number ? number : 20);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200396 pars->from = 0;
Marc Kupietz969cab92019-08-05 11:13:42 +0200397 pars->upto = window * 2 - 1;
398 knn *syn_nbs = NULL; // = (knn*) getCollocators(pars);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200399 free(pars);
Marc Kupietz59865a92021-03-11 17:16:51 +0100400 free(my_window_sums);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200401 free(target_sums);
402 return syn_nbs;
403}
404
Marc Kupietz04135302026-07-30 14:40:02 +0900405/* Frees a knn result as returned by getCollocators() via pthread_exit(). */
406void free_knn(knn *nbs) {
407 if (nbs == NULL)
408 return;
409 free(nbs->best);
410 free(nbs);
411}
412
Marc Kupietz565274e2026-09-06 13:17:17 +0200413/* Predicts the collocates of a query. The score of a collocate is
414 sigmoid(q . syn1neg[target, position]), which is linear in q before the
415 sigmoid, so the query does not have to be a single word: q can be the same
416 signed combination of input vectors that the paradigmatic side searches
417 around, and "König - Mann + Frau" asks for the contexts that König and Frau
418 predict but Mann does not.
419
420 The combination is the mean over the positive terms rather than their sum,
421 because the sigmoid is only informative over a narrow range of q . syn1neg
422 and the strongest collocates of a single word already sit at the top of it.
423 A plain sum would push them past MAX_EXP, where they all saturate to the
424 same value. Dividing by the number of terms keeps every query in the range
425 the threshold and the auto focus below are calibrated for, and leaves the
426 single word case exactly as it was: one term, divisor one. A balanced
427 analogy has one term as well, +1 -1 +1, and lands in the same range. */
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200428void *getCollocators(void *args) {
429 knnpars *pars = args;
Marc Kupietz969cab92019-08-05 11:13:42 +0200430 int N = pars->N;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200431
Marc Kupietz565274e2026-09-06 13:17:17 +0200432 wordlist *wl = pars->wl;
Marc Kupietz969cab92019-08-05 11:13:42 +0200433 knn *nbs = NULL;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200434 long window_layer_size = size * window * 2;
Marc Kupietz565274e2026-09-06 13:17:17 +0200435 long a, b, c, d, op, window_offset, target, max_target = 0, maxmax_target;
436 float f, max_f, maxmax_f, scale;
437 float qvec[max_size];
438 int terms = 0;
Marc Kupietz969cab92019-08-05 11:13:42 +0200439 float *target_sums = NULL, worstbest, wpos_sum;
440 collocator *best;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200441
Marc Kupietz565274e2026-09-06 13:17:17 +0200442 if (M2 == NULL || wl == NULL || wl->length < 1)
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200443 return NULL;
444
Marc Kupietz565274e2026-09-06 13:17:17 +0200445 for (a = 0; a < wl->length; a++) terms += (wl->sep[a] == '-' ? -1 : 1);
446 scale = (terms > 1 ? 1.0f / terms : 1.0f);
447 for (c = 0; c < size; c++) qvec[c] = 0;
448 for (a = 0; a < wl->length; a++) {
449 long long off = wl->wordi[a] * size;
450 if (wl->sep[a] == '-')
451 for (c = 0; c < size; c++) qvec[c] -= M2[off + c];
452 else
453 for (c = 0; c < size; c++) qvec[c] += M2[off + c];
454 }
455 for (c = 0; c < size; c++) qvec[c] *= scale;
456
Marc Kupietz969cab92019-08-05 11:13:42 +0200457 a = posix_memalign((void **)&target_sums, 128, pars->cutoff * sizeof(float));
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200458 memset(target_sums, 0, pars->cutoff * sizeof(float));
Marc Kupietz969cab92019-08-05 11:13:42 +0200459 best = malloc((N > 200 ? N : 200) * sizeof(collocator));
460 memset(best, 0, (N > 200 ? N : 200) * sizeof(collocator));
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200461 worstbest = pars->threshold;
462
463 for (b = 0; b < pars->cutoff; b++)
Marc Kupietz969cab92019-08-05 11:13:42 +0200464 target_sums[b] = 0;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200465 for (b = 0; b < N; b++) {
Marc Kupietz969cab92019-08-05 11:13:42 +0200466 best[b].wordi = -1;
467 best[b].probability = 1;
468 best[b].activation = worstbest;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200469 }
470
Marc Kupietz565274e2026-09-06 13:17:17 +0200471 d = wl->wordi[0];
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200472 maxmax_f = -1;
473 maxmax_target = 0;
474
475 for (a = pars->from; a < pars->upto; a++) {
Marc Kupietz969cab92019-08-05 11:13:42 +0200476 if (a >= window)
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200477 a++;
478 wpos_sum = 0;
Marc Kupietzc48d3902026-08-21 12:13:45 +0200479 DEBUG_PRINTF("window pos: %ld\n", a);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200480 if (a != window) {
481 max_f = -1;
482 window_offset = a * size;
483 if (a > window)
484 window_offset -= size;
Marc Kupietz969cab92019-08-05 11:13:42 +0200485 for (target = 0; target < pars->cutoff; target++) {
486 if (garbage && garbage[target]) continue;
Marc Kupietz565274e2026-09-06 13:17:17 +0200487 /* an operand of the query is not a collocate of itself */
488 for (op = 0; op < wl->length && wl->wordi[op] != target; op++)
489 ;
490 if (op < wl->length)
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200491 continue;
492 f = 0;
493 for (c = 0; c < size; c++)
Marc Kupietz565274e2026-09-06 13:17:17 +0200494 f += qvec[c] * syn1neg_window[target * window_layer_size + window_offset + c];
495 /* Saturate rather than drop. Skipping the tails used to lose exactly
496 the collocates the sigmoid can no longer tell apart, i.e. the
497 strongest ones, and left them out of wpos_sum and target_sums too. */
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200498 if (f < -MAX_EXP)
Marc Kupietz565274e2026-09-06 13:17:17 +0200499 f = -MAX_EXP;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200500 else if (f > MAX_EXP)
Marc Kupietz565274e2026-09-06 13:17:17 +0200501 f = MAX_EXP;
502 f = expTable[(int)((f + MAX_EXP) * (EXP_TABLE_SIZE / MAX_EXP / 2))];
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200503 wpos_sum += f;
504
505 target_sums[target] += f;
Marc Kupietz969cab92019-08-05 11:13:42 +0200506 if (f > worstbest) {
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200507 for (b = 0; b < N; b++) {
508 if (f > best[b].activation) {
Marc Kupietz969cab92019-08-05 11:13:42 +0200509 memmove(best + b + 1, best + b, (N - b - 1) * sizeof(collocator));
510 best[b].activation = f;
511 best[b].wordi = target;
512 best[b].position = window - a;
513 break;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200514 }
515 }
Marc Kupietz969cab92019-08-05 11:13:42 +0200516 if (b == N - 1)
517 worstbest = best[N - 1].activation;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200518 }
519 }
Marc Kupietzc48d3902026-08-21 12:13:45 +0200520 DEBUG_PRINTF("%ld %.2f\n", max_target, max_f);
521 DEBUG_PRINTF("%s (%.2f) ", &vocab[max_target * max_w], max_f);
Marc Kupietz969cab92019-08-05 11:13:42 +0200522 if (max_f > maxmax_f) {
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200523 maxmax_f = max_f;
524 maxmax_target = max_target;
525 }
526 for (b = 0; b < N; b++)
Marc Kupietz969cab92019-08-05 11:13:42 +0200527 if (best[b].position == window - a)
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200528 best[b].cprobability = best[b].activation / wpos_sum;
529 } else {
Marc Kupietzc48d3902026-08-21 12:13:45 +0200530 DEBUG_PRINTF("\x1b[1m%s\x1b[0m ", &vocab[d * max_w]);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200531 }
532 pars->window_sums[a] = wpos_sum;
533 }
534 for (b = 0; b < pars->cutoff; b++)
Marc Kupietz969cab92019-08-05 11:13:42 +0200535 pars->target_sums[b] += target_sums[b]; //(target_sums[b] / wpos_sum ) / (window * 2);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200536
537 free(target_sums);
Marc Kupietz969cab92019-08-05 11:13:42 +0200538 for (b = 0; b < N && best[b].wordi >= 0; b++)
539 ;
Marc Kupietz59865a92021-03-11 17:16:51 +0100540 // THIS LOOP IS NEEDED (b...)
Marc Kupietz969cab92019-08-05 11:13:42 +0200541 // printf("%d: best syn: %s %.2f %.5f\n", b, &vocab[best[b].wordi*max_w], best[b].activation, best[b].probability);
542 // printf("\n");
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200543 nbs = malloc(sizeof(knn));
Marc Kupietz969cab92019-08-05 11:13:42 +0200544 nbs->best = best;
545 nbs->length = b - 1;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200546 pthread_exit(nbs);
547}
548
Marc Kupietz0efe49b2020-04-06 18:30:22 +0200549float getOutputWeight(int hidden, long target, int window_position) {
550 const long window_layer_size = size * window * 2;
551 int a;
552
553 if (window_position == 0 || window_position > window || window_position < -window) {
554 fprintf(stderr, "window_position: %d - assert: -%d <= window_position <= %d && window_position != 0 failed.\n", window_position, window, window);
555 exit(-1);
556 }
557
558 if (hidden >= size) {
Marc Kupietz59865a92021-03-11 17:16:51 +0100559 fprintf(stderr, "hidden: %d - assert: hidden < %lld failed.\n", hidden, size);
Marc Kupietz0efe49b2020-04-06 18:30:22 +0200560 exit(-1);
561 }
562
563 if (target >= words) {
Marc Kupietz59865a92021-03-11 17:16:51 +0100564 fprintf(stderr, "target: %ld - assert: target < %lld failed.\n", target, words);
Marc Kupietz0efe49b2020-04-06 18:30:22 +0200565 exit(-1);
566 }
567
568 a = window_position + window;
569 if (a > window) {
570 --a;
571 }
572 long window_offset = a * size;
573 return syn1neg_window[target * window_layer_size + window_offset + hidden];
574}
575
Marc Kupietz04135302026-07-30 14:40:02 +0900576/* Returns an SV* (not an AV*) on purpose: for an AV* return value Inline::C
577 generates newRV(), which leaves the array itself with a reference count of
578 one after the mortal reference is gone, i.e. it leaks the whole array on
579 every call. */
580SV *getVecs(AV *array) {
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200581 int i, b;
582 AV *result = newAV();
Marc Kupietz969cab92019-08-05 11:13:42 +0200583 for (i = 0; i <= av_len(array); i++) {
584 SV **elem = av_fetch(array, i, 0);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200585 if (elem != NULL) {
Marc Kupietz969cab92019-08-05 11:13:42 +0200586 long j = (long)SvNV(*elem);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200587 AV *vector = newAV();
Marc Kupietz6f317a92026-07-30 15:09:54 +0900588 /* ranks come from the request, reading outside the model would crash */
589 if (j >= 0 && j < words) {
590 for (b = 0; b < size; b++) {
591 av_push(vector, newSVnv(M[b + j * size]));
592 }
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200593 }
Marc Kupietzbdd779a2024-08-05 10:02:29 +0200594 av_push(result, newRV_noinc((SV *)vector));
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200595 }
596 }
Marc Kupietz04135302026-07-30 14:40:02 +0900597 return newRV_noinc((SV *)result);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200598}
599
Marc Kupietz6f317a92026-07-30 15:09:54 +0900600/* Word ids of the collocator db refer to the primary model, which occupies
601 [0, words - merged_end) of its own vocabulary. libcollocatordb crashes on
602 ids outside that range, and the ids come straight from the request. */
603int valid_cdb_node(long node) {
604 return cdb != NULL && node >= 0 && node < words - merged_end;
605}
606
Marc Kupietz04135302026-07-30 14:40:02 +0900607/* All functions handing a string back to perl return an SV*, because for a
608 char* return value Inline::C only copies the string into the return SV and
609 never frees the buffer we allocated here. */
610SV *getSimilarProfiles(long node) {
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200611 int i;
612 char buffer[120000];
613 char pair_buffer[2048];
Marc Kupietz969cab92019-08-05 11:13:42 +0200614 buffer[0] = '[';
615 buffer[1] = 0;
Marc Kupietz6f317a92026-07-30 15:09:54 +0900616 if (node < 0 || node >= sprofiles_qty) {
Marc Kupietzc48d3902026-08-21 12:13:45 +0200617 DEBUG_PRINTF("Not available in precomputed profile\n");
Marc Kupietz04135302026-07-30 14:40:02 +0900618 return newSVpv("[{\"w\":\"not available\", \"v\":0}]\n", 0);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200619 }
620
Marc Kupietzc48d3902026-08-21 12:13:45 +0200621 DEBUG_PRINTF("******* %s ******\n", &vocab[max_w * node]);
Marc Kupietz969cab92019-08-05 11:13:42 +0200622
623 for (i = 0; i < 100 && i < sprofiles[node].len; i++) {
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200624 sprintf(pair_buffer, "{\"w\":\"%s\", \"v\":%f},", &vocab[max_w * (sprofiles[node].nbr[i].index)], sprofiles[node].nbr[i].value);
625 strcat(buffer, pair_buffer);
626 }
Marc Kupietz04135302026-07-30 14:40:02 +0900627 if (i > 0)
628 buffer[strlen(buffer) - 1] = ']';
629 else
630 strcat(buffer, "]");
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200631 strcat(buffer, "\n");
Marc Kupietzc48d3902026-08-21 12:13:45 +0200632 DEBUG_PRINTF("%s", buffer);
Marc Kupietz04135302026-07-30 14:40:02 +0900633 return newSVpv(buffer, 0);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200634}
635
Marc Kupietz04135302026-07-30 14:40:02 +0900636/* get_collocat*_as_json() hand out strdup()ed buffers that we own. */
637SV *getCollocationScores(long node, long collocate) {
Marc Kupietz6f317a92026-07-30 15:09:54 +0900638 char *json = NULL;
639 SV *res;
640 if (valid_cdb_node(node) && valid_cdb_node(collocate))
641 json = (char *)get_collocation_scores_as_json(cdb, node, collocate);
642 res = newSVpv(json ? json : "{\"collocates\":[]}", 0);
Marc Kupietz04135302026-07-30 14:40:02 +0900643 free(json);
644 return res;
Marc Kupietzf6080012021-03-12 09:14:42 +0100645}
646
Marc Kupietz04135302026-07-30 14:40:02 +0900647SV *getClassicCollocators(long node) {
Marc Kupietz6f317a92026-07-30 15:09:54 +0900648 char *json = NULL;
649 SV *res;
650 if (valid_cdb_node(node))
651 json = (char *)get_collocators_as_json(cdb, node);
652 res = newSVpv(json ? json : "{\"collocates\":[]}", 0);
Marc Kupietz04135302026-07-30 14:40:02 +0900653 free(json);
Marc Kupietz969cab92019-08-05 11:13:42 +0200654 return res;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200655}
656
Marc Kupietz565274e2026-09-06 13:17:17 +0200657/* Returns the position of a word in the vocabulary, or -1. In a merged model
658 the two vocabularies sit next to each other and search_backw selects which
659 of them is looked at. */
660static long long lookupWord(const char *word, int search_backw) {
661 long long b, lower = (merge_words ? merge_words : 0);
662 long long upper = (merge_words ? merge_words : words);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200663
Marc Kupietz565274e2026-09-06 13:17:17 +0200664 if (search_backw) {
665 for (b = words - 1; b >= lower && strcmp(&vocab[b * max_w], word) != 0; b--)
666 ;
667 if (b < lower) b = -1;
668 } else {
669 for (b = 0; b < upper && strcmp(&vocab[b * max_w], word) != 0; b++)
670 ;
671 if (b >= upper) b = -1;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200672 }
Marc Kupietz565274e2026-09-06 13:17:17 +0200673 return b;
674}
675
676/* Splits a query into the operands of a vector expression. Blanks separate
677 words as before, and a leading '+' or '-' decides with which sign a word
678 enters the query vector, so that "König - Mann + Frau" is answered with the
679 neighbours of vec(König) - vec(Mann) + vec(Frau) rather than with those of
680 any single word. A sign is only an operator at the beginning of a token,
681 which leaves hyphenated words such as "Nord-Süd-Dialog" searchable; a
682 free standing sign applies to the word that follows it.
683
684 Tokens that are not in the vocabulary are left out of the list and
685 collected in wl->oov, so that the caller can report them instead of
686 quietly answering a different question. */
687wordlist *getTargetWords(char *st1, int search_backw) {
688 wordlist *wl = calloc(1, sizeof(wordlist));
689 char *copy = strdup(st1), *tok, *saveptr = NULL;
690 size_t oov_len = 0;
691 int sign = '+';
692
693 if (wl == NULL || copy == NULL) {
694 free(wl);
695 free(copy);
696 return NULL;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200697 }
Marc Kupietz565274e2026-09-06 13:17:17 +0200698
699 for (tok = strtok_r(copy, " \t\r\n", &saveptr); tok != NULL; tok = strtok_r(NULL, " \t\r\n", &saveptr)) {
700 long long b;
701 if (*tok == '+' || *tok == '-') {
702 sign = *tok++;
703 if (*tok == 0) continue; /* " - Mann": the sign belongs to the next token */
704 }
705 if (wl->length >= MAX_TARGET_WORDS) break;
706 /* Counted before the lookup: what the query is asking for does not change
707 because one of its operands happens to be unknown. */
708 if (sign == '-') wl->subtractions++;
709 b = lookupWord(tok, search_backw);
710 if (b < 0) {
711 DEBUG_EPRINTF("Out of dictionary word: \"%s\"\n", tok);
712 if (oov_len + strlen(tok) + 2 <= sizeof(wl->oov)) {
713 if (oov_len > 0) wl->oov[oov_len++] = ' ';
714 strcpy(wl->oov + oov_len, tok);
715 oov_len += strlen(tok);
716 }
717 } else {
718 DEBUG_EPRINTF("Word: \"%s\" Sign: %c Position in vocabulary: %lld\n", &vocab[b * max_w], sign, b);
719 wl->sep[wl->length] = (char)sign;
720 wl->wordi[wl->length++] = b;
721 }
722 sign = '+';
723 }
724 free(copy);
Marc Kupietz969cab92019-08-05 11:13:42 +0200725 return (wl);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200726}
727
Marc Kupietzcb43e492019-12-03 10:07:53 +0100728long getWordNumber(char *word) {
729 wordlist *wl = getTargetWords(word, 0);
Marc Kupietz04135302026-07-30 14:40:02 +0900730 long res = 0;
731 if (wl == NULL)
732 return(0);
Marc Kupietzcb43e492019-12-03 10:07:53 +0100733 if(wl->length > 0)
Marc Kupietz04135302026-07-30 14:40:02 +0900734 res = wl->wordi[0];
735 free(wl);
736 return(res);
Marc Kupietzcb43e492019-12-03 10:07:53 +0100737}
738
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200739float get_distance(long b, long c) {
740 long a;
741 float dist = 0;
742 for (a = 0; a < size; a++) dist += M[a + c * size] * M[a + b * size];
743 return dist;
744}
745
Marc Kupietz04135302026-07-30 14:40:02 +0900746/* The result is computed once and then kept in a static buffer for the
747 lifetime of the process. */
748SV *getBiggestMergedDifferences() {
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200749 static char *result = NULL;
Marc Kupietz59865a92021-03-11 17:16:51 +0100750 float dist;
751 long long a, c;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200752 int N = 1000;
753
Marc Kupietz969cab92019-08-05 11:13:42 +0200754 if (merged_end == 0)
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200755 result = "[]";
Marc Kupietz969cab92019-08-05 11:13:42 +0200756
757 if (result != NULL)
Marc Kupietz04135302026-07-30 14:40:02 +0900758 return newSVpv(result, 0);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200759
Marc Kupietzc48d3902026-08-21 12:13:45 +0200760 DEBUG_PRINTF("Looking for biggest distances between main and merged vectors ...\n");
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200761 collocator *best;
762 best = malloc(N * sizeof(collocator));
763 memset(best, 0, N * sizeof(collocator));
764
Marc Kupietz969cab92019-08-05 11:13:42 +0200765 float worstbest = 1000000;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200766
767 for (a = 0; a < N; a++) best[a].activation = worstbest;
768
769 for (c = 0; c < 500000; c++) {
Marc Kupietz969cab92019-08-05 11:13:42 +0200770 if (garbage && garbage[c]) continue;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200771 dist = 0;
Marc Kupietz969cab92019-08-05 11:13:42 +0200772 for (a = 0; a < size; a++) dist += M[a + c * size] * M[a + (c + merged_end) * size];
773 if (dist < worstbest) {
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200774 for (a = 0; a < N; a++) {
775 if (dist < best[a].activation) {
Marc Kupietz969cab92019-08-05 11:13:42 +0200776 memmove(best + a + 1, best + a, (N - a - 1) * sizeof(collocator));
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200777 best[a].activation = dist;
778 best[a].wordi = c;
779 break;
780 }
781 }
Marc Kupietz969cab92019-08-05 11:13:42 +0200782 worstbest = best[N - 1].activation;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200783 }
784 }
785
Marc Kupietz04135302026-07-30 14:40:02 +0900786 result = malloc(N * (max_w + 64));
Marc Kupietzbdd779a2024-08-05 10:02:29 +0200787 char *p = (char *) result;
Marc Kupietz969cab92019-08-05 11:13:42 +0200788 *p++ = '[';
789 *p = 0;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200790 for (a = 0; a < N; a++) {
Marc Kupietz59865a92021-03-11 17:16:51 +0100791 p += sprintf(p, "{\"rank\":%lld,\"word\":\"%s\",\"dist\":%.3f},", a, &vocab[best[a].wordi * max_w], 1 - best[a].activation);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200792 }
793 *--p = ']';
Marc Kupietz04135302026-07-30 14:40:02 +0900794 free(best);
795 return newSVpv(result, 0);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200796}
797
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200798float cos_similarity(long b, long c) {
Marc Kupietz969cab92019-08-05 11:13:42 +0200799 float dist = 0;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200800 long a;
Marc Kupietz969cab92019-08-05 11:13:42 +0200801 for (a = 0; a < size; a++) dist += M[b * size + a] * M[c * size + a];
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200802 return dist;
803}
804
Marc Kupietz04135302026-07-30 14:40:02 +0900805SV *cos_similarity_as_json(char *w1, char *w2) {
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200806 wordlist *a, *b;
807 float res;
Marc Kupietz04135302026-07-30 14:40:02 +0900808 char json[32];
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200809 a = getTargetWords(w1, 0);
810 b = getTargetWords(w2, 0);
Marc Kupietz969cab92019-08-05 11:13:42 +0200811 if (a == NULL || b == NULL || a->length != 1 || b->length != 1)
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200812 res = -1;
Marc Kupietz04135302026-07-30 14:40:02 +0900813 else {
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200814 res = cos_similarity(a->wordi[0], b->wordi[0]);
Marc Kupietzc48d3902026-08-21 12:13:45 +0200815 DEBUG_EPRINTF("a: %lld b: %lld res:%f\n", a->wordi[0], b->wordi[0], res);
Marc Kupietz04135302026-07-30 14:40:02 +0900816 }
817 free(a);
818 free(b);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200819 sprintf(json, "%.5f", res);
Marc Kupietz04135302026-07-30 14:40:02 +0900820 return newSVpv(json, 0);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200821}
822
823void *_get_neighbours(void *arg) {
824 knnpars *pars = arg;
Marc Kupietz969cab92019-08-05 11:13:42 +0200825 int N = pars->N;
826 long from = pars->from;
827 unsigned long upto = pars->upto;
Marc Kupietz59865a92021-03-11 17:16:51 +0100828 char *sep;
Marc Kupietz969cab92019-08-05 11:13:42 +0200829 float dist, len, vec[max_size];
Marc Kupietz59865a92021-03-11 17:16:51 +0100830 long long a, b, c, cn, *bi;
Marc Kupietz969cab92019-08-05 11:13:42 +0200831 knn *nbs = NULL;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200832 wordlist *wl = pars->wl;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200833
Marc Kupietz969cab92019-08-05 11:13:42 +0200834 collocator *best = pars->best;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200835
Marc Kupietz969cab92019-08-05 11:13:42 +0200836 float worstbest = -1;
837
Marc Kupietz565274e2026-09-06 13:17:17 +0200838 for (a = 0; a < N; a++) {
839 best[a].activation = -1;
840 best[a].wordi = -1;
841 }
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200842 bi = wl->wordi;
Marc Kupietz969cab92019-08-05 11:13:42 +0200843 cn = wl->length;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200844 sep = wl->sep;
Marc Kupietz565274e2026-09-06 13:17:17 +0200845 if (cn < 1) {
Marc Kupietz969cab92019-08-05 11:13:42 +0200846 goto end;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200847 }
Marc Kupietz969cab92019-08-05 11:13:42 +0200848 for (a = 0; a < size; a++) vec[a] = 0;
849 for (b = 0; b < cn; b++) {
Marc Kupietz565274e2026-09-06 13:17:17 +0200850 if (sep[b] == '-')
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200851 for (a = 0; a < size; a++) vec[a] -= M[a + bi[b] * size];
852 else
853 for (a = 0; a < size; a++) vec[a] += M[a + bi[b] * size];
Marc Kupietz969cab92019-08-05 11:13:42 +0200854 }
855 len = 0;
856 for (a = 0; a < size; a++) len += vec[a] * vec[a];
857 len = sqrt(len);
Marc Kupietz565274e2026-09-06 13:17:17 +0200858 /* An expression whose operands cancel each other out, "Haus - Haus", has no
859 position to search around. */
860 if (len == 0) {
861 goto end;
862 }
Marc Kupietz969cab92019-08-05 11:13:42 +0200863 for (a = 0; a < size; a++) vec[a] /= len;
Marc Kupietz969cab92019-08-05 11:13:42 +0200864 for (c = from; c < upto; c++) {
865 if (garbage && garbage[c]) continue;
866 a = 0;
867 // do not skip taget word
868 // for (b = 0; b < cn; b++) if (bi[b] == c) a = 1;
869 // if (a == 1) continue;
870 dist = 0;
871 for (a = 0; a < size; a++) dist += vec[a] * M[a + c * size];
872 if (dist > worstbest) {
873 for (a = 0; a < N; a++) {
874 if (dist > best[a].activation) {
875 memmove(best + a + 1, best + a, (N - a - 1) * sizeof(collocator));
876 best[a].activation = dist;
877 best[a].wordi = c;
878 break;
879 }
880 }
881 worstbest = best[N - 1].activation;
882 }
883 }
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200884
885end:
Marc Kupietz969cab92019-08-05 11:13:42 +0200886 pthread_exit(nbs);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200887}
888
Marc Kupietz969cab92019-08-05 11:13:42 +0200889int cmp_activation(const void *a, const void *b) {
890 float fb = ((collocator *)a)->activation;
891 float fa = ((collocator *)b)->activation;
892 return (fa > fb) - (fa < fb);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200893}
894
Marc Kupietz969cab92019-08-05 11:13:42 +0200895int cmp_probability(const void *a, const void *b) {
896 float fb = ((collocator *)a)->probability;
897 float fa = ((collocator *)b)->probability;
898 return (fa > fb) - (fa < fb);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200899}
900
Marc Kupietz04135302026-07-30 14:40:02 +0900901SV *getPosWiseW2VCollocators(char *word, long maxPerPos, long cutoff, float threshold, const char *format) {
Marc Kupietz59865a92021-03-11 17:16:51 +0100902 float *target_sums = NULL;
Marc Kupietzd6a163c2026-07-30 14:48:10 +0900903 float *window_sums = NULL;
Marc Kupietz04135302026-07-30 14:40:02 +0900904 long a, b, entries = 0;
Marc Kupietz969cab92019-08-05 11:13:42 +0200905 knn *syn_nbs[MAX_THREADS];
906 knnpars pars[MAX_THREADS];
Marc Kupietz04135302026-07-30 14:40:02 +0900907 pthread_t *pt = NULL;
908 wordlist *wl = NULL;
Marc Kupietz969cab92019-08-05 11:13:42 +0200909 int syn_threads = (M2 ? window * 2 : 0);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200910 int search_backw = 0;
Marc Kupietz04135302026-07-30 14:40:02 +0900911 char *result = NULL;
912 SV *res_sv;
913
914 for (a = 0; a < MAX_THREADS; a++) syn_nbs[a] = NULL;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200915
Marc Kupietz969cab92019-08-05 11:13:42 +0200916 if (cutoff < 1 || cutoff > words)
917 cutoff = words;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200918
919 wl = getTargetWords(word, search_backw);
Marc Kupietz565274e2026-09-06 13:17:17 +0200920 if (wl == NULL || wl->length < 1 || syn_threads < 1) {
Marc Kupietz04135302026-07-30 14:40:02 +0900921 free(wl);
922 return newSVpv("", 0);
923 }
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200924
Marc Kupietz04135302026-07-30 14:40:02 +0900925 pt = (pthread_t *)malloc((num_threads + 1) * sizeof(pthread_t));
Marc Kupietzd6a163c2026-07-30 14:48:10 +0900926 window_sums = new_window_sums();
Marc Kupietz969cab92019-08-05 11:13:42 +0200927 a = posix_memalign((void **)&target_sums, 128, cutoff * sizeof(float));
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200928 memset(target_sums, 0, cutoff * sizeof(float));
929
Marc Kupietzc48d3902026-08-21 12:13:45 +0200930 DEBUG_PRINTF("Starting %d threads\n", syn_threads);
931 DEBUG_FFLUSH();
Marc Kupietz969cab92019-08-05 11:13:42 +0200932 for (a = 0; a < syn_threads; a++) {
933 pars[a].cutoff = cutoff;
934 pars[a].target_sums = target_sums;
935 pars[a].window_sums = window_sums;
936 pars[a].wl = wl;
937 pars[a].N = maxPerPos;
Marc Kupietz04135302026-07-30 14:40:02 +0900938 pars[a].best = NULL; /* getCollocators() allocates its own result array */
Marc Kupietz969cab92019-08-05 11:13:42 +0200939 pars[a].threshold = threshold;
940 pars[a].from = a;
941 pars[a].upto = a + 1;
942 pthread_create(&pt[a], NULL, getCollocators, (void *)&pars[a]);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200943 }
Marc Kupietzc48d3902026-08-21 12:13:45 +0200944 DEBUG_PRINTF("Waiting for syn threads to join\n");
945 DEBUG_FFLUSH();
Marc Kupietz969cab92019-08-05 11:13:42 +0200946 for (a = 0; a < syn_threads; a++) pthread_join(pt[a], (void *)&syn_nbs[a]);
Marc Kupietzc48d3902026-08-21 12:13:45 +0200947 DEBUG_PRINTF("Syn threads joint\n");
948 DEBUG_FFLUSH();
Marc Kupietz04135302026-07-30 14:40:02 +0900949 result = malloc((maxPerPos > 0 ? maxPerPos : 1) * (max_w + 96) * syn_threads + 16);
Marc Kupietzbdd779a2024-08-05 10:02:29 +0200950 char *p = (char *) result;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200951 *p = 0;
Marc Kupietz0ab97392024-12-10 16:16:32 +0100952 if (strcmp(format, "tsv") == 0) {
953 for (a = syn_threads - 1; a >= 0; a--) {
Marc Kupietz04135302026-07-30 14:40:02 +0900954 if (syn_nbs[a] == NULL) continue;
955 for (b = 0; b < syn_nbs[a]->length; b++, entries++) {
Marc Kupietz0ab97392024-12-10 16:16:32 +0100956 p += sprintf(p, "%ld\t%s\t%f\n", syn_nbs[a]->best[b].position, &vocab[syn_nbs[a]->best[b].wordi * max_w], syn_nbs[a]->best[b].activation);
957 }
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200958 }
Marc Kupietz0ab97392024-12-10 16:16:32 +0100959 } else {
960 p += sprintf(p, "[");
961 for (a = syn_threads - 1; a >= 0; a--) {
Marc Kupietz04135302026-07-30 14:40:02 +0900962 if (syn_nbs[a] == NULL) continue;
963 for (b = 0; b < syn_nbs[a]->length; b++, entries++) {
Marc Kupietz0ab97392024-12-10 16:16:32 +0100964 p += sprintf(p, "{\"pos\": %ld, \"word\":\"%s\",\"activation\": %f},\n", syn_nbs[a]->best[b].position, &vocab[syn_nbs[a]->best[b].wordi * max_w], syn_nbs[a]->best[b].activation);
965 }
966 }
Marc Kupietz04135302026-07-30 14:40:02 +0900967 if (entries > 0)
968 p -= 2; /* drop the trailing ",\n" */
Marc Kupietz0ab97392024-12-10 16:16:32 +0100969 p += sprintf(p, "\n]");
Marc Kupietz969cab92019-08-05 11:13:42 +0200970 }
Marc Kupietz0ab97392024-12-10 16:16:32 +0100971
Marc Kupietz04135302026-07-30 14:40:02 +0900972 res_sv = newSVpv(result, 0);
973
974 free(result);
975 free(target_sums);
Marc Kupietzd6a163c2026-07-30 14:48:10 +0900976 free(window_sums);
Marc Kupietz04135302026-07-30 14:40:02 +0900977 free(pt);
978 free(wl);
979 for (a = 0; a < syn_threads; a++) free_knn(syn_nbs[a]);
980
981 return res_sv;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200982}
983
Marc Kupietz04135302026-07-30 14:40:02 +0900984SV *getPosWiseW2VCollocatorsAsTsv(char *word, long maxPerPos, long cutoff, float threshold) {
Marc Kupietz0ab97392024-12-10 16:16:32 +0100985 return getPosWiseW2VCollocators(word, maxPerPos, cutoff, threshold, "tsv");
986}
987
Marc Kupietz04135302026-07-30 14:40:02 +0900988SV *getPosWiseW2VCollocatorsAsJson(char *word, long maxPerPos, long cutoff, float threshold) {
Marc Kupietz0ab97392024-12-10 16:16:32 +0100989 return getPosWiseW2VCollocators(word, maxPerPos, cutoff, threshold, "json");
990}
991
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200992SV *get_neighbours(char *st1, int N, int sort_by, int search_backw, long cutoff, int dedupe, int no_similar_profiles) {
993 HV *result = newHV();
Marc Kupietz59865a92021-03-11 17:16:51 +0100994 float *target_sums = NULL;
Marc Kupietzd6a163c2026-07-30 14:48:10 +0900995 float *window_sums = NULL;
Marc Kupietz969cab92019-08-05 11:13:42 +0200996 long a, b, c, d, slice;
997 knn *para_nbs[MAX_THREADS];
998 knn *syn_nbs[MAX_THREADS];
999 knnpars pars[MAX_THREADS];
1000 pthread_t *pt = (pthread_t *)malloc((num_threads + 1) * sizeof(pthread_t));
Marc Kupietz04135302026-07-30 14:40:02 +09001001 wordlist *wl = NULL;
Marc Kupietz565274e2026-09-06 13:17:17 +02001002 int syn_threads = 0;
1003 int para_threads = 0;
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001004
Marc Kupietz04135302026-07-30 14:40:02 +09001005 for (a = 0; a < MAX_THREADS; a++) para_nbs[a] = syn_nbs[a] = NULL;
1006
Marc Kupietz969cab92019-08-05 11:13:42 +02001007 if (N > MAX_NEIGHBOURS) N = MAX_NEIGHBOURS;
Marc Kupietz565274e2026-09-06 13:17:17 +02001008 if (N < 1) N = 1;
1009
1010 /* Every paradigmatic thread fills its own slice of N entries, and the
1011 syntagmatic part below works on the first MAX_NEIGHBOURS of the same
1012 array. How many paradigmatic threads there are is only known further
1013 down, so the array is sized for the maximum. */
1014 collocator *best = NULL;
1015 long best_entries = (long)N * num_threads;
1016 if (best_entries < MAX_NEIGHBOURS) best_entries = MAX_NEIGHBOURS;
1017 posix_memalign((void **)&best, 128, best_entries * sizeof(collocator));
1018 memset(best, 0, best_entries * sizeof(collocator));
Marc Kupietz969cab92019-08-05 11:13:42 +02001019
1020 if (cutoff < 1 || cutoff > words)
1021 cutoff = words;
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001022
1023 wl = getTargetWords(st1, search_backw);
Marc Kupietz565274e2026-09-06 13:17:17 +02001024 if (wl == NULL)
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001025 goto end;
1026
Marc Kupietz565274e2026-09-06 13:17:17 +02001027 /* Tell the caller which words the query vector was built from and which
1028 tokens of the query are unknown, so that "König - Mannn + Frau" is not
1029 silently answered as "König + Frau". */
1030 {
1031 SV *added = newSVpv("", 0);
1032 SV *unknown = newSVpv(wl->oov, 0);
1033 for (a = 0; a < wl->length; a++) {
1034 if (wl->sep[a] == '-') continue;
1035 if (SvCUR(added) > 0) sv_catpvn(added, " ", 1);
1036 sv_catpv(added, &vocab[wl->wordi[a] * max_w]);
1037 }
1038 if (latin_enc == 0) {
1039 SvUTF8_on(added);
1040 SvUTF8_on(unknown);
1041 }
1042 hv_store(result, "added", strlen("added"), added, 0);
1043 hv_store(result, "unknown", strlen("unknown"), unknown, 0);
1044 hv_store(result, "operands", strlen("operands"), newSViv(wl->length), 0);
1045 }
1046
1047 if (wl->length < 1)
1048 goto end;
1049
1050 syn_threads = (M2 ? window * 2 : 0);
1051 para_threads = (no_similar_profiles ? 0 : num_threads - syn_threads);
1052
Marc Kupietz04135302026-07-30 14:40:02 +09001053 slice = (para_threads > 0 ? cutoff / para_threads : cutoff);
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001054
Marc Kupietz969cab92019-08-05 11:13:42 +02001055 a = posix_memalign((void **)&target_sums, 128, cutoff * sizeof(float));
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001056 memset(target_sums, 0, cutoff * sizeof(float));
1057
Marc Kupietzc48d3902026-08-21 12:13:45 +02001058 DEBUG_PRINTF("Starting %d threads for paradigmatic search\n", para_threads);
1059 DEBUG_FFLUSH();
Marc Kupietz969cab92019-08-05 11:13:42 +02001060 for (a = 0; a < para_threads; a++) {
1061 pars[a].cutoff = cutoff;
1062 pars[a].token = st1;
1063 pars[a].wl = wl;
1064 pars[a].N = N;
1065 pars[a].best = &best[N * a];
1066 if (merge_words == 0 || search_backw == 0) {
1067 pars[a].from = a * slice;
1068 pars[a].upto = ((a + 1) * slice > cutoff ? cutoff : (a + 1) * slice);
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001069 } else {
1070 pars[a].from = merge_words + a * slice;
Marc Kupietz969cab92019-08-05 11:13:42 +02001071 pars[a].upto = merge_words + ((a + 1) * slice > cutoff ? cutoff : (a + 1) * slice);
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001072 }
Marc Kupietzc48d3902026-08-21 12:13:45 +02001073 DEBUG_PRINTF("From: %ld, Upto: %ld\n", pars[a].from, pars[a].upto);
Marc Kupietz969cab92019-08-05 11:13:42 +02001074 pthread_create(&pt[a], NULL, _get_neighbours, (void *)&pars[a]);
1075 }
Marc Kupietz565274e2026-09-06 13:17:17 +02001076 if (syn_threads) {
Marc Kupietzd6a163c2026-07-30 14:48:10 +09001077 window_sums = new_window_sums();
Marc Kupietz969cab92019-08-05 11:13:42 +02001078 for (a = 0; a < syn_threads; a++) {
1079 pars[a + para_threads].cutoff = cutoff;
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001080 pars[a + para_threads].target_sums = target_sums;
1081 pars[a + para_threads].window_sums = window_sums;
1082 pars[a + para_threads].wl = wl;
1083 pars[a + para_threads].N = N;
1084 pars[a + para_threads].threshold = MIN_RESP;
1085 pars[a + para_threads].from = a;
Marc Kupietz969cab92019-08-05 11:13:42 +02001086 pars[a + para_threads].upto = a + 1;
1087 pthread_create(&pt[a + para_threads], NULL, getCollocators, (void *)&pars[a + para_threads]);
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001088 }
1089 }
Marc Kupietzc48d3902026-08-21 12:13:45 +02001090 DEBUG_PRINTF("Waiting for para threads to join\n");
1091 DEBUG_FFLUSH();
Marc Kupietz969cab92019-08-05 11:13:42 +02001092 for (a = 0; a < para_threads; a++) pthread_join(pt[a], (void *)&para_nbs[a]);
Marc Kupietzc48d3902026-08-21 12:13:45 +02001093 DEBUG_PRINTF("Para threads joint\n");
1094 DEBUG_FFLUSH();
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001095
Marc Kupietz969cab92019-08-05 11:13:42 +02001096 /* if(!syn_nbs[0]) */
1097 /* goto end; */
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001098
Marc Kupietz969cab92019-08-05 11:13:42 +02001099 qsort(best, N * para_threads, sizeof(collocator), cmp_activation);
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001100
1101 long long chosen[MAX_NEIGHBOURS];
Marc Kupietzc48d3902026-08-21 12:13:45 +02001102 DEBUG_PRINTF("N: %d\n", N);
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001103
Marc Kupietz969cab92019-08-05 11:13:42 +02001104 AV *array = newAV();
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001105 int i, j;
Marc Kupietz969cab92019-08-05 11:13:42 +02001106 int l1_words = 0, l2_words = 0;
1107
1108 for (a = 0, i = 0; i < N && a < N * para_threads; a++) {
1109 int filtered = 0;
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001110 long long c = best[a].wordi;
Marc Kupietz565274e2026-09-06 13:17:17 +02001111 if (c < 0) /* the threads found fewer candidates than were asked for */
1112 break;
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001113 if ((merge_words && dedupe && i > 1) || (!merge_words && dedupe && i > 0)) {
Marc Kupietz969cab92019-08-05 11:13:42 +02001114 for (j = 0; j < i && !filtered; j++)
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001115 if (strcasestr(&vocab[c * max_w], &vocab[chosen[j] * max_w]) ||
1116 strcasestr(&vocab[chosen[j] * max_w], &vocab[c * max_w])) {
Marc Kupietzc48d3902026-08-21 12:13:45 +02001117 DEBUG_PRINTF("filtering %s %s\n", &vocab[chosen[j] * max_w], &vocab[c * max_w]);
Marc Kupietz969cab92019-08-05 11:13:42 +02001118 filtered = 1;
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001119 }
Marc Kupietz969cab92019-08-05 11:13:42 +02001120 if (filtered)
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001121 continue;
1122 }
1123
Marc Kupietz969cab92019-08-05 11:13:42 +02001124 if (0 && merge_words > 0) {
1125 if (c >= merge_words) {
1126 if (l1_words > N / 2)
1127 continue;
1128 else
1129 l1_words++;
1130 } else {
1131 if (l2_words > N / 2)
1132 continue;
1133 else
1134 l2_words++;
1135 }
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001136 }
1137
Marc Kupietz969cab92019-08-05 11:13:42 +02001138 // printf("%s l1:%d l2:%d i:%d a:%ld\n", &vocab[c * max_w], l1_words, l2_words, i, a);
1139 // fflush(stdout);
1140 HV *hash = newHV();
1141 SV *word = newSVpvf(&vocab[c * max_w], 0);
1142 chosen[i] = c;
1143 if (latin_enc == 0) SvUTF8_on(word);
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001144 fflush(stdout);
Marc Kupietz969cab92019-08-05 11:13:42 +02001145 hv_store(hash, "word", strlen("word"), word, 0);
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001146 hv_store(hash, "dist", strlen("dist"), newSVnv(best[a].activation), 0);
1147 hv_store(hash, "rank", strlen("rank"), newSVuv(best[a].wordi), 0);
1148 AV *vector = newAV();
1149 for (b = 0; b < size; b++) {
1150 av_push(vector, newSVnv(M[b + best[a].wordi * size]));
1151 }
Marc Kupietz969cab92019-08-05 11:13:42 +02001152 hv_store(hash, "vector", strlen("vector"), newRV_noinc((SV *)vector), 0);
1153 av_push(array, newRV_noinc((SV *)hash));
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001154 i++;
1155 }
Marc Kupietz969cab92019-08-05 11:13:42 +02001156 hv_store(result, "paradigmatic", strlen("paradigmatic"), newRV_noinc((SV *)array), 0);
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001157
Marc Kupietz969cab92019-08-05 11:13:42 +02001158 for (b = 0; b < MAX_NEIGHBOURS; b++) {
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001159 best[b].wordi = -1L;
1160 best[b].activation = 0;
1161 best[b].probability = 0;
1162 best[b].position = 0;
1163 best[b].activation_sum = 0;
Marc Kupietz969cab92019-08-05 11:13:42 +02001164 memset(best[b].heat, 0, sizeof(float) * 16);
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001165 }
1166
Marc Kupietz969cab92019-08-05 11:13:42 +02001167 float total_activation = 0;
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001168
Marc Kupietz565274e2026-09-06 13:17:17 +02001169 if (syn_threads) {
Marc Kupietzc48d3902026-08-21 12:13:45 +02001170 DEBUG_PRINTF("Waiting for syn threads to join\n");
1171 DEBUG_FFLUSH();
Marc Kupietz969cab92019-08-05 11:13:42 +02001172 for (a = 0; a < syn_threads; a++) pthread_join(pt[a + para_threads], (void *)&syn_nbs[a]);
1173 for (a = 0; a <= syn_threads; a++) {
1174 if (a == window) continue;
1175 total_activation += window_sums[a];
Marc Kupietzc48d3902026-08-21 12:13:45 +02001176 DEBUG_PRINTF("window pos: %ld, sum: %f\n", a, window_sums[a]);
Marc Kupietz969cab92019-08-05 11:13:42 +02001177 }
Marc Kupietzc48d3902026-08-21 12:13:45 +02001178 DEBUG_PRINTF("syn threads joint\n");
1179 DEBUG_FFLUSH();
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001180
Marc Kupietz969cab92019-08-05 11:13:42 +02001181 for (b = 0; b < syn_nbs[0]->length; b++) {
1182 memcpy(best + b, &syn_nbs[0]->best[b], sizeof(collocator));
1183 best[b].position = -1; // syn_nbs[0]->pos[b];
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001184 best[b].activation_sum = target_sums[syn_nbs[0]->best[b].wordi];
Marc Kupietz969cab92019-08-05 11:13:42 +02001185 best[b].max_activation = 0.0;
1186 best[b].average = 0.0;
1187 best[b].probability = 0.0;
1188 best[b].cprobability = syn_nbs[0]->best[b].cprobability;
1189 memset(best[b].heat, 0, sizeof(float) * 16);
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001190 }
Marc Kupietz969cab92019-08-05 11:13:42 +02001191
1192 float best_window_sum[MAX_NEIGHBOURS];
Marc Kupietz59865a92021-03-11 17:16:51 +01001193 int found_index = 0, i = 0, w;
Marc Kupietz969cab92019-08-05 11:13:42 +02001194 for (a = 0; a < syn_threads; a++) {
1195 for (b = 0; b < syn_nbs[a]->length; b++) {
1196 for (i = 0; i < found_index; i++)
1197 if (best[i].wordi == syn_nbs[a]->best[b].wordi)
1198 break;
1199 if (i >= found_index) {
1200 best[found_index].max_activation = 0.0;
1201 best[found_index].average = 0.0;
1202 best[found_index].probability = 0.0;
1203 memset(best[found_index].heat, 0, sizeof(float) * 16);
1204 best[found_index].cprobability = syn_nbs[a]->best[b].cprobability;
1205 best[found_index].activation_sum = target_sums[syn_nbs[a]->best[b].wordi]; // syn_nbs[a]->best[b].activation_sum;
1206 best[found_index++].wordi = syn_nbs[a]->best[b].wordi;
1207 // printf("found: %s\n", &vocab[syn_nbs[a]->index[b] * max_w]);
1208 }
1209 }
1210 }
1211 sort_by = 0; // ALWAYS AUTO-FOCUS
1212 if (sort_by != 1 && sort_by != 2) { // sort by auto focus mean
Marc Kupietzc48d3902026-08-21 12:13:45 +02001213 DEBUG_PRINTF("window: %d - syn_threads: %d, %d\n", window, syn_threads, (1 << syn_threads) - 1);
Marc Kupietz969cab92019-08-05 11:13:42 +02001214 int wpos;
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001215 int bits_set = 0;
Marc Kupietz969cab92019-08-05 11:13:42 +02001216 for (i = 0; i < found_index; i++) {
1217 best[i].activation = best[i].probability = best[i].average = best[i].cprobability_sum = 0;
1218 for (w = 1; w < (1 << syn_threads); w++) { // loop through all possible windows
1219 float word_window_sum = 0, word_window_average = 0, word_cprobability_sum = 0, word_activation_sum = 0, total_window_sum = 0;
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001220 bits_set = 0;
Marc Kupietz969cab92019-08-05 11:13:42 +02001221 for (a = 0; a < syn_threads; a++) {
1222 if ((1 << a) & w) {
1223 wpos = (a >= window ? a + 1 : a);
1224 total_window_sum += window_sums[wpos];
1225 }
1226 }
1227 // printf("%d window-sum %f\n", w, total_window_sum);
1228 for (a = 0; a < syn_threads; a++) {
1229 if ((1 << a) & w) {
1230 wpos = (a >= window ? a + 1 : a);
1231 bits_set++;
1232 for (b = 0; b < syn_nbs[a]->length; b++)
1233 if (best[i].wordi == syn_nbs[a]->best[b].wordi) {
1234 // float acti = syn_nbs[a]->best[b].activation / total_window_sum;
1235 // word_window_sum += syn_nbs[a]->dist[b] * syn_nbs[a]->norm[b]; // / window_sums[wpos]; // syn_nbs[a]->norm[b];
1236 // word_window_sum += syn_nbs[a]->norm[b]; // / window_sums[wpos]; // syn_nbs[a]->norm[b];
1237 // word_window_sum = (word_window_sum + syn_nbs[a]->norm[b]) - (word_window_sum * syn_nbs[a]->norm[b]); // syn_nbs[a]->norm[b];
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001238
Marc Kupietz969cab92019-08-05 11:13:42 +02001239 word_window_sum += syn_nbs[a]->best[b].activation; // / window_sums[wpos]; // syn_nbs[a]->norm[b];
1240 // word_window_sum += acti - (word_window_sum * acti); syn_nbs[a]->best[b].activation; // / window_sums[wpos]; // syn_nbs[a]->norm[b];
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001241
Marc Kupietz969cab92019-08-05 11:13:42 +02001242 word_window_average += syn_nbs[a]->best[b].activation; // - word_window_average * syn_nbs[a]->best[b].activation; // conormalied activation sum
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001243 word_cprobability_sum += syn_nbs[a]->best[b].cprobability - word_cprobability_sum * syn_nbs[a]->best[b].cprobability; // conormalied column probability sum
Marc Kupietz969cab92019-08-05 11:13:42 +02001244 word_activation_sum += syn_nbs[a]->best[b].activation;
1245 if (syn_nbs[a]->best[b].activation > best[i].max_activation)
1246 best[i].max_activation = syn_nbs[a]->best[b].activation;
1247 if (syn_nbs[a]->best[b].activation > best[i].heat[wpos])
1248 best[i].heat[wpos] = syn_nbs[a]->best[b].activation;
1249 }
1250 }
1251 }
1252 if (bits_set) {
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001253 word_window_average /= bits_set;
Marc Kupietz969cab92019-08-05 11:13:42 +02001254 // word_activation_sum /= bits_set;
1255 // word_window_sum /= bits_set;
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001256 }
1257
Marc Kupietz969cab92019-08-05 11:13:42 +02001258 word_window_sum /= total_window_sum;
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001259
Marc Kupietz969cab92019-08-05 11:13:42 +02001260 if (word_window_sum > best[i].probability) {
1261 // best[i].position = w;
1262 best[i].probability = word_window_sum;
1263 }
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001264
Marc Kupietz969cab92019-08-05 11:13:42 +02001265 if (word_cprobability_sum > best[i].cprobability_sum) {
1266 best[i].position = w;
1267 best[i].cprobability_sum = word_cprobability_sum;
1268 }
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001269
Marc Kupietz969cab92019-08-05 11:13:42 +02001270 best[i].average = word_window_average;
1271 // best[i].activation = word_activation_sum;
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001272 }
Marc Kupietz969cab92019-08-05 11:13:42 +02001273 }
1274 qsort(best, found_index, sizeof(collocator), cmp_probability);
1275 // for(i=0; i < found_index; i++) {
1276 // printf("found: %s - sum: %f - window: %d\n", &vocab[best[i].wordi * max_w], best[i].activation, best[i].position);
1277 // }
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001278
Marc Kupietz969cab92019-08-05 11:13:42 +02001279 } else if (sort_by == 1) { // responsiveness any window position
1280 int wpos;
1281 for (i = 0; i < found_index; i++) {
1282 float word_window_sum = 0, word_activation_sum = 0, total_window_sum = 0;
1283 for (a = 0; a < syn_threads; a++) {
1284 wpos = (a >= window ? a + 1 : a);
1285 for (b = 0; b < syn_nbs[a]->length; b++)
1286 if (best[i].wordi == syn_nbs[a]->best[b].wordi) {
1287 best[i].probability += syn_nbs[a]->best[b].probability;
1288 if (syn_nbs[a]->best[b].activation > 0.25)
1289 best[i].position |= 1 << wpos;
1290 if (syn_nbs[a]->best[b].activation > best[i].activation) {
1291 best[i].activation = syn_nbs[a]->best[b].activation;
1292 }
1293 }
1294 }
1295 }
1296 qsort(best, found_index, sizeof(collocator), cmp_activation);
1297 } else if (sort_by == 2) { // single window position
1298 for (a = 1; a < syn_threads; a++) {
1299 for (b = 0; b < syn_nbs[a]->length; b++) {
1300 for (c = 0; c < MAX_NEIGHBOURS; c++) {
1301 if (syn_nbs[a]->best[b].activation > best[c].activation) {
1302 for (d = MAX_NEIGHBOURS - 1; d > c; d--) {
1303 memmove(best + d, best + d - 1, sizeof(collocator));
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001304 }
1305 memcpy(best + c, &syn_nbs[a]->best[b], sizeof(collocator));
Marc Kupietz969cab92019-08-05 11:13:42 +02001306 best[c].position = 1 << (-syn_nbs[a]->best[b].position + window - (syn_nbs[a]->best[b].position < 0 ? 1 : 0));
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001307 break;
1308 }
1309 }
1310 }
1311 }
Marc Kupietz969cab92019-08-05 11:13:42 +02001312 } else { // sort by mean p
1313 for (a = 1; a < syn_threads; a++) {
1314 for (b = 0; b < syn_nbs[a]->length; b++) {
1315 for (c = 0; c < MAX_NEIGHBOURS; c++) {
1316 if (target_sums[syn_nbs[a]->best[b].wordi] > best[c].activation_sum) {
1317 for (d = MAX_NEIGHBOURS - 1; d > c; d--) {
1318 memmove(best + d, best + d - 1, sizeof(collocator));
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001319 }
1320 memcpy(best + c, &syn_nbs[a]->best[b], sizeof(collocator));
Marc Kupietz969cab92019-08-05 11:13:42 +02001321 best[c].position = (1 << 2 * window) - 1; // syn_nbs[a]->pos[b];
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001322 best[c].activation_sum = target_sums[syn_nbs[a]->best[b].wordi];
1323 break;
1324 }
1325 }
1326 }
1327 }
1328 }
1329 array = newAV();
Marc Kupietz969cab92019-08-05 11:13:42 +02001330 for (a = 0, i = 0; a < MAX_NEIGHBOURS && best[a].wordi >= 0; a++) {
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001331 long long c = best[a].wordi;
Marc Kupietz969cab92019-08-05 11:13:42 +02001332 /*
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001333 if (dedupe) {
1334 int filtered=0;
1335 for (j=0; j<i; j++)
1336 if (strcasestr(&vocab[c * max_w], chosen[j]) ||
1337 strcasestr(chosen[j], &vocab[c * max_w])) {
1338 printf("filtering %s %s\n", chosen[j], &vocab[c * max_w]);
1339 filtered = 1;
1340 }
1341 if(filtered)
1342 continue;
1343 }
1344*/
Marc Kupietz969cab92019-08-05 11:13:42 +02001345 chosen[i++] = c;
1346 HV *hash = newHV();
1347 SV *word = newSVpvf(&vocab[best[a].wordi * max_w], 0);
1348 AV *heat = newAV();
1349 if (latin_enc == 0) SvUTF8_on(word);
1350 hv_store(hash, "word", strlen("word"), word, 0);
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001351 hv_store(hash, "rank", strlen("rank"), newSVuv(best[a].wordi), 0);
1352 hv_store(hash, "average", strlen("average"), newSVnv(best[a].average), 0);
1353 hv_store(hash, "prob", strlen("prob"), newSVnv(best[a].probability), 0);
1354 hv_store(hash, "cprob", strlen("cprob"), newSVnv(best[a].cprobability_sum), 0);
Marc Kupietz969cab92019-08-05 11:13:42 +02001355 hv_store(hash, "max", strlen("max"), newSVnv(best[a].max_activation), 0); // newSVnv(target_sums[best[a].wordi]), 0);
1356 hv_store(hash, "overall", strlen("overall"), newSVnv(best[a].activation_sum / total_activation), 0); // newSVnv(target_sums[best[a].wordi]), 0);
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001357 hv_store(hash, "pos", strlen("pos"), newSVnv(best[a].position), 0);
Marc Kupietz969cab92019-08-05 11:13:42 +02001358 best[a].heat[5] = 0;
1359 for (i = 10; i >= 0; i--) av_push(heat, newSVnv(best[a].heat[i]));
1360 hv_store(hash, "heat", strlen("heat"), newRV_noinc((SV *)heat), 0);
1361 av_push(array, newRV_noinc((SV *)hash));
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001362 }
Marc Kupietz969cab92019-08-05 11:13:42 +02001363 hv_store(result, "syntagmatic", strlen("syntagmatic"), newRV_noinc((SV *)array), 0);
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001364 }
1365end:
Marc Kupietz969cab92019-08-05 11:13:42 +02001366 free(best);
Marc Kupietz04135302026-07-30 14:40:02 +09001367 free(target_sums);
Marc Kupietzd6a163c2026-07-30 14:48:10 +09001368 free(window_sums);
Marc Kupietz04135302026-07-30 14:40:02 +09001369 free(pt);
1370 free(wl);
1371 for (a = 0; a < MAX_THREADS; a++) {
1372 free_knn(para_nbs[a]);
1373 free_knn(syn_nbs[a]);
1374 }
Marc Kupietz969cab92019-08-05 11:13:42 +02001375 return newRV_noinc((SV *)result);
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001376}
1377
1378int dump_vecs(char *fname) {
Marc Kupietz969cab92019-08-05 11:13:42 +02001379 long i, j;
1380 FILE *f;
1381 /* if(words>100000)
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001382 words=100000;
1383*/
Marc Kupietz969cab92019-08-05 11:13:42 +02001384 if ((f = fopen(fname, "w")) == NULL) {
1385 fprintf(stderr, "cannot open %s for writing\n", fname);
1386 return (-1);
1387 }
1388 fprintf(f, "%lld %lld\n", words, size);
1389 for (i = 0; i < words; i++) {
1390 fprintf(f, "%s ", &vocab[i * max_w]);
1391 for (j = 0; j < size - 1; j++)
1392 fprintf(f, "%f ", M[i * size + j]);
1393 fprintf(f, "%f\n", M[i * size + j]);
1394 }
1395 fclose(f);
1396 return (0);
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001397}
1398
1399int dump_for_numpy(char *fname) {
Marc Kupietz969cab92019-08-05 11:13:42 +02001400 long i, j;
1401 FILE *f;
Marc Kupietzc0d41872021-02-25 16:33:22 +01001402 int max = words; // 300000;
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001403
Marc Kupietz969cab92019-08-05 11:13:42 +02001404 if ((f = fopen(fname, "w")) == NULL) {
1405 fprintf(stderr, "cannot open %s for writing\n", fname);
1406 return (-1);
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001407 }
Marc Kupietz969cab92019-08-05 11:13:42 +02001408 for (i = 0; i < max; i++) {
1409 for (j = 0; j < size - 1; j++)
1410 fprintf(f, "%f\t", M[i * size + j]);
1411 fprintf(f, "%f\n", M[i * size + j]);
1412 printf("%s\r\n", &vocab[i * max_w]);
1413 }
1414 if (merged_end > 0) {
1415 for (i = 0; i < max; i++) {
1416 for (j = 0; j < size - 1; j++)
1417 fprintf(f, "%f\t", M[(merged_end + i) * size + j]);
1418 fprintf(f, "%f\n", M[(merged_end + i) * size + j]);
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001419 printf("_%s\r\n", &vocab[i * max_w]);
1420 }
Marc Kupietz969cab92019-08-05 11:13:42 +02001421 }
1422 fclose(f);
1423 return (0);
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001424}
Marc Kupietz043db152023-11-05 17:47:53 +01001425
1426unsigned long getVocabSize() {
1427 return (unsigned long) words;
1428}
Marc Kupietz6f317a92026-07-30 15:09:54 +09001429
1430/* First rank of the primary model in the merged vocabulary, 0 if no second
1431 model was merged in. mergeVectors() puts the merged in model at ranks
1432 [0, merged_end) and the primary model - the one the collocator db belongs
1433 to - at [merged_end, words). */
1434long getMergedEnd() {
1435 return (long) merged_end;
1436}