blob: e72cf4c61d770594e1011e11bcdc006559bcf234 [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;
Marc Kupietz21fcd5f2026-09-06 13:17:29 +020056 /* The score before the sigmoid, q . syn1neg[collocate, position]. activation
57 is expTable[z], quantized in steps of 1/83 and capped at MAX_EXP; z is
58 not. raw is the value at one position, max_raw the maximum over
59 positions. */
60 float raw;
61 float max_raw;
Marc Kupietz969cab92019-08-05 11:13:42 +020062 float heat[16];
Marc Kupietzf11d20c2019-08-02 15:42:04 +020063} collocator;
64
65typedef struct {
Marc Kupietz969cab92019-08-05 11:13:42 +020066 collocator *best;
67 int length;
Marc Kupietzf11d20c2019-08-02 15:42:04 +020068} knn;
Marc Kupietz969cab92019-08-05 11:13:42 +020069
Marc Kupietzf11d20c2019-08-02 15:42:04 +020070typedef struct {
Marc Kupietz565274e2026-09-06 13:17:17 +020071 long long wordi[MAX_TARGET_WORDS];
72 /* '+' or '-': the sign with which wordi[i] enters the query vector */
73 char sep[MAX_TARGET_WORDS];
74 /* blank separated tokens of the query that are not in the vocabulary */
75 char oov[max_size];
76 int length; /* number of tokens found in the vocabulary */
77 int subtractions; /* how many of them enter with a '-' */
Marc Kupietzf11d20c2019-08-02 15:42:04 +020078} wordlist;
79
80typedef struct {
81 long cutoff;
82 wordlist *wl;
Marc Kupietz969cab92019-08-05 11:13:42 +020083 char *token;
84 int N;
85 long from;
86 unsigned long upto;
Marc Kupietzf11d20c2019-08-02 15:42:04 +020087 collocator *best;
88 float *target_sums;
89 float *window_sums;
90 float threshold;
91} knnpars;
92
93typedef struct {
94 uint32_t index;
95 float value;
96} sparse_t;
97
98typedef struct {
99 uint32_t len;
100 sparse_t nbr[100];
101} profile_t;
102
Marc Kupietz969cab92019-08-05 11:13:42 +0200103float *M, *M2 = 0L, *syn1neg_window, *expTable;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200104char *vocab;
105char *garbage = NULL;
106COLLOCATORDB *cdb = NULL;
107profile_t *sprofiles = NULL;
108size_t sprofiles_qty = 0;
109
110long long words, size, merged_end;
111long long merge_words = 0;
Marc Kupietz969cab92019-08-05 11:13:42 +0200112int num_threads = 20;
113int latin_enc = 0;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200114int window;
115
116/* load collocation profiles if file exists */
117int load_sprofiles(char *vecsname) {
118 char *basename = strdup(vecsname);
119 char *pos = strstr(basename, ".vecs");
Marc Kupietz969cab92019-08-05 11:13:42 +0200120 if (pos)
121 *pos = 0;
122
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200123 char binsprofiles_fname[256];
124 strcpy(binsprofiles_fname, basename);
Marc Kupietz969cab92019-08-05 11:13:42 +0200125 strcat(binsprofiles_fname, ".sprofiles.bin");
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200126 FILE *fp = fopen(binsprofiles_fname, "rb");
127 if (fp == NULL) {
128 printf("Collocation profiles %s not found. No problem.\n", binsprofiles_fname);
129 return 0;
130 }
131 fseek(fp, 0L, SEEK_END);
132 size_t sz = ftell(fp);
133 fclose(fp);
134
135 int fd = open(binsprofiles_fname, O_RDONLY);
Marc Kupietz969cab92019-08-05 11:13:42 +0200136 sprofiles = mmap(0, sz, PROT_READ, MAP_SHARED, fd, 0);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200137 if (sprofiles == MAP_FAILED) {
138 close(fd);
139 fprintf(stderr, "Cannot mmap %s\n", binsprofiles_fname);
140 sprofiles = NULL;
141 return 0;
Marc Kupietz969cab92019-08-05 11:13:42 +0200142 } else {
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200143 sprofiles_qty = sz / sizeof(profile_t);
144 fprintf(stderr, "Successfully mmaped %s containing similar profiles for %ld word forms.\n", binsprofiles_fname, sprofiles_qty);
145 }
146 return 1;
147}
148
Marc Kupietzc0d41872021-02-25 16:33:22 +0100149char *removeExtension(char* myStr) {
150 char *retStr;
151 char *lastExt;
152 if (myStr == NULL) return NULL;
153 if ((retStr = malloc (strlen (myStr) + 1)) == NULL) return NULL;
154 strcpy (retStr, myStr);
155 lastExt = strrchr (retStr, '.');
156 if (lastExt != NULL)
157 *lastExt = '\0';
158 return retStr;
159}
160
Marc Kupietz0efe49b2020-04-06 18:30:22 +0200161int init_net(char *file_name, char *net_name, int latin, int do_open_cdb) {
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200162 FILE *f, *binvecs, *binwords;
Marc Kupietz969cab92019-08-05 11:13:42 +0200163 int binwords_fd, binvecs_fd, net_fd, i;
Marc Kupietz59865a92021-03-11 17:16:51 +0100164 long long a, b;
Marc Kupietz969cab92019-08-05 11:13:42 +0200165 float len;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200166 double val;
167
Marc Kupietzc0d41872021-02-25 16:33:22 +0100168 char binvecs_fname[1024], binwords_fname[1024];
169
170 if (strstr(file_name, ".txt")) {
171 strcpy(binwords_fname, removeExtension(file_name));
172 } else {
173 strcpy(binwords_fname, file_name);
174 }
Marc Kupietz969cab92019-08-05 11:13:42 +0200175 strcat(binwords_fname, ".words");
176 strcpy(binvecs_fname, file_name);
177 strcat(binvecs_fname, ".vecs");
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200178
179 latin_enc = latin;
180 f = fopen(file_name, "rb");
181 if (f == NULL) {
182 printf("Input file %s not found\n", file_name);
183 return -1;
184 }
185 fscanf(f, "%lld", &words);
Marc Kupietz969cab92019-08-05 11:13:42 +0200186 if (MAX_WORDS > 0 && words > MAX_WORDS) words = MAX_WORDS;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200187 fscanf(f, "%lld", &size);
Marc Kupietz969cab92019-08-05 11:13:42 +0200188 if ((binvecs_fd = open(binvecs_fname, O_RDONLY)) < 0 || (binwords_fd = open(binwords_fname, O_RDONLY)) < 0) {
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200189 printf("Converting %s to memory mappable structures\n", file_name);
Marc Kupietz969cab92019-08-05 11:13:42 +0200190 vocab = (char *)malloc((long long)words * max_w * sizeof(char));
191 M = (float *)malloc((long long)words * (long long)size * sizeof(float));
192 if (M == NULL) {
193 printf("Cannot allocate memory: %lld MB %lld %lld\n", (long long)words * size * sizeof(float) / 1048576, words, size);
194 return -1;
195 }
196 if (strstr(file_name, ".txt")) {
Marc Kupietzc0d41872021-02-25 16:33:22 +0100197 printf("%lld words in ascii vector file with vector size %lld\n", words, size);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200198 for (b = 0; b < words; b++) {
199 a = 0;
200 while (1) {
201 vocab[b * max_w + a] = fgetc(f);
202 if (feof(f) || (vocab[b * max_w + a] == ' ')) break;
203 if ((a < max_w) && (vocab[b * max_w + a] != '\n')) a++;
204 }
205 vocab[b * max_w + a] = 0;
206 len = 0;
207 for (a = 0; a < size; a++) {
208 fscanf(f, "%lf", &val);
209 M[a + b * size] = val;
210 len += val * val;
Marc Kupietz969cab92019-08-05 11:13:42 +0200211 }
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200212 len = sqrt(len);
213 for (a = 0; a < size; a++) M[a + b * size] /= len;
214 }
215 } else {
216 for (b = 0; b < words; b++) {
217 a = 0;
218 while (1) {
219 vocab[b * max_w + a] = fgetc(f);
220 if (feof(f) || (vocab[b * max_w + a] == ' ')) break;
221 if ((a < max_w) && (vocab[b * max_w + a] != '\n')) a++;
222 }
223 vocab[b * max_w + a] = 0;
224 fread(&M[b * size], sizeof(float), size, f);
225 len = 0;
226 for (a = 0; a < size; a++) len += M[a + b * size] * M[a + b * size];
227 len = sqrt(len);
228 for (a = 0; a < size; a++) M[a + b * size] /= len;
229 }
230 }
Marc Kupietz969cab92019-08-05 11:13:42 +0200231 if ((binvecs = fopen(binvecs_fname, "wb")) != NULL && (binwords = fopen(binwords_fname, "wb")) != NULL) {
232 fwrite(M, sizeof(float), (long long)words * (long long)size, binvecs);
233 fclose(binvecs);
234 fwrite(vocab, sizeof(char), (long long)words * max_w, binwords);
235 fclose(binwords);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200236 }
Marc Kupietz969cab92019-08-05 11:13:42 +0200237 }
238 if ((binvecs_fd = open(binvecs_fname, O_RDONLY)) >= 0 && (binwords_fd = open(binwords_fname, O_RDONLY)) >= 0) {
239 M = mmap(0, sizeof(float) * (long long)words * (long long)size, PROT_READ, MAP_SHARED, binvecs_fd, 0);
240 vocab = mmap(0, sizeof(char) * (long long)words * max_w, PROT_READ, MAP_SHARED, binwords_fd, 0);
241 if (M == MAP_FAILED || vocab == MAP_FAILED) {
242 close(binvecs_fd);
243 close(binwords_fd);
244 fprintf(stderr, "Cannot mmap %s or %s\n", binwords_fname, binvecs_fname);
245 exit(-1);
246 }
247 } else {
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200248 fprintf(stderr, "Cannot open %s or %s\n", binwords_fname, binvecs_fname);
249 exit(-1);
Marc Kupietz969cab92019-08-05 11:13:42 +0200250 }
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200251 fclose(f);
252
Marc Kupietz969cab92019-08-05 11:13:42 +0200253 if (net_name && strlen(net_name) > 0) {
254 if ((net_fd = open(net_name, O_RDONLY)) >= 0) {
255 window = (lseek(net_fd, 0, SEEK_END) - sizeof(float) * words * size) / words / size / sizeof(float) / 2;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200256 // lseek(net_fd, sizeof(float) * words * size, SEEK_SET);
257 // munmap(M, sizeof(float) * words * size);
258 M2 = mmap(0, sizeof(float) * words * size + sizeof(float) * 2 * window * size * words, PROT_READ, MAP_SHARED, net_fd, 0);
259 if (M2 == MAP_FAILED) {
260 close(net_fd);
261 fprintf(stderr, "Cannot mmap %s\n", net_name);
262 exit(-1);
263 }
Marc Kupietz969cab92019-08-05 11:13:42 +0200264 syn1neg_window = M2 + words * size;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200265 } else {
266 fprintf(stderr, "Cannot open %s\n", net_name);
267 exit(-1);
268 }
269 fprintf(stderr, "Successfully memmaped %s. Determined window size: %d\n", net_name, window);
270
Marc Kupietz0efe49b2020-04-06 18:30:22 +0200271 if (do_open_cdb) {
272 char collocatordb_name[2048];
273 strcpy(collocatordb_name, net_name);
274 char *ext = rindex(collocatordb_name, '.');
275 if (ext) {
276 strcpy(ext, ".rocksdb");
277 if (access(collocatordb_name, R_OK) == 0) {
278 *ext = 0;
279 fprintf(stderr, "Opening collocator DB %s\n", collocatordb_name);
280 cdb = open_collocatordb(collocatordb_name);
Marc Kupietzc0d41872021-02-25 16:33:22 +0100281 } else {
282 fprintf(stderr, "Cannot open collocator DB %s\n", collocatordb_name);
Marc Kupietz0efe49b2020-04-06 18:30:22 +0200283 }
Marc Kupietz969cab92019-08-05 11:13:42 +0200284 }
285 }
286 }
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200287
Marc Kupietz969cab92019-08-05 11:13:42 +0200288 expTable = (float *)malloc((EXP_TABLE_SIZE + 1) * sizeof(float));
289 for (i = 0; i < EXP_TABLE_SIZE; i++) {
290 expTable[i] = exp((i / (float)EXP_TABLE_SIZE * 2 - 1) * MAX_EXP); // Precompute the exp() table
291 expTable[i] = expTable[i] / (expTable[i] + 1); // Precompute f(x) = x / (x + 1)
292 }
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200293
294 return 0;
295}
296
Marc Kupietzd6a163c2026-07-30 14:48:10 +0900297/* The collocator threads of one request accumulate their activation sums per
298 window position here. This must not be shared between requests, so every
299 request allocates its own array. Only valid once init_net() has determined
300 the window size, i.e. if M2 is set. */
301float *new_window_sums() {
302 return calloc((window + 1) * 2, sizeof(float));
303}
304
Marc Kupietz969cab92019-08-05 11:13:42 +0200305long mergeVectors(char *file_name) {
Marc Kupietz59865a92021-03-11 17:16:51 +0100306 FILE *f;
307 int binwords_fd, binvecs_fd;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200308 float *merge_vecs;
309 char *merge_vocab;
Marc Kupietz969cab92019-08-05 11:13:42 +0200310 /* long long merge_words, merge_size; */
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200311 long long merge_size;
312
Marc Kupietz969cab92019-08-05 11:13:42 +0200313 char binvecs_fname[256], binwords_fname[256];
Marc Kupietzc0d41872021-02-25 16:33:22 +0100314
315
Marc Kupietz969cab92019-08-05 11:13:42 +0200316 strcpy(binwords_fname, file_name);
317 strcat(binwords_fname, ".words");
318 strcpy(binvecs_fname, file_name);
319 strcat(binvecs_fname, ".vecs");
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200320
321 f = fopen(file_name, "rb");
322 if (f == NULL) {
323 printf("Input file %s not found\n", file_name);
Marc Kupietz59865a92021-03-11 17:16:51 +0100324 exit(-1);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200325 }
326 fscanf(f, "%lld", &merge_words);
327 fscanf(f, "%lld", &merge_size);
Marc Kupietz969cab92019-08-05 11:13:42 +0200328 if (merge_size != size) {
329 fprintf(stderr, "vectors must have the same length\n");
330 exit(-1);
331 }
332 if ((binvecs_fd = open(binvecs_fname, O_RDONLY)) >= 0 && (binwords_fd = open(binwords_fname, O_RDONLY)) >= 0) {
333 merge_vecs = malloc(sizeof(float) * (words + merge_words) * size);
334 merge_vocab = malloc(sizeof(char) * (words + merge_words) * max_w);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200335 if (merge_vecs == NULL || merge_vocab == NULL) {
Marc Kupietz969cab92019-08-05 11:13:42 +0200336 close(binvecs_fd);
337 close(binwords_fd);
338 fprintf(stderr, "Cannot reserve memory for %s or %s\n", binwords_fname, binvecs_fname);
339 exit(-1);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200340 }
341 read(binvecs_fd, merge_vecs, merge_words * size * sizeof(float));
342 read(binwords_fd, merge_vocab, merge_words * max_w);
Marc Kupietz969cab92019-08-05 11:13:42 +0200343 } else {
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200344 fprintf(stderr, "Cannot open %s or %s\n", binwords_fname, binvecs_fname);
345 exit(-1);
Marc Kupietz969cab92019-08-05 11:13:42 +0200346 }
347 printf("Successfully reallocated memory\nMerging...\n");
348 fflush(stdout);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200349 memcpy(merge_vecs + merge_words * size, M, words * size * sizeof(float));
350 memcpy(merge_vocab + merge_words * max_w, vocab, words * max_w);
351 munmap(M, words * size * sizeof(float));
352 munmap(vocab, words * max_w);
353 M = merge_vecs;
354 vocab = merge_vocab;
355 merged_end = merge_words;
356 words += merge_words;
357 fclose(f);
Marc Kupietz969cab92019-08-05 11:13:42 +0200358 printf("merged_end: %lld, words: %lld\n", merged_end, words);
359 //printBiggestMergedDifferences();
360 return ((long)merged_end);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200361}
362
363void filter_garbage() {
364 long i;
365 unsigned char *w, previous, c;
366 garbage = malloc(words);
367 memset(garbage, 0, words);
368 for (i = 0; i < words; i++) {
Marc Kupietz59865a92021-03-11 17:16:51 +0100369 w = (unsigned char *) vocab + i * max_w;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200370 previous = 0;
Marc Kupietz59865a92021-03-11 17:16:51 +0100371 if (strncmp("quot", (const char *)w, 4) == 0) {
Marc Kupietz969cab92019-08-05 11:13:42 +0200372 garbage[i] = 1;
373 // printf("Gargabe: %s\n", vocab + i * max_w);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200374 } else {
Marc Kupietz969cab92019-08-05 11:13:42 +0200375 while ((c = *w++) && !garbage[i]) {
376 if (((c <= 90 && c >= 65) && (previous >= 97 && previous <= 122)) ||
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200377 (previous == '-' && (c & 32)) ||
Marc Kupietz969cab92019-08-05 11:13:42 +0200378 (previous == 0xc2 && (c == 0xa4 || c == 0xb6)) ||
379 (previous == 'q' && c == 'u' && *(w) == 'o' && *(w + 1) == 't') || /* quot */
380 c == '<') {
381 garbage[i] = 1;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200382 continue;
383 }
384 previous = c;
385 }
386 }
387 }
388 return;
389}
390
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200391knn *simpleGetCollocators(int word, int number, long cutoff, int *result) {
392 knnpars *pars = calloc(sizeof(knnpars), 1);
Marc Kupietz59865a92021-03-11 17:16:51 +0100393 float *target_sums = NULL;
394 float *my_window_sums = malloc(sizeof(float) * (window + 1) * 2);
Marc Kupietz969cab92019-08-05 11:13:42 +0200395 pars->cutoff = (cutoff ? cutoff : 300000);
Marc Kupietz59865a92021-03-11 17:16:51 +0100396 long a;
Marc Kupietz969cab92019-08-05 11:13:42 +0200397 for (a = 0; a < cutoff; a++)
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200398 target_sums[a] = 0;
399 pars->target_sums = target_sums;
Marc Kupietz59865a92021-03-11 17:16:51 +0100400 pars->window_sums = my_window_sums;
Marc Kupietz969cab92019-08-05 11:13:42 +0200401 pars->N = (number ? number : 20);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200402 pars->from = 0;
Marc Kupietz969cab92019-08-05 11:13:42 +0200403 pars->upto = window * 2 - 1;
404 knn *syn_nbs = NULL; // = (knn*) getCollocators(pars);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200405 free(pars);
Marc Kupietz59865a92021-03-11 17:16:51 +0100406 free(my_window_sums);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200407 free(target_sums);
408 return syn_nbs;
409}
410
Marc Kupietz04135302026-07-30 14:40:02 +0900411/* Frees a knn result as returned by getCollocators() via pthread_exit(). */
412void free_knn(knn *nbs) {
413 if (nbs == NULL)
414 return;
415 free(nbs->best);
416 free(nbs);
417}
418
Marc Kupietz21fcd5f2026-09-06 13:17:29 +0200419/* Predicts the collocates of a query. The score is
420 sigmoid(q . syn1neg[target, position]), linear in q before the sigmoid, so q
421 may be the signed combination of input vectors that the paradigmatic side
422 searches around rather than the vector of a single word.
Marc Kupietz565274e2026-09-06 13:17:17 +0200423
Marc Kupietz21fcd5f2026-09-06 13:17:29 +0200424 q is the mean over the terms, not their sum: the sigmoid is informative over
425 a narrow range only, and a sum would push the strongest collocates past
426 MAX_EXP into saturation. The mean keeps every query in the range MIN_RESP
427 and the auto focus are calibrated for. One word, and a balanced analogy
428 (+1 -1 +1), are one term and keep the previous scale. */
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200429void *getCollocators(void *args) {
430 knnpars *pars = args;
Marc Kupietz969cab92019-08-05 11:13:42 +0200431 int N = pars->N;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200432
Marc Kupietz565274e2026-09-06 13:17:17 +0200433 wordlist *wl = pars->wl;
Marc Kupietz969cab92019-08-05 11:13:42 +0200434 knn *nbs = NULL;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200435 long window_layer_size = size * window * 2;
Marc Kupietz565274e2026-09-06 13:17:17 +0200436 long a, b, c, d, op, window_offset, target, max_target = 0, maxmax_target;
Marc Kupietz21fcd5f2026-09-06 13:17:29 +0200437 float f, raw, max_f, maxmax_f, scale;
Marc Kupietz565274e2026-09-06 13:17:17 +0200438 float qvec[max_size];
439 int terms = 0;
Marc Kupietz969cab92019-08-05 11:13:42 +0200440 float *target_sums = NULL, worstbest, wpos_sum;
441 collocator *best;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200442
Marc Kupietz565274e2026-09-06 13:17:17 +0200443 if (M2 == NULL || wl == NULL || wl->length < 1)
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200444 return NULL;
445
Marc Kupietz565274e2026-09-06 13:17:17 +0200446 for (a = 0; a < wl->length; a++) terms += (wl->sep[a] == '-' ? -1 : 1);
447 scale = (terms > 1 ? 1.0f / terms : 1.0f);
448 for (c = 0; c < size; c++) qvec[c] = 0;
449 for (a = 0; a < wl->length; a++) {
450 long long off = wl->wordi[a] * size;
451 if (wl->sep[a] == '-')
452 for (c = 0; c < size; c++) qvec[c] -= M2[off + c];
453 else
454 for (c = 0; c < size; c++) qvec[c] += M2[off + c];
455 }
456 for (c = 0; c < size; c++) qvec[c] *= scale;
457
Marc Kupietz969cab92019-08-05 11:13:42 +0200458 a = posix_memalign((void **)&target_sums, 128, pars->cutoff * sizeof(float));
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200459 memset(target_sums, 0, pars->cutoff * sizeof(float));
Marc Kupietz969cab92019-08-05 11:13:42 +0200460 best = malloc((N > 200 ? N : 200) * sizeof(collocator));
461 memset(best, 0, (N > 200 ? N : 200) * sizeof(collocator));
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200462 worstbest = pars->threshold;
463
464 for (b = 0; b < pars->cutoff; b++)
Marc Kupietz969cab92019-08-05 11:13:42 +0200465 target_sums[b] = 0;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200466 for (b = 0; b < N; b++) {
Marc Kupietz969cab92019-08-05 11:13:42 +0200467 best[b].wordi = -1;
468 best[b].probability = 1;
469 best[b].activation = worstbest;
Marc Kupietz21fcd5f2026-09-06 13:17:29 +0200470 best[b].raw = 0;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200471 }
472
Marc Kupietz565274e2026-09-06 13:17:17 +0200473 d = wl->wordi[0];
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200474 maxmax_f = -1;
475 maxmax_target = 0;
476
477 for (a = pars->from; a < pars->upto; a++) {
Marc Kupietz969cab92019-08-05 11:13:42 +0200478 if (a >= window)
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200479 a++;
480 wpos_sum = 0;
Marc Kupietzc48d3902026-08-21 12:13:45 +0200481 DEBUG_PRINTF("window pos: %ld\n", a);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200482 if (a != window) {
483 max_f = -1;
484 window_offset = a * size;
485 if (a > window)
486 window_offset -= size;
Marc Kupietz969cab92019-08-05 11:13:42 +0200487 for (target = 0; target < pars->cutoff; target++) {
488 if (garbage && garbage[target]) continue;
Marc Kupietz565274e2026-09-06 13:17:17 +0200489 /* an operand of the query is not a collocate of itself */
490 for (op = 0; op < wl->length && wl->wordi[op] != target; op++)
491 ;
492 if (op < wl->length)
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200493 continue;
494 f = 0;
495 for (c = 0; c < size; c++)
Marc Kupietz565274e2026-09-06 13:17:17 +0200496 f += qvec[c] * syn1neg_window[target * window_layer_size + window_offset + c];
Marc Kupietz21fcd5f2026-09-06 13:17:29 +0200497 raw = f;
498 /* Saturate rather than drop: skipping the tails removed the strongest
499 collocates from the list and from wpos_sum and target_sums. */
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200500 if (f < -MAX_EXP)
Marc Kupietz565274e2026-09-06 13:17:17 +0200501 f = -MAX_EXP;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200502 else if (f > MAX_EXP)
Marc Kupietz565274e2026-09-06 13:17:17 +0200503 f = MAX_EXP;
504 f = expTable[(int)((f + MAX_EXP) * (EXP_TABLE_SIZE / MAX_EXP / 2))];
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200505 wpos_sum += f;
506
507 target_sums[target] += f;
Marc Kupietz969cab92019-08-05 11:13:42 +0200508 if (f > worstbest) {
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200509 for (b = 0; b < N; b++) {
510 if (f > best[b].activation) {
Marc Kupietz969cab92019-08-05 11:13:42 +0200511 memmove(best + b + 1, best + b, (N - b - 1) * sizeof(collocator));
512 best[b].activation = f;
Marc Kupietz21fcd5f2026-09-06 13:17:29 +0200513 best[b].raw = raw;
Marc Kupietz969cab92019-08-05 11:13:42 +0200514 best[b].wordi = target;
515 best[b].position = window - a;
516 break;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200517 }
518 }
Marc Kupietz969cab92019-08-05 11:13:42 +0200519 if (b == N - 1)
520 worstbest = best[N - 1].activation;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200521 }
522 }
Marc Kupietzc48d3902026-08-21 12:13:45 +0200523 DEBUG_PRINTF("%ld %.2f\n", max_target, max_f);
524 DEBUG_PRINTF("%s (%.2f) ", &vocab[max_target * max_w], max_f);
Marc Kupietz969cab92019-08-05 11:13:42 +0200525 if (max_f > maxmax_f) {
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200526 maxmax_f = max_f;
527 maxmax_target = max_target;
528 }
529 for (b = 0; b < N; b++)
Marc Kupietz969cab92019-08-05 11:13:42 +0200530 if (best[b].position == window - a)
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200531 best[b].cprobability = best[b].activation / wpos_sum;
532 } else {
Marc Kupietzc48d3902026-08-21 12:13:45 +0200533 DEBUG_PRINTF("\x1b[1m%s\x1b[0m ", &vocab[d * max_w]);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200534 }
535 pars->window_sums[a] = wpos_sum;
536 }
537 for (b = 0; b < pars->cutoff; b++)
Marc Kupietz969cab92019-08-05 11:13:42 +0200538 pars->target_sums[b] += target_sums[b]; //(target_sums[b] / wpos_sum ) / (window * 2);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200539
540 free(target_sums);
Marc Kupietz969cab92019-08-05 11:13:42 +0200541 for (b = 0; b < N && best[b].wordi >= 0; b++)
542 ;
Marc Kupietz59865a92021-03-11 17:16:51 +0100543 // THIS LOOP IS NEEDED (b...)
Marc Kupietz969cab92019-08-05 11:13:42 +0200544 // printf("%d: best syn: %s %.2f %.5f\n", b, &vocab[best[b].wordi*max_w], best[b].activation, best[b].probability);
545 // printf("\n");
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200546 nbs = malloc(sizeof(knn));
Marc Kupietz969cab92019-08-05 11:13:42 +0200547 nbs->best = best;
548 nbs->length = b - 1;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200549 pthread_exit(nbs);
550}
551
Marc Kupietz0efe49b2020-04-06 18:30:22 +0200552float getOutputWeight(int hidden, long target, int window_position) {
553 const long window_layer_size = size * window * 2;
554 int a;
555
556 if (window_position == 0 || window_position > window || window_position < -window) {
557 fprintf(stderr, "window_position: %d - assert: -%d <= window_position <= %d && window_position != 0 failed.\n", window_position, window, window);
558 exit(-1);
559 }
560
561 if (hidden >= size) {
Marc Kupietz59865a92021-03-11 17:16:51 +0100562 fprintf(stderr, "hidden: %d - assert: hidden < %lld failed.\n", hidden, size);
Marc Kupietz0efe49b2020-04-06 18:30:22 +0200563 exit(-1);
564 }
565
566 if (target >= words) {
Marc Kupietz59865a92021-03-11 17:16:51 +0100567 fprintf(stderr, "target: %ld - assert: target < %lld failed.\n", target, words);
Marc Kupietz0efe49b2020-04-06 18:30:22 +0200568 exit(-1);
569 }
570
571 a = window_position + window;
572 if (a > window) {
573 --a;
574 }
575 long window_offset = a * size;
576 return syn1neg_window[target * window_layer_size + window_offset + hidden];
577}
578
Marc Kupietz04135302026-07-30 14:40:02 +0900579/* Returns an SV* (not an AV*) on purpose: for an AV* return value Inline::C
580 generates newRV(), which leaves the array itself with a reference count of
581 one after the mortal reference is gone, i.e. it leaks the whole array on
582 every call. */
583SV *getVecs(AV *array) {
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200584 int i, b;
585 AV *result = newAV();
Marc Kupietz969cab92019-08-05 11:13:42 +0200586 for (i = 0; i <= av_len(array); i++) {
587 SV **elem = av_fetch(array, i, 0);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200588 if (elem != NULL) {
Marc Kupietz969cab92019-08-05 11:13:42 +0200589 long j = (long)SvNV(*elem);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200590 AV *vector = newAV();
Marc Kupietz6f317a92026-07-30 15:09:54 +0900591 /* ranks come from the request, reading outside the model would crash */
592 if (j >= 0 && j < words) {
593 for (b = 0; b < size; b++) {
594 av_push(vector, newSVnv(M[b + j * size]));
595 }
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200596 }
Marc Kupietzbdd779a2024-08-05 10:02:29 +0200597 av_push(result, newRV_noinc((SV *)vector));
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200598 }
599 }
Marc Kupietz04135302026-07-30 14:40:02 +0900600 return newRV_noinc((SV *)result);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200601}
602
Marc Kupietz6f317a92026-07-30 15:09:54 +0900603/* Word ids of the collocator db refer to the primary model, which occupies
604 [0, words - merged_end) of its own vocabulary. libcollocatordb crashes on
605 ids outside that range, and the ids come straight from the request. */
606int valid_cdb_node(long node) {
607 return cdb != NULL && node >= 0 && node < words - merged_end;
608}
609
Marc Kupietz04135302026-07-30 14:40:02 +0900610/* All functions handing a string back to perl return an SV*, because for a
611 char* return value Inline::C only copies the string into the return SV and
612 never frees the buffer we allocated here. */
613SV *getSimilarProfiles(long node) {
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200614 int i;
615 char buffer[120000];
616 char pair_buffer[2048];
Marc Kupietz969cab92019-08-05 11:13:42 +0200617 buffer[0] = '[';
618 buffer[1] = 0;
Marc Kupietz6f317a92026-07-30 15:09:54 +0900619 if (node < 0 || node >= sprofiles_qty) {
Marc Kupietzc48d3902026-08-21 12:13:45 +0200620 DEBUG_PRINTF("Not available in precomputed profile\n");
Marc Kupietz04135302026-07-30 14:40:02 +0900621 return newSVpv("[{\"w\":\"not available\", \"v\":0}]\n", 0);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200622 }
623
Marc Kupietzc48d3902026-08-21 12:13:45 +0200624 DEBUG_PRINTF("******* %s ******\n", &vocab[max_w * node]);
Marc Kupietz969cab92019-08-05 11:13:42 +0200625
626 for (i = 0; i < 100 && i < sprofiles[node].len; i++) {
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200627 sprintf(pair_buffer, "{\"w\":\"%s\", \"v\":%f},", &vocab[max_w * (sprofiles[node].nbr[i].index)], sprofiles[node].nbr[i].value);
628 strcat(buffer, pair_buffer);
629 }
Marc Kupietz04135302026-07-30 14:40:02 +0900630 if (i > 0)
631 buffer[strlen(buffer) - 1] = ']';
632 else
633 strcat(buffer, "]");
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200634 strcat(buffer, "\n");
Marc Kupietzc48d3902026-08-21 12:13:45 +0200635 DEBUG_PRINTF("%s", buffer);
Marc Kupietz04135302026-07-30 14:40:02 +0900636 return newSVpv(buffer, 0);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200637}
638
Marc Kupietz04135302026-07-30 14:40:02 +0900639/* get_collocat*_as_json() hand out strdup()ed buffers that we own. */
640SV *getCollocationScores(long node, long collocate) {
Marc Kupietz6f317a92026-07-30 15:09:54 +0900641 char *json = NULL;
642 SV *res;
643 if (valid_cdb_node(node) && valid_cdb_node(collocate))
644 json = (char *)get_collocation_scores_as_json(cdb, node, collocate);
645 res = newSVpv(json ? json : "{\"collocates\":[]}", 0);
Marc Kupietz04135302026-07-30 14:40:02 +0900646 free(json);
647 return res;
Marc Kupietzf6080012021-03-12 09:14:42 +0100648}
649
Marc Kupietz04135302026-07-30 14:40:02 +0900650SV *getClassicCollocators(long node) {
Marc Kupietz6f317a92026-07-30 15:09:54 +0900651 char *json = NULL;
652 SV *res;
653 if (valid_cdb_node(node))
654 json = (char *)get_collocators_as_json(cdb, node);
655 res = newSVpv(json ? json : "{\"collocates\":[]}", 0);
Marc Kupietz04135302026-07-30 14:40:02 +0900656 free(json);
Marc Kupietz969cab92019-08-05 11:13:42 +0200657 return res;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200658}
659
Marc Kupietz565274e2026-09-06 13:17:17 +0200660/* Returns the position of a word in the vocabulary, or -1. In a merged model
661 the two vocabularies sit next to each other and search_backw selects which
662 of them is looked at. */
663static long long lookupWord(const char *word, int search_backw) {
664 long long b, lower = (merge_words ? merge_words : 0);
665 long long upper = (merge_words ? merge_words : words);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200666
Marc Kupietz565274e2026-09-06 13:17:17 +0200667 if (search_backw) {
668 for (b = words - 1; b >= lower && strcmp(&vocab[b * max_w], word) != 0; b--)
669 ;
670 if (b < lower) b = -1;
671 } else {
672 for (b = 0; b < upper && strcmp(&vocab[b * max_w], word) != 0; b++)
673 ;
674 if (b >= upper) b = -1;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200675 }
Marc Kupietz565274e2026-09-06 13:17:17 +0200676 return b;
677}
678
679/* Splits a query into the operands of a vector expression. Blanks separate
680 words as before, and a leading '+' or '-' decides with which sign a word
681 enters the query vector, so that "König - Mann + Frau" is answered with the
682 neighbours of vec(König) - vec(Mann) + vec(Frau) rather than with those of
683 any single word. A sign is only an operator at the beginning of a token,
684 which leaves hyphenated words such as "Nord-Süd-Dialog" searchable; a
685 free standing sign applies to the word that follows it.
686
687 Tokens that are not in the vocabulary are left out of the list and
Marc Kupietz21fcd5f2026-09-06 13:17:29 +0200688 collected in wl->oov, so that the caller can report them. */
Marc Kupietz565274e2026-09-06 13:17:17 +0200689wordlist *getTargetWords(char *st1, int search_backw) {
690 wordlist *wl = calloc(1, sizeof(wordlist));
691 char *copy = strdup(st1), *tok, *saveptr = NULL;
692 size_t oov_len = 0;
693 int sign = '+';
694
695 if (wl == NULL || copy == NULL) {
696 free(wl);
697 free(copy);
698 return NULL;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200699 }
Marc Kupietz565274e2026-09-06 13:17:17 +0200700
701 for (tok = strtok_r(copy, " \t\r\n", &saveptr); tok != NULL; tok = strtok_r(NULL, " \t\r\n", &saveptr)) {
702 long long b;
703 if (*tok == '+' || *tok == '-') {
704 sign = *tok++;
705 if (*tok == 0) continue; /* " - Mann": the sign belongs to the next token */
706 }
707 if (wl->length >= MAX_TARGET_WORDS) break;
Marc Kupietz21fcd5f2026-09-06 13:17:29 +0200708 /* Counted before the lookup: an unknown operand does not change what was
709 asked for. */
Marc Kupietz565274e2026-09-06 13:17:17 +0200710 if (sign == '-') wl->subtractions++;
711 b = lookupWord(tok, search_backw);
712 if (b < 0) {
713 DEBUG_EPRINTF("Out of dictionary word: \"%s\"\n", tok);
714 if (oov_len + strlen(tok) + 2 <= sizeof(wl->oov)) {
715 if (oov_len > 0) wl->oov[oov_len++] = ' ';
716 strcpy(wl->oov + oov_len, tok);
717 oov_len += strlen(tok);
718 }
719 } else {
720 DEBUG_EPRINTF("Word: \"%s\" Sign: %c Position in vocabulary: %lld\n", &vocab[b * max_w], sign, b);
721 wl->sep[wl->length] = (char)sign;
722 wl->wordi[wl->length++] = b;
723 }
724 sign = '+';
725 }
726 free(copy);
Marc Kupietz969cab92019-08-05 11:13:42 +0200727 return (wl);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200728}
729
Marc Kupietzcb43e492019-12-03 10:07:53 +0100730long getWordNumber(char *word) {
731 wordlist *wl = getTargetWords(word, 0);
Marc Kupietz04135302026-07-30 14:40:02 +0900732 long res = 0;
733 if (wl == NULL)
734 return(0);
Marc Kupietzcb43e492019-12-03 10:07:53 +0100735 if(wl->length > 0)
Marc Kupietz04135302026-07-30 14:40:02 +0900736 res = wl->wordi[0];
737 free(wl);
738 return(res);
Marc Kupietzcb43e492019-12-03 10:07:53 +0100739}
740
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200741float get_distance(long b, long c) {
742 long a;
743 float dist = 0;
744 for (a = 0; a < size; a++) dist += M[a + c * size] * M[a + b * size];
745 return dist;
746}
747
Marc Kupietz04135302026-07-30 14:40:02 +0900748/* The result is computed once and then kept in a static buffer for the
749 lifetime of the process. */
750SV *getBiggestMergedDifferences() {
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200751 static char *result = NULL;
Marc Kupietz59865a92021-03-11 17:16:51 +0100752 float dist;
753 long long a, c;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200754 int N = 1000;
755
Marc Kupietz969cab92019-08-05 11:13:42 +0200756 if (merged_end == 0)
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200757 result = "[]";
Marc Kupietz969cab92019-08-05 11:13:42 +0200758
759 if (result != NULL)
Marc Kupietz04135302026-07-30 14:40:02 +0900760 return newSVpv(result, 0);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200761
Marc Kupietzc48d3902026-08-21 12:13:45 +0200762 DEBUG_PRINTF("Looking for biggest distances between main and merged vectors ...\n");
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200763 collocator *best;
764 best = malloc(N * sizeof(collocator));
765 memset(best, 0, N * sizeof(collocator));
766
Marc Kupietz969cab92019-08-05 11:13:42 +0200767 float worstbest = 1000000;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200768
769 for (a = 0; a < N; a++) best[a].activation = worstbest;
770
771 for (c = 0; c < 500000; c++) {
Marc Kupietz969cab92019-08-05 11:13:42 +0200772 if (garbage && garbage[c]) continue;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200773 dist = 0;
Marc Kupietz969cab92019-08-05 11:13:42 +0200774 for (a = 0; a < size; a++) dist += M[a + c * size] * M[a + (c + merged_end) * size];
775 if (dist < worstbest) {
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200776 for (a = 0; a < N; a++) {
777 if (dist < best[a].activation) {
Marc Kupietz969cab92019-08-05 11:13:42 +0200778 memmove(best + a + 1, best + a, (N - a - 1) * sizeof(collocator));
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200779 best[a].activation = dist;
780 best[a].wordi = c;
781 break;
782 }
783 }
Marc Kupietz969cab92019-08-05 11:13:42 +0200784 worstbest = best[N - 1].activation;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200785 }
786 }
787
Marc Kupietz04135302026-07-30 14:40:02 +0900788 result = malloc(N * (max_w + 64));
Marc Kupietzbdd779a2024-08-05 10:02:29 +0200789 char *p = (char *) result;
Marc Kupietz969cab92019-08-05 11:13:42 +0200790 *p++ = '[';
791 *p = 0;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200792 for (a = 0; a < N; a++) {
Marc Kupietz59865a92021-03-11 17:16:51 +0100793 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 +0200794 }
795 *--p = ']';
Marc Kupietz04135302026-07-30 14:40:02 +0900796 free(best);
797 return newSVpv(result, 0);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200798}
799
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200800float cos_similarity(long b, long c) {
Marc Kupietz969cab92019-08-05 11:13:42 +0200801 float dist = 0;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200802 long a;
Marc Kupietz969cab92019-08-05 11:13:42 +0200803 for (a = 0; a < size; a++) dist += M[b * size + a] * M[c * size + a];
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200804 return dist;
805}
806
Marc Kupietz04135302026-07-30 14:40:02 +0900807SV *cos_similarity_as_json(char *w1, char *w2) {
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200808 wordlist *a, *b;
809 float res;
Marc Kupietz04135302026-07-30 14:40:02 +0900810 char json[32];
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200811 a = getTargetWords(w1, 0);
812 b = getTargetWords(w2, 0);
Marc Kupietz969cab92019-08-05 11:13:42 +0200813 if (a == NULL || b == NULL || a->length != 1 || b->length != 1)
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200814 res = -1;
Marc Kupietz04135302026-07-30 14:40:02 +0900815 else {
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200816 res = cos_similarity(a->wordi[0], b->wordi[0]);
Marc Kupietzc48d3902026-08-21 12:13:45 +0200817 DEBUG_EPRINTF("a: %lld b: %lld res:%f\n", a->wordi[0], b->wordi[0], res);
Marc Kupietz04135302026-07-30 14:40:02 +0900818 }
819 free(a);
820 free(b);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200821 sprintf(json, "%.5f", res);
Marc Kupietz04135302026-07-30 14:40:02 +0900822 return newSVpv(json, 0);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200823}
824
825void *_get_neighbours(void *arg) {
826 knnpars *pars = arg;
Marc Kupietz969cab92019-08-05 11:13:42 +0200827 int N = pars->N;
828 long from = pars->from;
829 unsigned long upto = pars->upto;
Marc Kupietz59865a92021-03-11 17:16:51 +0100830 char *sep;
Marc Kupietz969cab92019-08-05 11:13:42 +0200831 float dist, len, vec[max_size];
Marc Kupietz59865a92021-03-11 17:16:51 +0100832 long long a, b, c, cn, *bi;
Marc Kupietz969cab92019-08-05 11:13:42 +0200833 knn *nbs = NULL;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200834 wordlist *wl = pars->wl;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200835
Marc Kupietz969cab92019-08-05 11:13:42 +0200836 collocator *best = pars->best;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200837
Marc Kupietz969cab92019-08-05 11:13:42 +0200838 float worstbest = -1;
839
Marc Kupietz565274e2026-09-06 13:17:17 +0200840 for (a = 0; a < N; a++) {
841 best[a].activation = -1;
842 best[a].wordi = -1;
843 }
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200844 bi = wl->wordi;
Marc Kupietz969cab92019-08-05 11:13:42 +0200845 cn = wl->length;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200846 sep = wl->sep;
Marc Kupietz565274e2026-09-06 13:17:17 +0200847 if (cn < 1) {
Marc Kupietz969cab92019-08-05 11:13:42 +0200848 goto end;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200849 }
Marc Kupietz969cab92019-08-05 11:13:42 +0200850 for (a = 0; a < size; a++) vec[a] = 0;
851 for (b = 0; b < cn; b++) {
Marc Kupietz565274e2026-09-06 13:17:17 +0200852 if (sep[b] == '-')
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200853 for (a = 0; a < size; a++) vec[a] -= M[a + bi[b] * size];
854 else
855 for (a = 0; a < size; a++) vec[a] += M[a + bi[b] * size];
Marc Kupietz969cab92019-08-05 11:13:42 +0200856 }
857 len = 0;
858 for (a = 0; a < size; a++) len += vec[a] * vec[a];
859 len = sqrt(len);
Marc Kupietz565274e2026-09-06 13:17:17 +0200860 /* An expression whose operands cancel each other out, "Haus - Haus", has no
861 position to search around. */
862 if (len == 0) {
863 goto end;
864 }
Marc Kupietz969cab92019-08-05 11:13:42 +0200865 for (a = 0; a < size; a++) vec[a] /= len;
Marc Kupietz969cab92019-08-05 11:13:42 +0200866 for (c = from; c < upto; c++) {
867 if (garbage && garbage[c]) continue;
868 a = 0;
869 // do not skip taget word
870 // for (b = 0; b < cn; b++) if (bi[b] == c) a = 1;
871 // if (a == 1) continue;
872 dist = 0;
873 for (a = 0; a < size; a++) dist += vec[a] * M[a + c * size];
874 if (dist > worstbest) {
875 for (a = 0; a < N; a++) {
876 if (dist > best[a].activation) {
877 memmove(best + a + 1, best + a, (N - a - 1) * sizeof(collocator));
878 best[a].activation = dist;
879 best[a].wordi = c;
880 break;
881 }
882 }
883 worstbest = best[N - 1].activation;
884 }
885 }
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200886
887end:
Marc Kupietz969cab92019-08-05 11:13:42 +0200888 pthread_exit(nbs);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200889}
890
Marc Kupietz969cab92019-08-05 11:13:42 +0200891int cmp_activation(const void *a, const void *b) {
892 float fb = ((collocator *)a)->activation;
893 float fa = ((collocator *)b)->activation;
894 return (fa > fb) - (fa < fb);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200895}
896
Marc Kupietz969cab92019-08-05 11:13:42 +0200897int cmp_probability(const void *a, const void *b) {
898 float fb = ((collocator *)a)->probability;
899 float fa = ((collocator *)b)->probability;
900 return (fa > fb) - (fa < fb);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200901}
902
Marc Kupietz04135302026-07-30 14:40:02 +0900903SV *getPosWiseW2VCollocators(char *word, long maxPerPos, long cutoff, float threshold, const char *format) {
Marc Kupietz59865a92021-03-11 17:16:51 +0100904 float *target_sums = NULL;
Marc Kupietzd6a163c2026-07-30 14:48:10 +0900905 float *window_sums = NULL;
Marc Kupietz04135302026-07-30 14:40:02 +0900906 long a, b, entries = 0;
Marc Kupietz969cab92019-08-05 11:13:42 +0200907 knn *syn_nbs[MAX_THREADS];
908 knnpars pars[MAX_THREADS];
Marc Kupietz04135302026-07-30 14:40:02 +0900909 pthread_t *pt = NULL;
910 wordlist *wl = NULL;
Marc Kupietz969cab92019-08-05 11:13:42 +0200911 int syn_threads = (M2 ? window * 2 : 0);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200912 int search_backw = 0;
Marc Kupietz04135302026-07-30 14:40:02 +0900913 char *result = NULL;
914 SV *res_sv;
915
916 for (a = 0; a < MAX_THREADS; a++) syn_nbs[a] = NULL;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200917
Marc Kupietz969cab92019-08-05 11:13:42 +0200918 if (cutoff < 1 || cutoff > words)
919 cutoff = words;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200920
921 wl = getTargetWords(word, search_backw);
Marc Kupietz565274e2026-09-06 13:17:17 +0200922 if (wl == NULL || wl->length < 1 || syn_threads < 1) {
Marc Kupietz04135302026-07-30 14:40:02 +0900923 free(wl);
924 return newSVpv("", 0);
925 }
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200926
Marc Kupietz04135302026-07-30 14:40:02 +0900927 pt = (pthread_t *)malloc((num_threads + 1) * sizeof(pthread_t));
Marc Kupietzd6a163c2026-07-30 14:48:10 +0900928 window_sums = new_window_sums();
Marc Kupietz969cab92019-08-05 11:13:42 +0200929 a = posix_memalign((void **)&target_sums, 128, cutoff * sizeof(float));
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200930 memset(target_sums, 0, cutoff * sizeof(float));
931
Marc Kupietzc48d3902026-08-21 12:13:45 +0200932 DEBUG_PRINTF("Starting %d threads\n", syn_threads);
933 DEBUG_FFLUSH();
Marc Kupietz969cab92019-08-05 11:13:42 +0200934 for (a = 0; a < syn_threads; a++) {
935 pars[a].cutoff = cutoff;
936 pars[a].target_sums = target_sums;
937 pars[a].window_sums = window_sums;
938 pars[a].wl = wl;
939 pars[a].N = maxPerPos;
Marc Kupietz04135302026-07-30 14:40:02 +0900940 pars[a].best = NULL; /* getCollocators() allocates its own result array */
Marc Kupietz969cab92019-08-05 11:13:42 +0200941 pars[a].threshold = threshold;
942 pars[a].from = a;
943 pars[a].upto = a + 1;
944 pthread_create(&pt[a], NULL, getCollocators, (void *)&pars[a]);
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200945 }
Marc Kupietzc48d3902026-08-21 12:13:45 +0200946 DEBUG_PRINTF("Waiting for syn threads to join\n");
947 DEBUG_FFLUSH();
Marc Kupietz969cab92019-08-05 11:13:42 +0200948 for (a = 0; a < syn_threads; a++) pthread_join(pt[a], (void *)&syn_nbs[a]);
Marc Kupietzc48d3902026-08-21 12:13:45 +0200949 DEBUG_PRINTF("Syn threads joint\n");
950 DEBUG_FFLUSH();
Marc Kupietz04135302026-07-30 14:40:02 +0900951 result = malloc((maxPerPos > 0 ? maxPerPos : 1) * (max_w + 96) * syn_threads + 16);
Marc Kupietzbdd779a2024-08-05 10:02:29 +0200952 char *p = (char *) result;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200953 *p = 0;
Marc Kupietz0ab97392024-12-10 16:16:32 +0100954 if (strcmp(format, "tsv") == 0) {
955 for (a = syn_threads - 1; a >= 0; a--) {
Marc Kupietz04135302026-07-30 14:40:02 +0900956 if (syn_nbs[a] == NULL) continue;
957 for (b = 0; b < syn_nbs[a]->length; b++, entries++) {
Marc Kupietz0ab97392024-12-10 16:16:32 +0100958 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);
959 }
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200960 }
Marc Kupietz0ab97392024-12-10 16:16:32 +0100961 } else {
962 p += sprintf(p, "[");
963 for (a = syn_threads - 1; a >= 0; a--) {
Marc Kupietz04135302026-07-30 14:40:02 +0900964 if (syn_nbs[a] == NULL) continue;
965 for (b = 0; b < syn_nbs[a]->length; b++, entries++) {
Marc Kupietz0ab97392024-12-10 16:16:32 +0100966 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);
967 }
968 }
Marc Kupietz04135302026-07-30 14:40:02 +0900969 if (entries > 0)
970 p -= 2; /* drop the trailing ",\n" */
Marc Kupietz0ab97392024-12-10 16:16:32 +0100971 p += sprintf(p, "\n]");
Marc Kupietz969cab92019-08-05 11:13:42 +0200972 }
Marc Kupietz0ab97392024-12-10 16:16:32 +0100973
Marc Kupietz04135302026-07-30 14:40:02 +0900974 res_sv = newSVpv(result, 0);
975
976 free(result);
977 free(target_sums);
Marc Kupietzd6a163c2026-07-30 14:48:10 +0900978 free(window_sums);
Marc Kupietz04135302026-07-30 14:40:02 +0900979 free(pt);
980 free(wl);
981 for (a = 0; a < syn_threads; a++) free_knn(syn_nbs[a]);
982
983 return res_sv;
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200984}
985
Marc Kupietz04135302026-07-30 14:40:02 +0900986SV *getPosWiseW2VCollocatorsAsTsv(char *word, long maxPerPos, long cutoff, float threshold) {
Marc Kupietz0ab97392024-12-10 16:16:32 +0100987 return getPosWiseW2VCollocators(word, maxPerPos, cutoff, threshold, "tsv");
988}
989
Marc Kupietz04135302026-07-30 14:40:02 +0900990SV *getPosWiseW2VCollocatorsAsJson(char *word, long maxPerPos, long cutoff, float threshold) {
Marc Kupietz0ab97392024-12-10 16:16:32 +0100991 return getPosWiseW2VCollocators(word, maxPerPos, cutoff, threshold, "json");
992}
993
Marc Kupietzf11d20c2019-08-02 15:42:04 +0200994SV *get_neighbours(char *st1, int N, int sort_by, int search_backw, long cutoff, int dedupe, int no_similar_profiles) {
995 HV *result = newHV();
Marc Kupietz59865a92021-03-11 17:16:51 +0100996 float *target_sums = NULL;
Marc Kupietzd6a163c2026-07-30 14:48:10 +0900997 float *window_sums = NULL;
Marc Kupietz969cab92019-08-05 11:13:42 +0200998 long a, b, c, d, slice;
999 knn *para_nbs[MAX_THREADS];
1000 knn *syn_nbs[MAX_THREADS];
1001 knnpars pars[MAX_THREADS];
1002 pthread_t *pt = (pthread_t *)malloc((num_threads + 1) * sizeof(pthread_t));
Marc Kupietz04135302026-07-30 14:40:02 +09001003 wordlist *wl = NULL;
Marc Kupietz565274e2026-09-06 13:17:17 +02001004 int syn_threads = 0;
1005 int para_threads = 0;
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001006
Marc Kupietz04135302026-07-30 14:40:02 +09001007 for (a = 0; a < MAX_THREADS; a++) para_nbs[a] = syn_nbs[a] = NULL;
1008
Marc Kupietz969cab92019-08-05 11:13:42 +02001009 if (N > MAX_NEIGHBOURS) N = MAX_NEIGHBOURS;
Marc Kupietz565274e2026-09-06 13:17:17 +02001010 if (N < 1) N = 1;
1011
1012 /* Every paradigmatic thread fills its own slice of N entries, and the
1013 syntagmatic part below works on the first MAX_NEIGHBOURS of the same
1014 array. How many paradigmatic threads there are is only known further
1015 down, so the array is sized for the maximum. */
1016 collocator *best = NULL;
1017 long best_entries = (long)N * num_threads;
1018 if (best_entries < MAX_NEIGHBOURS) best_entries = MAX_NEIGHBOURS;
1019 posix_memalign((void **)&best, 128, best_entries * sizeof(collocator));
1020 memset(best, 0, best_entries * sizeof(collocator));
Marc Kupietz969cab92019-08-05 11:13:42 +02001021
1022 if (cutoff < 1 || cutoff > words)
1023 cutoff = words;
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001024
1025 wl = getTargetWords(st1, search_backw);
Marc Kupietz565274e2026-09-06 13:17:17 +02001026 if (wl == NULL)
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001027 goto end;
1028
Marc Kupietz565274e2026-09-06 13:17:17 +02001029 /* Tell the caller which words the query vector was built from and which
1030 tokens of the query are unknown, so that "König - Mannn + Frau" is not
1031 silently answered as "König + Frau". */
1032 {
1033 SV *added = newSVpv("", 0);
1034 SV *unknown = newSVpv(wl->oov, 0);
1035 for (a = 0; a < wl->length; a++) {
1036 if (wl->sep[a] == '-') continue;
1037 if (SvCUR(added) > 0) sv_catpvn(added, " ", 1);
1038 sv_catpv(added, &vocab[wl->wordi[a] * max_w]);
1039 }
1040 if (latin_enc == 0) {
1041 SvUTF8_on(added);
1042 SvUTF8_on(unknown);
1043 }
1044 hv_store(result, "added", strlen("added"), added, 0);
1045 hv_store(result, "unknown", strlen("unknown"), unknown, 0);
1046 hv_store(result, "operands", strlen("operands"), newSViv(wl->length), 0);
1047 }
1048
1049 if (wl->length < 1)
1050 goto end;
1051
1052 syn_threads = (M2 ? window * 2 : 0);
1053 para_threads = (no_similar_profiles ? 0 : num_threads - syn_threads);
1054
Marc Kupietz04135302026-07-30 14:40:02 +09001055 slice = (para_threads > 0 ? cutoff / para_threads : cutoff);
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001056
Marc Kupietz969cab92019-08-05 11:13:42 +02001057 a = posix_memalign((void **)&target_sums, 128, cutoff * sizeof(float));
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001058 memset(target_sums, 0, cutoff * sizeof(float));
1059
Marc Kupietzc48d3902026-08-21 12:13:45 +02001060 DEBUG_PRINTF("Starting %d threads for paradigmatic search\n", para_threads);
1061 DEBUG_FFLUSH();
Marc Kupietz969cab92019-08-05 11:13:42 +02001062 for (a = 0; a < para_threads; a++) {
1063 pars[a].cutoff = cutoff;
1064 pars[a].token = st1;
1065 pars[a].wl = wl;
1066 pars[a].N = N;
1067 pars[a].best = &best[N * a];
1068 if (merge_words == 0 || search_backw == 0) {
1069 pars[a].from = a * slice;
1070 pars[a].upto = ((a + 1) * slice > cutoff ? cutoff : (a + 1) * slice);
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001071 } else {
1072 pars[a].from = merge_words + a * slice;
Marc Kupietz969cab92019-08-05 11:13:42 +02001073 pars[a].upto = merge_words + ((a + 1) * slice > cutoff ? cutoff : (a + 1) * slice);
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001074 }
Marc Kupietzc48d3902026-08-21 12:13:45 +02001075 DEBUG_PRINTF("From: %ld, Upto: %ld\n", pars[a].from, pars[a].upto);
Marc Kupietz969cab92019-08-05 11:13:42 +02001076 pthread_create(&pt[a], NULL, _get_neighbours, (void *)&pars[a]);
1077 }
Marc Kupietz565274e2026-09-06 13:17:17 +02001078 if (syn_threads) {
Marc Kupietzd6a163c2026-07-30 14:48:10 +09001079 window_sums = new_window_sums();
Marc Kupietz969cab92019-08-05 11:13:42 +02001080 for (a = 0; a < syn_threads; a++) {
1081 pars[a + para_threads].cutoff = cutoff;
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001082 pars[a + para_threads].target_sums = target_sums;
1083 pars[a + para_threads].window_sums = window_sums;
1084 pars[a + para_threads].wl = wl;
1085 pars[a + para_threads].N = N;
1086 pars[a + para_threads].threshold = MIN_RESP;
1087 pars[a + para_threads].from = a;
Marc Kupietz969cab92019-08-05 11:13:42 +02001088 pars[a + para_threads].upto = a + 1;
1089 pthread_create(&pt[a + para_threads], NULL, getCollocators, (void *)&pars[a + para_threads]);
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001090 }
1091 }
Marc Kupietzc48d3902026-08-21 12:13:45 +02001092 DEBUG_PRINTF("Waiting for para threads to join\n");
1093 DEBUG_FFLUSH();
Marc Kupietz969cab92019-08-05 11:13:42 +02001094 for (a = 0; a < para_threads; a++) pthread_join(pt[a], (void *)&para_nbs[a]);
Marc Kupietzc48d3902026-08-21 12:13:45 +02001095 DEBUG_PRINTF("Para threads joint\n");
1096 DEBUG_FFLUSH();
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001097
Marc Kupietz969cab92019-08-05 11:13:42 +02001098 /* if(!syn_nbs[0]) */
1099 /* goto end; */
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001100
Marc Kupietz969cab92019-08-05 11:13:42 +02001101 qsort(best, N * para_threads, sizeof(collocator), cmp_activation);
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001102
1103 long long chosen[MAX_NEIGHBOURS];
Marc Kupietzc48d3902026-08-21 12:13:45 +02001104 DEBUG_PRINTF("N: %d\n", N);
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001105
Marc Kupietz969cab92019-08-05 11:13:42 +02001106 AV *array = newAV();
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001107 int i, j;
Marc Kupietz969cab92019-08-05 11:13:42 +02001108 int l1_words = 0, l2_words = 0;
1109
1110 for (a = 0, i = 0; i < N && a < N * para_threads; a++) {
1111 int filtered = 0;
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001112 long long c = best[a].wordi;
Marc Kupietz565274e2026-09-06 13:17:17 +02001113 if (c < 0) /* the threads found fewer candidates than were asked for */
1114 break;
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001115 if ((merge_words && dedupe && i > 1) || (!merge_words && dedupe && i > 0)) {
Marc Kupietz969cab92019-08-05 11:13:42 +02001116 for (j = 0; j < i && !filtered; j++)
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001117 if (strcasestr(&vocab[c * max_w], &vocab[chosen[j] * max_w]) ||
1118 strcasestr(&vocab[chosen[j] * max_w], &vocab[c * max_w])) {
Marc Kupietzc48d3902026-08-21 12:13:45 +02001119 DEBUG_PRINTF("filtering %s %s\n", &vocab[chosen[j] * max_w], &vocab[c * max_w]);
Marc Kupietz969cab92019-08-05 11:13:42 +02001120 filtered = 1;
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001121 }
Marc Kupietz969cab92019-08-05 11:13:42 +02001122 if (filtered)
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001123 continue;
1124 }
1125
Marc Kupietz969cab92019-08-05 11:13:42 +02001126 if (0 && merge_words > 0) {
1127 if (c >= merge_words) {
1128 if (l1_words > N / 2)
1129 continue;
1130 else
1131 l1_words++;
1132 } else {
1133 if (l2_words > N / 2)
1134 continue;
1135 else
1136 l2_words++;
1137 }
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001138 }
1139
Marc Kupietz969cab92019-08-05 11:13:42 +02001140 // printf("%s l1:%d l2:%d i:%d a:%ld\n", &vocab[c * max_w], l1_words, l2_words, i, a);
1141 // fflush(stdout);
1142 HV *hash = newHV();
1143 SV *word = newSVpvf(&vocab[c * max_w], 0);
1144 chosen[i] = c;
1145 if (latin_enc == 0) SvUTF8_on(word);
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001146 fflush(stdout);
Marc Kupietz969cab92019-08-05 11:13:42 +02001147 hv_store(hash, "word", strlen("word"), word, 0);
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001148 hv_store(hash, "dist", strlen("dist"), newSVnv(best[a].activation), 0);
1149 hv_store(hash, "rank", strlen("rank"), newSVuv(best[a].wordi), 0);
1150 AV *vector = newAV();
1151 for (b = 0; b < size; b++) {
1152 av_push(vector, newSVnv(M[b + best[a].wordi * size]));
1153 }
Marc Kupietz969cab92019-08-05 11:13:42 +02001154 hv_store(hash, "vector", strlen("vector"), newRV_noinc((SV *)vector), 0);
1155 av_push(array, newRV_noinc((SV *)hash));
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001156 i++;
1157 }
Marc Kupietz969cab92019-08-05 11:13:42 +02001158 hv_store(result, "paradigmatic", strlen("paradigmatic"), newRV_noinc((SV *)array), 0);
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001159
Marc Kupietz969cab92019-08-05 11:13:42 +02001160 for (b = 0; b < MAX_NEIGHBOURS; b++) {
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001161 best[b].wordi = -1L;
1162 best[b].activation = 0;
Marc Kupietz21fcd5f2026-09-06 13:17:29 +02001163 best[b].raw = 0;
1164 best[b].max_raw = 0;
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001165 best[b].probability = 0;
1166 best[b].position = 0;
1167 best[b].activation_sum = 0;
Marc Kupietz969cab92019-08-05 11:13:42 +02001168 memset(best[b].heat, 0, sizeof(float) * 16);
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001169 }
1170
Marc Kupietz969cab92019-08-05 11:13:42 +02001171 float total_activation = 0;
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001172
Marc Kupietz565274e2026-09-06 13:17:17 +02001173 if (syn_threads) {
Marc Kupietzc48d3902026-08-21 12:13:45 +02001174 DEBUG_PRINTF("Waiting for syn threads to join\n");
1175 DEBUG_FFLUSH();
Marc Kupietz969cab92019-08-05 11:13:42 +02001176 for (a = 0; a < syn_threads; a++) pthread_join(pt[a + para_threads], (void *)&syn_nbs[a]);
1177 for (a = 0; a <= syn_threads; a++) {
1178 if (a == window) continue;
1179 total_activation += window_sums[a];
Marc Kupietzc48d3902026-08-21 12:13:45 +02001180 DEBUG_PRINTF("window pos: %ld, sum: %f\n", a, window_sums[a]);
Marc Kupietz969cab92019-08-05 11:13:42 +02001181 }
Marc Kupietzc48d3902026-08-21 12:13:45 +02001182 DEBUG_PRINTF("syn threads joint\n");
1183 DEBUG_FFLUSH();
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001184
Marc Kupietz969cab92019-08-05 11:13:42 +02001185 for (b = 0; b < syn_nbs[0]->length; b++) {
1186 memcpy(best + b, &syn_nbs[0]->best[b], sizeof(collocator));
1187 best[b].position = -1; // syn_nbs[0]->pos[b];
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001188 best[b].activation_sum = target_sums[syn_nbs[0]->best[b].wordi];
Marc Kupietz969cab92019-08-05 11:13:42 +02001189 best[b].max_activation = 0.0;
Marc Kupietz21fcd5f2026-09-06 13:17:29 +02001190 best[b].max_raw = 0.0;
Marc Kupietz969cab92019-08-05 11:13:42 +02001191 best[b].average = 0.0;
1192 best[b].probability = 0.0;
1193 best[b].cprobability = syn_nbs[0]->best[b].cprobability;
1194 memset(best[b].heat, 0, sizeof(float) * 16);
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001195 }
Marc Kupietz969cab92019-08-05 11:13:42 +02001196
1197 float best_window_sum[MAX_NEIGHBOURS];
Marc Kupietz59865a92021-03-11 17:16:51 +01001198 int found_index = 0, i = 0, w;
Marc Kupietz969cab92019-08-05 11:13:42 +02001199 for (a = 0; a < syn_threads; a++) {
1200 for (b = 0; b < syn_nbs[a]->length; b++) {
1201 for (i = 0; i < found_index; i++)
1202 if (best[i].wordi == syn_nbs[a]->best[b].wordi)
1203 break;
1204 if (i >= found_index) {
1205 best[found_index].max_activation = 0.0;
Marc Kupietz21fcd5f2026-09-06 13:17:29 +02001206 best[found_index].max_raw = 0.0;
Marc Kupietz969cab92019-08-05 11:13:42 +02001207 best[found_index].average = 0.0;
1208 best[found_index].probability = 0.0;
1209 memset(best[found_index].heat, 0, sizeof(float) * 16);
1210 best[found_index].cprobability = syn_nbs[a]->best[b].cprobability;
1211 best[found_index].activation_sum = target_sums[syn_nbs[a]->best[b].wordi]; // syn_nbs[a]->best[b].activation_sum;
1212 best[found_index++].wordi = syn_nbs[a]->best[b].wordi;
1213 // printf("found: %s\n", &vocab[syn_nbs[a]->index[b] * max_w]);
1214 }
1215 }
1216 }
1217 sort_by = 0; // ALWAYS AUTO-FOCUS
1218 if (sort_by != 1 && sort_by != 2) { // sort by auto focus mean
Marc Kupietzc48d3902026-08-21 12:13:45 +02001219 DEBUG_PRINTF("window: %d - syn_threads: %d, %d\n", window, syn_threads, (1 << syn_threads) - 1);
Marc Kupietz969cab92019-08-05 11:13:42 +02001220 int wpos;
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001221 int bits_set = 0;
Marc Kupietz969cab92019-08-05 11:13:42 +02001222 for (i = 0; i < found_index; i++) {
1223 best[i].activation = best[i].probability = best[i].average = best[i].cprobability_sum = 0;
1224 for (w = 1; w < (1 << syn_threads); w++) { // loop through all possible windows
1225 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 +02001226 bits_set = 0;
Marc Kupietz969cab92019-08-05 11:13:42 +02001227 for (a = 0; a < syn_threads; a++) {
1228 if ((1 << a) & w) {
1229 wpos = (a >= window ? a + 1 : a);
1230 total_window_sum += window_sums[wpos];
1231 }
1232 }
1233 // printf("%d window-sum %f\n", w, total_window_sum);
1234 for (a = 0; a < syn_threads; a++) {
1235 if ((1 << a) & w) {
1236 wpos = (a >= window ? a + 1 : a);
1237 bits_set++;
1238 for (b = 0; b < syn_nbs[a]->length; b++)
1239 if (best[i].wordi == syn_nbs[a]->best[b].wordi) {
1240 // float acti = syn_nbs[a]->best[b].activation / total_window_sum;
1241 // word_window_sum += syn_nbs[a]->dist[b] * syn_nbs[a]->norm[b]; // / window_sums[wpos]; // syn_nbs[a]->norm[b];
1242 // word_window_sum += syn_nbs[a]->norm[b]; // / window_sums[wpos]; // syn_nbs[a]->norm[b];
1243 // 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 +02001244
Marc Kupietz969cab92019-08-05 11:13:42 +02001245 word_window_sum += syn_nbs[a]->best[b].activation; // / window_sums[wpos]; // syn_nbs[a]->norm[b];
1246 // 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 +02001247
Marc Kupietz969cab92019-08-05 11:13:42 +02001248 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 +02001249 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 +02001250 word_activation_sum += syn_nbs[a]->best[b].activation;
1251 if (syn_nbs[a]->best[b].activation > best[i].max_activation)
1252 best[i].max_activation = syn_nbs[a]->best[b].activation;
Marc Kupietz21fcd5f2026-09-06 13:17:29 +02001253 if (syn_nbs[a]->best[b].raw > best[i].max_raw)
1254 best[i].max_raw = syn_nbs[a]->best[b].raw;
Marc Kupietz969cab92019-08-05 11:13:42 +02001255 if (syn_nbs[a]->best[b].activation > best[i].heat[wpos])
1256 best[i].heat[wpos] = syn_nbs[a]->best[b].activation;
1257 }
1258 }
1259 }
1260 if (bits_set) {
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001261 word_window_average /= bits_set;
Marc Kupietz969cab92019-08-05 11:13:42 +02001262 // word_activation_sum /= bits_set;
1263 // word_window_sum /= bits_set;
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001264 }
1265
Marc Kupietz969cab92019-08-05 11:13:42 +02001266 word_window_sum /= total_window_sum;
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001267
Marc Kupietz969cab92019-08-05 11:13:42 +02001268 if (word_window_sum > best[i].probability) {
1269 // best[i].position = w;
1270 best[i].probability = word_window_sum;
1271 }
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001272
Marc Kupietz969cab92019-08-05 11:13:42 +02001273 if (word_cprobability_sum > best[i].cprobability_sum) {
1274 best[i].position = w;
1275 best[i].cprobability_sum = word_cprobability_sum;
1276 }
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001277
Marc Kupietz969cab92019-08-05 11:13:42 +02001278 best[i].average = word_window_average;
1279 // best[i].activation = word_activation_sum;
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001280 }
Marc Kupietz969cab92019-08-05 11:13:42 +02001281 }
1282 qsort(best, found_index, sizeof(collocator), cmp_probability);
1283 // for(i=0; i < found_index; i++) {
1284 // printf("found: %s - sum: %f - window: %d\n", &vocab[best[i].wordi * max_w], best[i].activation, best[i].position);
1285 // }
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001286
Marc Kupietz969cab92019-08-05 11:13:42 +02001287 } else if (sort_by == 1) { // responsiveness any window position
1288 int wpos;
1289 for (i = 0; i < found_index; i++) {
1290 float word_window_sum = 0, word_activation_sum = 0, total_window_sum = 0;
1291 for (a = 0; a < syn_threads; a++) {
1292 wpos = (a >= window ? a + 1 : a);
1293 for (b = 0; b < syn_nbs[a]->length; b++)
1294 if (best[i].wordi == syn_nbs[a]->best[b].wordi) {
1295 best[i].probability += syn_nbs[a]->best[b].probability;
1296 if (syn_nbs[a]->best[b].activation > 0.25)
1297 best[i].position |= 1 << wpos;
1298 if (syn_nbs[a]->best[b].activation > best[i].activation) {
1299 best[i].activation = syn_nbs[a]->best[b].activation;
1300 }
1301 }
1302 }
1303 }
1304 qsort(best, found_index, sizeof(collocator), cmp_activation);
1305 } else if (sort_by == 2) { // single window position
1306 for (a = 1; a < syn_threads; a++) {
1307 for (b = 0; b < syn_nbs[a]->length; b++) {
1308 for (c = 0; c < MAX_NEIGHBOURS; c++) {
1309 if (syn_nbs[a]->best[b].activation > best[c].activation) {
1310 for (d = MAX_NEIGHBOURS - 1; d > c; d--) {
1311 memmove(best + d, best + d - 1, sizeof(collocator));
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001312 }
1313 memcpy(best + c, &syn_nbs[a]->best[b], sizeof(collocator));
Marc Kupietz969cab92019-08-05 11:13:42 +02001314 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 +02001315 break;
1316 }
1317 }
1318 }
1319 }
Marc Kupietz969cab92019-08-05 11:13:42 +02001320 } else { // sort by mean p
1321 for (a = 1; a < syn_threads; a++) {
1322 for (b = 0; b < syn_nbs[a]->length; b++) {
1323 for (c = 0; c < MAX_NEIGHBOURS; c++) {
1324 if (target_sums[syn_nbs[a]->best[b].wordi] > best[c].activation_sum) {
1325 for (d = MAX_NEIGHBOURS - 1; d > c; d--) {
1326 memmove(best + d, best + d - 1, sizeof(collocator));
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001327 }
1328 memcpy(best + c, &syn_nbs[a]->best[b], sizeof(collocator));
Marc Kupietz969cab92019-08-05 11:13:42 +02001329 best[c].position = (1 << 2 * window) - 1; // syn_nbs[a]->pos[b];
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001330 best[c].activation_sum = target_sums[syn_nbs[a]->best[b].wordi];
1331 break;
1332 }
1333 }
1334 }
1335 }
1336 }
1337 array = newAV();
Marc Kupietz969cab92019-08-05 11:13:42 +02001338 for (a = 0, i = 0; a < MAX_NEIGHBOURS && best[a].wordi >= 0; a++) {
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001339 long long c = best[a].wordi;
Marc Kupietz969cab92019-08-05 11:13:42 +02001340 /*
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001341 if (dedupe) {
1342 int filtered=0;
1343 for (j=0; j<i; j++)
1344 if (strcasestr(&vocab[c * max_w], chosen[j]) ||
1345 strcasestr(chosen[j], &vocab[c * max_w])) {
1346 printf("filtering %s %s\n", chosen[j], &vocab[c * max_w]);
1347 filtered = 1;
1348 }
1349 if(filtered)
1350 continue;
1351 }
1352*/
Marc Kupietz969cab92019-08-05 11:13:42 +02001353 chosen[i++] = c;
1354 HV *hash = newHV();
1355 SV *word = newSVpvf(&vocab[best[a].wordi * max_w], 0);
1356 AV *heat = newAV();
1357 if (latin_enc == 0) SvUTF8_on(word);
1358 hv_store(hash, "word", strlen("word"), word, 0);
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001359 hv_store(hash, "rank", strlen("rank"), newSVuv(best[a].wordi), 0);
1360 hv_store(hash, "average", strlen("average"), newSVnv(best[a].average), 0);
1361 hv_store(hash, "prob", strlen("prob"), newSVnv(best[a].probability), 0);
1362 hv_store(hash, "cprob", strlen("cprob"), newSVnv(best[a].cprobability_sum), 0);
Marc Kupietz21fcd5f2026-09-06 13:17:29 +02001363 hv_store(hash, "dot", strlen("dot"), newSVnv(best[a].max_raw), 0);
Marc Kupietz969cab92019-08-05 11:13:42 +02001364 hv_store(hash, "max", strlen("max"), newSVnv(best[a].max_activation), 0); // newSVnv(target_sums[best[a].wordi]), 0);
1365 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 +02001366 hv_store(hash, "pos", strlen("pos"), newSVnv(best[a].position), 0);
Marc Kupietz969cab92019-08-05 11:13:42 +02001367 best[a].heat[5] = 0;
1368 for (i = 10; i >= 0; i--) av_push(heat, newSVnv(best[a].heat[i]));
1369 hv_store(hash, "heat", strlen("heat"), newRV_noinc((SV *)heat), 0);
1370 av_push(array, newRV_noinc((SV *)hash));
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001371 }
Marc Kupietz969cab92019-08-05 11:13:42 +02001372 hv_store(result, "syntagmatic", strlen("syntagmatic"), newRV_noinc((SV *)array), 0);
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001373 }
1374end:
Marc Kupietz969cab92019-08-05 11:13:42 +02001375 free(best);
Marc Kupietz04135302026-07-30 14:40:02 +09001376 free(target_sums);
Marc Kupietzd6a163c2026-07-30 14:48:10 +09001377 free(window_sums);
Marc Kupietz04135302026-07-30 14:40:02 +09001378 free(pt);
1379 free(wl);
1380 for (a = 0; a < MAX_THREADS; a++) {
1381 free_knn(para_nbs[a]);
1382 free_knn(syn_nbs[a]);
1383 }
Marc Kupietz969cab92019-08-05 11:13:42 +02001384 return newRV_noinc((SV *)result);
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001385}
1386
1387int dump_vecs(char *fname) {
Marc Kupietz969cab92019-08-05 11:13:42 +02001388 long i, j;
1389 FILE *f;
1390 /* if(words>100000)
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001391 words=100000;
1392*/
Marc Kupietz969cab92019-08-05 11:13:42 +02001393 if ((f = fopen(fname, "w")) == NULL) {
1394 fprintf(stderr, "cannot open %s for writing\n", fname);
1395 return (-1);
1396 }
1397 fprintf(f, "%lld %lld\n", words, size);
1398 for (i = 0; i < words; i++) {
1399 fprintf(f, "%s ", &vocab[i * max_w]);
1400 for (j = 0; j < size - 1; j++)
1401 fprintf(f, "%f ", M[i * size + j]);
1402 fprintf(f, "%f\n", M[i * size + j]);
1403 }
1404 fclose(f);
1405 return (0);
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001406}
1407
1408int dump_for_numpy(char *fname) {
Marc Kupietz969cab92019-08-05 11:13:42 +02001409 long i, j;
1410 FILE *f;
Marc Kupietzc0d41872021-02-25 16:33:22 +01001411 int max = words; // 300000;
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001412
Marc Kupietz969cab92019-08-05 11:13:42 +02001413 if ((f = fopen(fname, "w")) == NULL) {
1414 fprintf(stderr, "cannot open %s for writing\n", fname);
1415 return (-1);
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001416 }
Marc Kupietz969cab92019-08-05 11:13:42 +02001417 for (i = 0; i < max; i++) {
1418 for (j = 0; j < size - 1; j++)
1419 fprintf(f, "%f\t", M[i * size + j]);
1420 fprintf(f, "%f\n", M[i * size + j]);
1421 printf("%s\r\n", &vocab[i * max_w]);
1422 }
1423 if (merged_end > 0) {
1424 for (i = 0; i < max; i++) {
1425 for (j = 0; j < size - 1; j++)
1426 fprintf(f, "%f\t", M[(merged_end + i) * size + j]);
1427 fprintf(f, "%f\n", M[(merged_end + i) * size + j]);
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001428 printf("_%s\r\n", &vocab[i * max_w]);
1429 }
Marc Kupietz969cab92019-08-05 11:13:42 +02001430 }
1431 fclose(f);
1432 return (0);
Marc Kupietzf11d20c2019-08-02 15:42:04 +02001433}
Marc Kupietz043db152023-11-05 17:47:53 +01001434
1435unsigned long getVocabSize() {
1436 return (unsigned long) words;
1437}
Marc Kupietz6f317a92026-07-30 15:09:54 +09001438
1439/* First rank of the primary model in the merged vocabulary, 0 if no second
1440 model was merged in. mergeVectors() puts the merged in model at ranks
1441 [0, merged_end) and the primary model - the one the collocator db belongs
1442 to - at [merged_end, words). */
1443long getMergedEnd() {
1444 return (long) merged_end;
1445}