mycai 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,868 @@
1
+ /**
2
+ * Turkish Resonance AI Core - Autoregressive Spectral SLM Text Generator (v5.1)
3
+ * Uses HDC/FHRR phase context bundling and circular convolution via FFT for next-token prediction.
4
+ * Features WebAssembly (Wasm) acceleration for FFT and cosine similarity scoring.
5
+ */
6
+
7
+ import fs from 'fs';
8
+ import path from 'path';
9
+ import { fileURLToPath } from 'url';
10
+
11
+ import { Representation, HDCEngine } from './hdc.js';
12
+ import { TurkishMorphology } from './morphology.js';
13
+ import { SafetensorsParser } from './safetensors.js';
14
+ import { WeightSpectralAnalyzer } from '../experiments/weight_analyzer.js';
15
+
16
+ const __filename = fileURLToPath(import.meta.url);
17
+ const __dirname = path.dirname(__filename);
18
+
19
+ const SAFETENSORS_PATH = path.join(__dirname, '..', 'data', 'google_bert_tiny.safetensors');
20
+
21
+ // Convert real weights to complex phases
22
+ function mapToPhase(realWeights) {
23
+ const phases = new Float32Array(realWeights.length);
24
+ for (let i = 0; i < realWeights.length; i++) {
25
+ phases[i] = Math.PI * Math.tanh(realWeights[i]);
26
+ }
27
+ return phases;
28
+ }
29
+
30
+ // Complex Hadamard pointwise product: C = A * B
31
+ function complexHadamardProduct(A, B) {
32
+ const N = A.D;
33
+ const C_re = new Float32Array(N);
34
+ const C_im = new Float32Array(N);
35
+ const C_magnitude = new Float32Array(N);
36
+
37
+ for (let k = 0; k < N; k++) {
38
+ C_re[k] = A.re[k] * B.re[k] - A.im[k] * B.im[k];
39
+ C_im[k] = A.re[k] * B.im[k] + A.im[k] * B.re[k];
40
+ C_magnitude[k] = Math.sqrt(C_re[k] * C_re[k] + C_im[k] * C_im[k]);
41
+ }
42
+
43
+ return {
44
+ type: 'complex',
45
+ D: N,
46
+ re: C_re,
47
+ im: C_im,
48
+ magnitude: C_magnitude
49
+ };
50
+ }
51
+
52
+ // Suffix derivation generator for expanding vocabulary with Turkish grammatical rules
53
+ function generateDerivations(root, morphology) {
54
+ const harmony = morphology.determineVowelHarmony(root);
55
+ const lastChar = root[root.length - 1];
56
+ const isVowel = morphology.allVowels.has(lastChar);
57
+
58
+ const derivations = [root];
59
+
60
+ if (harmony === 'front') {
61
+ // Front vowels harmony (e, i, ö, ü)
62
+ derivations.push(root + 'ler');
63
+ derivations.push(root + (isVowel ? 'nin' : 'in'));
64
+ derivations.push(root + (isVowel ? 'ye' : 'e'));
65
+ derivations.push(root + (isVowel ? 'yi' : 'i'));
66
+ const loc = (lastChar === 't' || lastChar === 'k' || lastChar === 'ç' || lastChar === 'p' || lastChar === 's' || lastChar === 'ş' || lastChar === 'h' || lastChar === 'f') ? 'te' : 'de';
67
+ derivations.push(root + loc);
68
+ const abl = (lastChar === 't' || lastChar === 'k' || lastChar === 'ç' || lastChar === 'p' || lastChar === 's' || lastChar === 'ş' || lastChar === 'h' || lastChar === 'f') ? 'ten' : 'den';
69
+ derivations.push(root + abl);
70
+ derivations.push(root + (isVowel ? 'm' : 'im'));
71
+ derivations.push(root + (isVowel ? 'niz' : 'iniz'));
72
+ derivations.push(root + 'leriniz');
73
+ derivations.push(root + 'lerimizden');
74
+ } else {
75
+ // Back vowels harmony (a, ı, o, u)
76
+ derivations.push(root + 'lar');
77
+ derivations.push(root + (isVowel ? 'nın' : 'ın'));
78
+ derivations.push(root + (isVowel ? 'ya' : 'a'));
79
+ derivations.push(root + (isVowel ? 'yı' : 'ı'));
80
+ const loc = (lastChar === 't' || lastChar === 'k' || lastChar === 'ç' || lastChar === 'p' || lastChar === 's' || lastChar === 'ş' || lastChar === 'h' || lastChar === 'f') ? 'ta' : 'da';
81
+ derivations.push(root + loc);
82
+ const abl = (lastChar === 't' || lastChar === 'k' || lastChar === 'ç' || lastChar === 'p' || lastChar === 's' || lastChar === 'ş' || lastChar === 'h' || lastChar === 'f') ? 'tan' : 'dan';
83
+ derivations.push(root + abl);
84
+ derivations.push(root + (isVowel ? 'm' : 'ım'));
85
+ derivations.push(root + (isVowel ? 'nız' : 'ınız'));
86
+ derivations.push(root + 'larınız');
87
+ derivations.push(root + 'larımızdan');
88
+ }
89
+
90
+ return derivations;
91
+ }
92
+
93
+ export class SpectralSLM {
94
+ constructor(D = 4096) {
95
+ this.D = D;
96
+ this.hdc = new HDCEngine(D);
97
+ this.morphology = new TurkishMorphology();
98
+ this.analyzer = new WeightSpectralAnalyzer(D);
99
+
100
+ this.vocab = [];
101
+ this.vocabMap = new Map(); // Index map to speed up lookups
102
+ this.embeddings = new Map();
103
+ this.transitionCounts = new Map();
104
+ this.sparseWeightsSpec = null;
105
+ this.wordCounts = new Map();
106
+ this.freqIndices = [];
107
+ this.wordDerivationIndices = new Map(); // Pre-calculated derivations indices
108
+
109
+ this.wasmInstance = null;
110
+ this.wasmMemory = null;
111
+
112
+ // Dynamic pointers for vocabulary buffers inside Wasm Memory
113
+ this.vocabCosPtr = 0;
114
+ this.vocabSinPtr = 0;
115
+ this.scoresPtr = 0;
116
+
117
+ // Dynamic pointers for transition and top-k selection arrays
118
+ this.transIndicesPtr = 0;
119
+ this.transMultipliersPtr = 0;
120
+ this.topIndicesPtr = 0;
121
+ this.topScoresPtr = 0;
122
+ this.candidateIndicesPtr = 0;
123
+
124
+ this.init();
125
+ }
126
+
127
+ init() {
128
+ // 0. Load WebAssembly Module synchronously
129
+ try {
130
+ const wasmPath = path.join(__dirname, '..', 'wasm', 'spectral_core.wasm');
131
+ if (fs.existsSync(wasmPath)) {
132
+ const wasmBuffer = fs.readFileSync(wasmPath);
133
+ const imports = {
134
+ env: {
135
+ cosf: Math.cos,
136
+ sinf: Math.sin,
137
+ atan2f: Math.atan2
138
+ }
139
+ };
140
+ const wasmModule = new WebAssembly.Module(wasmBuffer);
141
+ this.wasmInstance = new WebAssembly.Instance(wasmModule, imports);
142
+ this.wasmMemory = this.wasmInstance.exports.memory;
143
+ }
144
+ } catch (e) {
145
+ console.warn("WASM Core loading failed, falling back to Pure JS:", e.message);
146
+ }
147
+
148
+ // 1. Define a rich training corpus of representative Turkish sentences
149
+ const trainingCorpus = [
150
+ "evimizden yeni çıktık",
151
+ "yeni bir kitap aldım",
152
+ "bilimsel araştırmalar yapay zeka ile hızlandı",
153
+ "güzel bir gün başladı",
154
+ "iyi bir insan olmak önemlidir",
155
+ "türkçe dil yapısı çok zengindir",
156
+ "yapay zeka insan beyni gibi çalışır",
157
+ "öğrenmek ve düşünmek zihni geliştirir",
158
+ "büyük bir adım attık",
159
+ "yeni projeler üzerinde çalışıyoruz",
160
+ "okuma alışkanlığı kazanmak önemlidir",
161
+ "bilim ve teknik dünyayı değiştiriyor",
162
+ "evimizden okula kadar yürüdük",
163
+ "yapay zeka dil modelleri üzerine kuruludur",
164
+ "yeni bir dünya bizi bekliyor",
165
+ "kitaplar en iyi arkadaştır",
166
+ "güzel bir gelecek inşa ediyoruz",
167
+ "iyi bir eğitim almak önemlidir",
168
+ "türkçe konuşmak ve yazmak çok güzel",
169
+ "bilimsel gerçekler her zaman kazanır",
170
+ "yapay sinir ağları karmaşık modellerdir",
171
+ "beyin ve zihin araştırmaları sürüyor",
172
+ "evimizden yeni bir yola çıktık",
173
+ "yeni bir başlangıç yapmak iyidir",
174
+ "bilimsel okuma yapmak zihni açar",
175
+ "okuma yapmak insanı geliştirir",
176
+ "evimizden çıktık ve okula gittik",
177
+ "yeni bir güne uyandık",
178
+ "bilim insanları yapay zeka geliştiriyor",
179
+ "türkçe dil bilgisi kuralları önemlidir",
180
+ "evimizden okula gittik yeni bir kitap aldık",
181
+
182
+ // Prompt alignment additions
183
+ "televizyonu açıp haberleri izledik",
184
+ "televizyonu açıp maçı izledim",
185
+ "arabaya binip okula gittik",
186
+ "arabaya binip yola çıktık",
187
+ "bilgisayarı kapatıp uyudum",
188
+ "bilgisayarı kapatıp yattım",
189
+
190
+ // Bilim ve Teknoloji (Science & Technology)
191
+ "yapay zeka modelleri karmaşık veri setlerini analiz eder",
192
+ "yeni yazılımlar bilgisayar performansını artırır",
193
+ "bilim insanları uzay boşluğunda yeni gezegenler keşfetti",
194
+ "biyoloji laboratuvarında hücre yapısı inceleniyor",
195
+ "fizik deneyleri yerçekimi kuvvetini ölçer",
196
+ "kimyasal bileşikler yeni maddeler oluşturur",
197
+ "yazılım mühendisleri kod yazıp sistemleri test ediyor",
198
+ "internet ağları dünya genelinde hızlı iletişim sağlar",
199
+ "teknolojik gelişmeler günlük yaşamı kolaylaştırır",
200
+ "veri tabanı yönetimi bilgi güvenliğini korur",
201
+
202
+ // Tarih ve Kültür (History & Culture)
203
+ "türk tarihi eski çağlardan günümüze kadar uzanır",
204
+ "cumhuriyet yönetimi bağımsızlık mücadelesi ile kuruldu",
205
+ "eski saraylar tarihi eserler koruma altına alındı",
206
+ "kültürel değerler nesilden nesile aktarılır",
207
+ "müzeler geçmiş dönemin izlerini sergiler",
208
+ "arkeolojik kazılar antik kentleri gün yüzüne çıkardı",
209
+ "geleneksel el sanatları kültürümüzün zenginliğini yansıtır",
210
+ "tarihi belgeler geçmişi anlamamıza ışık tutar",
211
+ "cumhuriyet bayramı her yıl coşkuyla kutlanır",
212
+ "anadolu toprakları birçok medeniyete ev sahipliği yaptı",
213
+
214
+ // Sanat ve Edebiyat (Art & Literature)
215
+ "güzel sanatlar insanın yaratıcı yönünü geliştirir",
216
+ "roman okumak farklı dünyaları keşfetmenizi sağlar",
217
+ "yeni tiyatro oyunu izleyiciden büyük ilgi gördü",
218
+ "klasik müzik zihni dinlendirir ve odaklanmayı artırır",
219
+ "ressam tuval üzerine renkli yağlı boyalar sürdü",
220
+ "şiir yazmak duyguları kelimelerle ifade etmektir",
221
+ "sinema filmleri toplumsal konuları beyaz perdeye taşır",
222
+ "kitap fuarı bu yıl binlerce okuyucuyu ağırladı",
223
+ "edebi eserler dilin estetik gücünü gösterir",
224
+ "heykeltıraş mermer bloğu sanata dönüştürdü",
225
+
226
+ // Günlük Yaşam ve Sağlık (Daily Life & Health)
227
+ "sabah erkenden kalkıp yürüyüş yapmak sağlıklıdır",
228
+ "dengeli beslenmek vücut direncini artırır",
229
+ "düzenli uyku zihinsel yorgunluğu azaltır",
230
+ "akşam yemeğinde taze sebze çorbası içtik",
231
+ "arkadaşlarımla kütüphanede ders çalıştık",
232
+ "pazardan taze meyve ve sebze aldım",
233
+ "temiz hava almak stresi azaltmaya yardımcı olur",
234
+ "bol su içmek böbrek sağlığı için önemlidir",
235
+ "hafta sonu ailemle güzel bir piknik yaptık",
236
+ "spor yapmak kas yapısını güçlendirir",
237
+
238
+ // Coğrafya ve Doğa (Geography & Nature)
239
+ "türkiye üç tarafı denizlerle çevrili bir yarımadadır",
240
+ "ormanlar havadaki karbondioksit oranını düşürür",
241
+ "ege kıyılarında zeytin ağaçları yetişir",
242
+ "akdeniz iklimi sıcak ve kurak yazlar getirir",
243
+ "karadeniz dağları gür ormanlarla kaplıdır",
244
+ "doğayı korumak gelecek nesiller için görevimizdir",
245
+ "akarsular göllere ve denizlere dökülür",
246
+ "yüksek dağ zirveleri her zaman karla kaplıdır",
247
+ "doğal kaynaklarımızı verimli kullanmalıyız",
248
+ "bahar aylarında çiçekler rengarenk açar",
249
+
250
+ // Ekonomi ve İş Dünyası (Economics & Business)
251
+ "ekonomik büyüme yeni iş imkanları yaratır",
252
+ "yatırım yapmak birikimleri değerlendirmenin yoludur",
253
+ "ticaret hacmi ülkeler arasındaki ilişkileri güçlendirir",
254
+ "üretim kapasitesi teknolojik yatırımlarla arttı",
255
+ "piyasa analizi doğru kararlar almayı kolaylaştırır",
256
+ "müşteri memnuniyeti şirketlerin başarısını belirler",
257
+ "finansal okuryazarlık bütçe yönetimini sağlar",
258
+ "yeni girişimler sektöre canlılık kazandırır",
259
+ "ithalat ve ihracat dengesi ekonomik istikrarı korur",
260
+ "banka işlemleri internet üzerinden hızlıca yapılır",
261
+
262
+ // Geniş Semantik Kapsam (Broad Domain Expansion)
263
+ "yazılım mühendisleri yapay zeka modelleri üzerine araştırma yapıyor",
264
+ "bilgi teknolojileri ve veri analizi iş süreçlerini kolaylaştırır",
265
+ "sağlıklı beslenme ve spor yapmak yaşam kalitesini artırır",
266
+ "türk tarihi ve kültürü dünya genelinde büyük ilgi görüyor",
267
+ "doğal yaşamı ve çevreyi korumak hepimizin sorumluluğundadır",
268
+ "bilimsel makaleler ve araştırmalar yeni teknolojilere kapı açar",
269
+ "sanat ve edebiyat toplumun kültürel zenginliğini besler ve geliştirir",
270
+ "küresel ekonomik dengeler ithalat ve ihracat oranlarıyla değişir",
271
+ "eğitim sistemi yeni nesillerin geleceğini ve başarısını belirler",
272
+ "bilgisayar ağları veri güvenliği ve hızlı bilgi akışı sağlar"
273
+ ];
274
+
275
+ // Load Vocabulary from tr_corpus_embed.js and filter strictly using regex + training words
276
+ let initialVocab = [];
277
+ try {
278
+ const corpusPath = path.join(__dirname, '..', 'tr_corpus_embed.js');
279
+ const fileContent = fs.readFileSync(corpusPath, 'utf8');
280
+ const match = fileContent.match(/const TR_CORPUS_ROOTS\s*=\s*(\[[\s\S]*?\]);/);
281
+ if (match) {
282
+ initialVocab = JSON.parse(match[1]);
283
+ }
284
+ } catch (e) {
285
+ // Ignored
286
+ }
287
+
288
+ const turkishRegex = /^[a-zçgğıoöşuüâîû]+$/;
289
+ const cleanCorpusWords = new Set([
290
+ 'ev', 'yeni', 'bir', 'kitap', 'okul', 'çıktık', 'aldım', 'güzel', 'iyi',
291
+ 'yapay', 'zeka', 'bilimsel', 'okuma', 'öğrenmek', 'dil', 'türkçe', 'insan',
292
+ 'olmak', 'önemlidir', 'geliştirir', 'değiştiriyor', 'yürüdük', 'gittik', 'yola',
293
+ 'başlangıç', 'güne', 'uyandık', 'kuruludur',
294
+ 'televizyonu', 'açıp', 'izledik', 'izledim', 'arabaya', 'binip', 'bilgisayarı',
295
+ 'kapatıp', 'uyudum', 'yattım'
296
+ ]);
297
+
298
+ // Compute corpus counts for Zipf thresholding and Transition Whitening
299
+ this.wordCounts.clear();
300
+ for (const sentence of trainingCorpus) {
301
+ sentence.split(/\s+/).forEach(w => {
302
+ const clean = w.trim().toLowerCase().replace(/[^a-zçgğıoöşuüâîû]/g, '');
303
+ if (clean.length >= 2 && turkishRegex.test(clean)) {
304
+ this.wordCounts.set(clean, (this.wordCounts.get(clean) || 0) + 1);
305
+ }
306
+ });
307
+ }
308
+
309
+ // Zipf threshold: prune words occurring < 2 times (unless they are core query words)
310
+ for (const [w, count] of this.wordCounts.entries()) {
311
+ if (count >= 2) {
312
+ cleanCorpusWords.add(w);
313
+ }
314
+ }
315
+
316
+ // Genişletme Hamlesi: 5000 kökün tamamından morfolojik varyasyon türeterek 50.000+ kelime üret
317
+ for (const w of initialVocab) {
318
+ const clean = w.trim().toLowerCase().replace(/[^a-zçgğıoöşuüâîû]/g, '');
319
+ if (clean.length >= 2 && turkishRegex.test(clean)) {
320
+ const derivations = generateDerivations(clean, this.morphology);
321
+ for (const deriv of derivations) {
322
+ cleanCorpusWords.add(deriv);
323
+ }
324
+ }
325
+ }
326
+
327
+ this.vocab = ['<unk>', ...Array.from(cleanCorpusWords)];
328
+ this.vocabMap.clear();
329
+ for (let i = 0; i < this.vocab.length; i++) {
330
+ this.vocabMap.set(this.vocab[i], i);
331
+ }
332
+
333
+ // Pre-calculate top 15 frequent words indices to restrict candidates search space to absolute minimum
334
+ this.freqIndices = this.vocab
335
+ .map((w, idx) => ({ w, idx }))
336
+ .sort((a, b) => (this.wordCounts.get(b.w) || 0) - (this.wordCounts.get(a.w) || 0))
337
+ .slice(0, 15)
338
+ .map(x => x.idx);
339
+
340
+ // Pre-calculate morphological suffix derivations indices map to avoid string manipulations in loop
341
+ this.wordDerivationIndices.clear();
342
+ for (const root of this.vocab) {
343
+ const derivations = generateDerivations(root, this.morphology);
344
+ const indices = [];
345
+ for (const deriv of derivations) {
346
+ const idx = this.vocabMap.get(deriv);
347
+ if (idx !== undefined) {
348
+ indices.push(idx);
349
+ }
350
+ }
351
+ this.wordDerivationIndices.set(root, indices);
352
+ }
353
+
354
+ // 2. Generate deterministic FHRR embeddings for all vocabulary words
355
+ for (const word of this.vocab) {
356
+ const emb = this.hdc.generateSeeded('complex', word);
357
+ // Pre-convert to Float32Array for ultra-fast direct memcpy/memmove in V8
358
+ emb.values = new Float32Array(emb.values);
359
+ const cosVals = new Float32Array(this.D);
360
+ const sinVals = new Float32Array(this.D);
361
+ for (let i = 0; i < this.D; i++) {
362
+ cosVals[i] = Math.cos(emb.values[i]);
363
+ sinVals[i] = Math.sin(emb.values[i]);
364
+ }
365
+ this.embeddings.set(word, { emb, cosVals, sinVals });
366
+ }
367
+
368
+ // 2b. Write vocabulary embeddings to Wasm memory using dynamic malloc allocator
369
+ if (this.wasmInstance) {
370
+ const vocabSize = this.vocab.length;
371
+ const dSize = this.D;
372
+
373
+ // Dynamic allocation in Wasm memory
374
+ this.vocabCosPtr = this.wasmInstance.exports.malloc(vocabSize * dSize * 4);
375
+ this.vocabSinPtr = this.wasmInstance.exports.malloc(vocabSize * dSize * 4);
376
+ this.scoresPtr = this.wasmInstance.exports.malloc(vocabSize * 4);
377
+
378
+ this.transIndicesPtr = this.wasmInstance.exports.malloc(64 * 4);
379
+ this.transMultipliersPtr = this.wasmInstance.exports.malloc(64 * 4);
380
+ this.topIndicesPtr = this.wasmInstance.exports.malloc(5 * 4);
381
+ this.topScoresPtr = this.wasmInstance.exports.malloc(5 * 4);
382
+ this.candidateIndicesPtr = this.wasmInstance.exports.malloc(1024 * 4); // Space for up to 1024 candidates
383
+
384
+ if (this.vocabCosPtr === 0 || this.vocabSinPtr === 0 || this.scoresPtr === 0 ||
385
+ this.transIndicesPtr === 0 || this.transMultipliersPtr === 0 ||
386
+ this.topIndicesPtr === 0 || this.topScoresPtr === 0 || this.candidateIndicesPtr === 0) {
387
+ console.error("WASM Dynamic Allocation failed! Falling back to Pure JS.");
388
+ this.wasmInstance = null;
389
+ } else {
390
+ // Fresh views of the buffer (handles detaching after grow)
391
+ const memoryBuffer = this.wasmMemory.buffer;
392
+ const vocabCosView = new Float32Array(memoryBuffer, this.vocabCosPtr, vocabSize * dSize);
393
+ const vocabSinView = new Float32Array(memoryBuffer, this.vocabSinPtr, vocabSize * dSize);
394
+
395
+ for (let v = 0; v < vocabSize; v++) {
396
+ const word = this.vocab[v];
397
+ const data = this.embeddings.get(word);
398
+ vocabCosView.set(data.cosVals, v * dSize);
399
+ vocabSinView.set(data.sinVals, v * dSize);
400
+ }
401
+ }
402
+ }
403
+
404
+ // 3. Extract Bi-gram Transitions and encode to phase differences using TF-IDF / Transition Whitening
405
+ const re = new Float32Array(this.D);
406
+ const im = new Float32Array(this.D);
407
+ this.transitionCounts = new Map();
408
+ let transitionRepsCount = 0;
409
+
410
+ for (const sentence of trainingCorpus) {
411
+ const words = sentence.split(/\s+/).map(w => w.trim().toLowerCase().replace(/[^a-zçgğıoöşuüâîû]/g, '')).filter(w => w.length >= 2);
412
+ for (let i = 0; i < words.length - 1; i++) {
413
+ const w1 = words[i];
414
+ const w2 = words[i + 1];
415
+
416
+ // Populate transition frequencies map
417
+ if (!this.transitionCounts.has(w1)) {
418
+ this.transitionCounts.set(w1, new Map());
419
+ }
420
+ const m = this.transitionCounts.get(w1);
421
+ m.set(w2, (m.get(w2) || 0) + 1);
422
+
423
+ const data1 = this.embeddings.get(w1);
424
+ const data2 = this.embeddings.get(w2);
425
+
426
+ if (data1 && data2) {
427
+ // Whitening factor: 1 / sqrt(freq(w_j))
428
+ const freq = this.wordCounts.get(w2) || 1;
429
+ const weight = 1.0 / Math.sqrt(freq);
430
+
431
+ for (let j = 0; j < this.D; j++) {
432
+ let diff = data2.emb.values[j] - data1.emb.values[j];
433
+ if (diff < 0) diff += 2 * Math.PI;
434
+ diff = diff % (2 * Math.PI);
435
+
436
+ re[j] += weight * Math.cos(diff);
437
+ im[j] += weight * Math.sin(diff);
438
+ }
439
+ transitionRepsCount++;
440
+ }
441
+ }
442
+ }
443
+
444
+ // Bundle transition phase differences
445
+ let wRep;
446
+ if (transitionRepsCount > 0) {
447
+ const bundleVals = new Float32Array(this.D);
448
+ for (let j = 0; j < this.D; j++) {
449
+ let v = Math.atan2(im[j], re[j]);
450
+ if (v < 0) v += 2 * Math.PI;
451
+ bundleVals[j] = v;
452
+ }
453
+ wRep = new Representation('complex', bundleVals, this.D);
454
+ } else {
455
+ wRep = this.hdc.generateSeeded('complex', 'fallback_transitions');
456
+ }
457
+
458
+ // Convert weights to spectral domain and apply 20% sparse pruning
459
+ const wSpec = this.analyzer.spectralEngine.spectralTransform(wRep);
460
+ this.sparseWeightsSpec = this.analyzer.compressSpectrum(wSpec, 0.20);
461
+
462
+ // Write sparse weights spectrum to Wasm memory once during init
463
+ if (this.wasmInstance) {
464
+ const specRePtr = this.wasmInstance.exports.get_transition_spec_re_ptr();
465
+ const specImPtr = this.wasmInstance.exports.get_transition_spec_im_ptr();
466
+ const memoryBuffer = this.wasmMemory.buffer;
467
+
468
+ const specReView = new Float32Array(memoryBuffer, specRePtr, this.D);
469
+ const specImView = new Float32Array(memoryBuffer, specImPtr, this.D);
470
+
471
+ specReView.set(this.sparseWeightsSpec.re);
472
+ specImView.set(this.sparseWeightsSpec.im);
473
+ }
474
+ }
475
+
476
+ // HDC Context Encoding: for Bi-gram transition retrieval, X_context is simply the last token representation
477
+ encodeContext(tokens) {
478
+ if (!tokens || tokens.length === 0) {
479
+ return this.hdc.generateSeeded('complex', 'empty_context');
480
+ }
481
+
482
+ const lastToken = tokens[tokens.length - 1];
483
+ let data = this.embeddings.get(lastToken);
484
+ if (!data) {
485
+ data = this.embeddings.get('<unk>');
486
+ if (!data) {
487
+ return this.hdc.generateSeeded('complex', 'empty_context');
488
+ }
489
+ }
490
+
491
+ return data.emb;
492
+ }
493
+
494
+ // Helper to combine two phase representations linearly in complex plane
495
+ combineRepresentations(repA, weightA, repB, weightB) {
496
+ const vals = new Float32Array(this.D);
497
+ for (let i = 0; i < this.D; i++) {
498
+ const re = weightA * Math.cos(repA.values[i]) + weightB * Math.cos(repB.values[i]);
499
+ const im = weightA * Math.sin(repA.values[i]) + weightB * Math.sin(repB.values[i]);
500
+ let v = Math.atan2(im, re);
501
+ if (v < 0) v += 2 * Math.PI;
502
+ vals[i] = v;
503
+ }
504
+ return new Representation('complex', vals, this.D);
505
+ }
506
+
507
+ // Generate next token resonance scores and sample the selected word
508
+ generateNextToken(tokens, temperature = 0.7, promptLength = 0) {
509
+ if (this.wasmInstance) {
510
+ const memoryBuffer = this.wasmMemory.buffer;
511
+ const contextPtr = this.wasmInstance.exports.get_context_angles_ptr();
512
+ const contextView = new Float32Array(memoryBuffer, contextPtr, this.D);
513
+
514
+ // 1. Context combination completely offloaded using zero-copy indices method
515
+ if (promptLength > 0 && tokens.length > promptLength) {
516
+ let promptIdx = this.vocabMap.get(tokens[promptLength - 1]);
517
+ if (promptIdx === undefined) promptIdx = 0;
518
+ let generatedIdx = this.vocabMap.get(tokens[tokens.length - 1]);
519
+ if (generatedIdx === undefined) generatedIdx = 0;
520
+
521
+ // Zero-copy combination natively in Wasm directly reading from vocabCos/vocabSin arrays
522
+ this.wasmInstance.exports.combine_phases_by_indices(
523
+ promptIdx,
524
+ 0.4,
525
+ generatedIdx,
526
+ 0.6,
527
+ this.vocabCosPtr,
528
+ this.vocabSinPtr,
529
+ contextPtr
530
+ );
531
+ } else {
532
+ const lastToken = tokens[tokens.length - 1];
533
+ let data = this.embeddings.get(lastToken);
534
+ if (!data) data = this.embeddings.get('<unk>');
535
+ contextView.set(data.emb.values);
536
+ }
537
+
538
+ // 2. Perform forward prediction in Wasm
539
+ this.wasmInstance.exports.spectral_predict();
540
+
541
+ // 3. Dynamic Candidate Restrictive Selection: directly write to Wasm memory avoiding Set creation
542
+ const candidateIndicesView = new Int32Array(memoryBuffer, this.candidateIndicesPtr, 1024);
543
+
544
+ // Initialize candidates list with the pre-calculated 15 frequent words indices
545
+ const freqLen = this.freqIndices.length;
546
+ for (let i = 0; i < freqLen; i++) {
547
+ candidateIndicesView[i] = this.freqIndices[i];
548
+ }
549
+ let numCandidates = freqLen;
550
+
551
+ // Add all prompt word indices
552
+ for (let i = 0; i < promptLength; i++) {
553
+ const idx = this.vocabMap.get(tokens[i]);
554
+ if (idx !== undefined && numCandidates < 1024) {
555
+ candidateIndicesView[numCandidates++] = idx;
556
+ }
557
+ }
558
+
559
+ // Map transitions and write indices and multipliers to Wasm Memory
560
+ const lastToken = tokens[tokens.length - 1];
561
+ let numTransitions = -1; // -1 indicates no lastToken (first word)
562
+
563
+ if (lastToken) {
564
+ const m = this.transitionCounts.get(lastToken);
565
+ if (!m) {
566
+ numTransitions = 0; // 0 indicates lastToken exists but has no transitions recorded
567
+ } else {
568
+ const transIndices = new Int32Array(memoryBuffer, this.transIndicesPtr, m.size);
569
+ const transMultipliers = new Float32Array(memoryBuffer, this.transMultipliersPtr, m.size);
570
+
571
+ let idx = 0;
572
+ for (const [nextWord, count] of m.entries()) {
573
+ const wordIdx = this.vocabMap.get(nextWord);
574
+ if (wordIdx !== undefined) {
575
+ transIndices[idx] = wordIdx;
576
+ transMultipliers[idx] = 1.5 + count * 2.0;
577
+ idx++;
578
+
579
+ // Add transitions to candidates pool
580
+ if (numCandidates < 1024) {
581
+ candidateIndicesView[numCandidates++] = wordIdx;
582
+ }
583
+
584
+ // Add pre-calculated suffix derivations of transition words to candidate pool (O(1) lookup!)
585
+ const derivationsIndices = this.wordDerivationIndices.get(nextWord);
586
+ if (derivationsIndices) {
587
+ const derivLen = derivationsIndices.length;
588
+ for (let d = 0; d < derivLen; d++) {
589
+ if (numCandidates < 1024) {
590
+ candidateIndicesView[numCandidates++] = derivationsIndices[d];
591
+ }
592
+ }
593
+ }
594
+ }
595
+ }
596
+ numTransitions = idx;
597
+ }
598
+ }
599
+
600
+ // 4. Compute similarities and select top-5 candidates ONLY from the active candidate pool (blazing fast ~0.15ms)
601
+ this.wasmInstance.exports.compute_similarity_for_candidates(
602
+ this.candidateIndicesPtr,
603
+ numCandidates,
604
+ this.vocabCosPtr,
605
+ this.vocabSinPtr,
606
+ this.transIndicesPtr,
607
+ this.transMultipliersPtr,
608
+ numTransitions,
609
+ this.topIndicesPtr,
610
+ this.topScoresPtr
611
+ );
612
+
613
+ // 5. Read top-5 results from Wasm memory
614
+ const topIndicesView = new Int32Array(this.wasmMemory.buffer, this.topIndicesPtr, 5);
615
+ const topScoresView = new Float32Array(this.wasmMemory.buffer, this.topScoresPtr, 5);
616
+
617
+ const candidates = [];
618
+ const lastHarmony = lastToken ? this.morphology.determineVowelHarmony(lastToken) : null;
619
+ const recentK = tokens.slice(-4);
620
+
621
+ for (let i = 0; i < 5; i++) {
622
+ const wordIdx = topIndicesView[i];
623
+ if (wordIdx === -1) continue;
624
+
625
+ const word = this.vocab[wordIdx];
626
+ let score = topScoresView[i];
627
+
628
+ // Apply vowel harmony, repetition penalties, and dynamic n-gram blockers only on these top 5 candidates
629
+ if (tokens.length >= 1) {
630
+ const last1 = tokens[tokens.length - 1];
631
+ for (let idx = 0; idx < tokens.length - 1; idx++) {
632
+ if (tokens[idx] === last1 && tokens[idx + 1] === word) {
633
+ score = 0.0;
634
+ break;
635
+ }
636
+ }
637
+ }
638
+ if (tokens.length >= 2) {
639
+ const last2 = tokens[tokens.length - 2];
640
+ const last1 = tokens[tokens.length - 1];
641
+ for (let idx = 0; idx < tokens.length - 2; idx++) {
642
+ if (tokens[idx] === last2 && tokens[idx + 1] === last1 && tokens[idx + 2] === word) {
643
+ score = 0.0;
644
+ break;
645
+ }
646
+ }
647
+ }
648
+
649
+ if (recentK.includes(word)) {
650
+ score *= 0.1;
651
+ }
652
+
653
+ if (lastHarmony && this.morphology.suffixFeatures.hasOwnProperty(word)) {
654
+ const suffixHarmony = this.morphology.determineVowelHarmony(word);
655
+ if (suffixHarmony !== lastHarmony) {
656
+ score = 0.0;
657
+ }
658
+ } else if (lastHarmony) {
659
+ const candidateHarmony = this.morphology.determineVowelHarmony(word);
660
+ if (candidateHarmony === lastHarmony) {
661
+ score *= 1.15;
662
+ }
663
+ }
664
+
665
+ candidates.push({ word, score });
666
+ }
667
+
668
+ // Re-sort the final 5 candidates after penalties
669
+ candidates.sort((a, b) => b.score - a.score);
670
+
671
+ // Temperature Softmax selection
672
+ if (temperature <= 0.0) {
673
+ return candidates[0];
674
+ }
675
+
676
+ const expScores = candidates.map(c => Math.exp(c.score / temperature));
677
+ const totalExp = expScores.reduce((sum, val) => sum + val, 0.0);
678
+
679
+ let rand = Math.random() * totalExp;
680
+ for (let i = 0; i < candidates.length; i++) {
681
+ rand -= expScores[i];
682
+ if (rand <= 0.0) {
683
+ return candidates[i];
684
+ }
685
+ }
686
+
687
+ return candidates[0];
688
+
689
+ } else {
690
+ // Pure JS Fallback
691
+ let contextVec;
692
+ if (promptLength > 0 && tokens.length > promptLength) {
693
+ const xPrompt = this.encodeContext(tokens.slice(0, promptLength));
694
+ const xGenerated = this.encodeContext([tokens[tokens.length - 1]]);
695
+ contextVec = this.combineRepresentations(xPrompt, 0.4, xGenerated, 0.6);
696
+ } else {
697
+ contextVec = this.encodeContext(tokens);
698
+ }
699
+
700
+ // 1. Spectral Feed-Forward inference: O(N log N) Circular Convolution
701
+ const contextSpec = this.analyzer.spectralEngine.spectralTransform(contextVec);
702
+ const productSpec = complexHadamardProduct(this.sparseWeightsSpec, contextSpec);
703
+ const reconstructedRep = this.analyzer.reconstructBlock(productSpec);
704
+ const yQuery = reconstructedRep.values; // predicted FHRR phase vector
705
+
706
+ // 2. Scan vocabulary and compute phase cosine resonance similarity using precomputed trig values
707
+ const candidates = [];
708
+ const lastToken = tokens[tokens.length - 1];
709
+ const lastHarmony = lastToken ? this.morphology.determineVowelHarmony(lastToken) : null;
710
+
711
+ // Pre-compute cos and sin of yQuery once
712
+ const cosY = new Float32Array(this.D);
713
+ const sinY = new Float32Array(this.D);
714
+ for (let i = 0; i < this.D; i++) {
715
+ cosY[i] = Math.cos(yQuery[i]);
716
+ sinY[i] = Math.sin(yQuery[i]);
717
+ }
718
+
719
+ for (const [word, data] of this.embeddings.entries()) {
720
+ let sumCos = 0.0;
721
+ const cosEmb = data.cosVals;
722
+ const sinEmb = data.sinVals;
723
+ for (let i = 0; i < this.D; i++) {
724
+ sumCos += cosY[i] * cosEmb[i] + sinY[i] * sinEmb[i];
725
+ }
726
+ let score = sumCos / this.D;
727
+
728
+ score = Math.max(0.0001, (score + 1.0) / 2.0);
729
+
730
+ if (lastToken) {
731
+ const m = this.transitionCounts.get(lastToken);
732
+ if (m) {
733
+ const count = m.get(word) || 0;
734
+ if (count > 0) {
735
+ score *= (1.5 + count * 2.0);
736
+ } else {
737
+ score *= 0.1;
738
+ }
739
+ } else {
740
+ score *= 0.5;
741
+ }
742
+ }
743
+
744
+ if (tokens.length >= 1) {
745
+ const last1 = tokens[tokens.length - 1];
746
+ for (let i = 0; i < tokens.length - 1; i++) {
747
+ if (tokens[i] === last1 && tokens[i + 1] === word) {
748
+ score = 0.0;
749
+ break;
750
+ }
751
+ }
752
+ }
753
+ if (tokens.length >= 2) {
754
+ const last2 = tokens[tokens.length - 2];
755
+ const last1 = tokens[tokens.length - 1];
756
+ for (let i = 0; i < tokens.length - 2; i++) {
757
+ if (tokens[i] === last2 && tokens[i + 1] === last1 && tokens[i + 2] === word) {
758
+ score = 0.0;
759
+ break;
760
+ }
761
+ }
762
+ }
763
+
764
+ const recentK = tokens.slice(-4);
765
+ if (recentK.includes(word)) {
766
+ score *= 0.1;
767
+ }
768
+
769
+ if (lastHarmony && this.morphology.suffixFeatures.hasOwnProperty(word)) {
770
+ const suffixHarmony = this.morphology.determineVowelHarmony(word);
771
+ if (suffixHarmony !== lastHarmony) {
772
+ score = 0.0;
773
+ }
774
+ } else if (lastHarmony) {
775
+ const candidateHarmony = this.morphology.determineVowelHarmony(word);
776
+ if (candidateHarmony === lastHarmony) {
777
+ score *= 1.15;
778
+ }
779
+ }
780
+
781
+ candidates.push({ word, score });
782
+ }
783
+
784
+ candidates.sort((a, b) => b.score - a.score);
785
+
786
+ if (temperature <= 0.0) {
787
+ return candidates[0];
788
+ }
789
+
790
+ const k = 5;
791
+ const topKCandidates = candidates.slice(0, k);
792
+
793
+ const expScores = topKCandidates.map(c => Math.exp(c.score / temperature));
794
+ const totalExp = expScores.reduce((sum, val) => sum + val, 0.0);
795
+
796
+ let rand = Math.random() * totalExp;
797
+ for (let i = 0; i < topKCandidates.length; i++) {
798
+ rand -= expScores[i];
799
+ if (rand <= 0.0) {
800
+ return topKCandidates[i];
801
+ }
802
+ }
803
+
804
+ return topKCandidates[0];
805
+ }
806
+ }
807
+
808
+ // Autoregressive text generator
809
+ generateText(prompt, maxLength = 10, temperature = 0.7) {
810
+ const startTime = performance.now();
811
+
812
+ // Normalize and tokenize prompt
813
+ const cleanPrompt = this.morphology.normalize(prompt);
814
+ const tokens = cleanPrompt.split(/\s+/).filter(Boolean);
815
+
816
+ if (tokens.length === 0) {
817
+ tokens.push('ev'); // Default fallback token
818
+ }
819
+
820
+ const promptLength = tokens.length;
821
+ const steps = [];
822
+ let stopReason = 'length';
823
+
824
+ for (let step = 0; step < maxLength; step++) {
825
+ const stepStart = performance.now();
826
+ const nextObj = this.generateNextToken(tokens, temperature, promptLength);
827
+ const stepEnd = performance.now();
828
+
829
+ tokens.push(nextObj.word);
830
+ steps.push({
831
+ word: nextObj.word,
832
+ score: nextObj.score,
833
+ latencyMs: stepEnd - stepStart
834
+ });
835
+
836
+ // Stop early if EOS/predicate word or period is generated
837
+ const word = nextObj.word;
838
+ if (word === '.' || word.endsWith('.') ||
839
+ word === 'gittik' || word === 'aldım' || word === 'aldık' || word === 'geliştirir' ||
840
+ word === 'uyandık' || word === 'kuruludur' || word === 'çıktık' ||
841
+ word === 'izledim' || word === 'izledik' || word === 'uyudum' ||
842
+ word === 'yattım' || word === 'gittim' || word === 'yaptım' ||
843
+ word === 'öğrendim') {
844
+ stopReason = 'stop';
845
+ break;
846
+ }
847
+
848
+ // Stop if duplicate sequence is detected
849
+ if (tokens.slice(-3).every((val, i, arr) => val === arr[0]) && tokens.length > promptLength + 2) {
850
+ stopReason = 'stop';
851
+ break;
852
+ }
853
+ }
854
+
855
+ const totalLatency = performance.now() - startTime;
856
+ const generatedText = tokens.join(' ');
857
+
858
+ return {
859
+ prompt,
860
+ generatedText,
861
+ newTokens: tokens.slice(promptLength),
862
+ steps,
863
+ totalLatencyMs: totalLatency,
864
+ finishReason: stopReason,
865
+ vocabSize: this.vocab.length
866
+ };
867
+ }
868
+ }