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,357 @@
1
+ /**
2
+ * Turkish Morphology Module
3
+ * Handles normalization, vowel harmony analysis, suffix splitting, and feature extraction.
4
+ */
5
+ export class TurkishMorphology {
6
+ constructor(roots = []) {
7
+ this.onVowels = new Set(['e', 'i', 'ö', 'ü']);
8
+ this.backVowels = new Set(['a', 'ı', 'o', 'u']);
9
+ this.allVowels = new Set(['a', 'e', 'ı', 'i', 'o', 'ö', 'u', 'ü']);
10
+
11
+ this.customRoots = roots;
12
+
13
+ // Suffix feature dictionary mapping
14
+ this.suffixFeatures = {
15
+ // Plural
16
+ 'ler': { number: 'plural' },
17
+ 'lar': { number: 'plural' },
18
+
19
+ // Cases
20
+ 'den': { case: 'ablative' },
21
+ 'dan': { case: 'ablative' },
22
+ 'ten': { case: 'ablative' },
23
+ 'tan': { case: 'ablative' },
24
+ 'de': { case: 'locative' },
25
+ 'da': { case: 'locative' },
26
+ 'te': { case: 'locative' },
27
+ 'ta': { case: 'locative' },
28
+ 'e': { case: 'dative' },
29
+ 'a': { case: 'dative' },
30
+ 'in': { case: 'genitive' },
31
+ 'ın': { case: 'genitive' },
32
+ 'un': { case: 'genitive' },
33
+ 'ün': { case: 'genitive' },
34
+
35
+ // Possessions
36
+ 'im': { possession: '1sg' },
37
+ 'ım': { possession: '1sg' },
38
+ 'um': { possession: '1sg' },
39
+ 'üm': { possession: '1sg' },
40
+ 'in': { possession: '2sg' }, // overlaps with genitive, handled dynamically
41
+ 'ın': { possession: '2sg' },
42
+ 'un': { possession: '2sg' },
43
+ 'ün': { possession: '2sg' },
44
+ 'i': { possession: '3sg' },
45
+ 'ı': { possession: '3sg' },
46
+ 'u': { possession: '3sg' },
47
+ 'ü': { possession: '3sg' },
48
+ 'imiz': { possession: '1pl' },
49
+ 'ımız': { possession: '1pl' },
50
+ 'umuz': { possession: '1pl' },
51
+ 'ümüz': { possession: '1pl' },
52
+ 'iniz': { possession: '2pl' },
53
+ 'ınız': { possession: '2pl' },
54
+ 'ünüz': { possession: '2pl' },
55
+ 'unuz': { possession: '2pl' },
56
+ 'leri': { possession: '3pl' },
57
+ 'ları': { possession: '3pl' },
58
+
59
+ // Tense/Aspect/Mood
60
+ 'iyor': { tense: 'present' },
61
+ 'ıyor': { tense: 'present' },
62
+ 'uyor': { tense: 'present' },
63
+ 'üyor': { tense: 'present' },
64
+ 'ecek': { tense: 'future' },
65
+ 'acak': { tense: 'future' },
66
+ 'miş': { tense: 'narrative_past' },
67
+ 'mış': { tense: 'narrative_past' },
68
+ 'müş': { tense: 'narrative_past' },
69
+ 'muş': { tense: 'narrative_past' },
70
+ 'di': { tense: 'past' },
71
+ 'dı': { tense: 'past' },
72
+ 'du': { tense: 'past' },
73
+ 'dü': { tense: 'past' },
74
+ 'ti': { tense: 'past' },
75
+ 'tı': { tense: 'past' },
76
+ 'tu': { tense: 'past' },
77
+ 'tü': { tense: 'past' },
78
+
79
+ // Infinitives and derivations
80
+ 'mek': { aspect: 'infinitive' },
81
+ 'mak': { aspect: 'infinitive' },
82
+ 'lik': { derivation: 'noun' },
83
+ 'lık': { derivation: 'noun' },
84
+ 'luk': { derivation: 'noun' },
85
+ 'lük': { derivation: 'noun' },
86
+ 'li': { derivation: 'with' },
87
+ 'lı': { derivation: 'with' },
88
+ 'lu': { derivation: 'with' },
89
+ 'lü': { derivation: 'with' },
90
+ 'siz': { derivation: 'without' },
91
+ 'sız': { derivation: 'without' },
92
+ 'suz': { derivation: 'without' },
93
+ 'süz': { derivation: 'without' },
94
+ 'ce': { case: 'equative' },
95
+ 'ca': { case: 'equative' },
96
+ 'ça': { case: 'equative' },
97
+ 'çe': { case: 'equative' },
98
+ // Copula (Assertive)
99
+ 'dir': { copula: 'assertive' },
100
+ 'dır': { copula: 'assertive' },
101
+ 'dur': { copula: 'assertive' },
102
+ 'dür': { copula: 'assertive' },
103
+ 'tir': { copula: 'assertive' },
104
+ 'tır': { copula: 'assertive' },
105
+ 'tur': { copula: 'assertive' },
106
+ 'tür': { copula: 'assertive' },
107
+ // Relative Modifier
108
+ 'ki': { pronoun: 'relative' },
109
+ 'kiler': { pronoun: 'relative_plural' },
110
+ // Adverbial / Manner
111
+ 'cesine': { aspect: 'manner' },
112
+ 'casına': { aspect: 'manner' },
113
+ 'ken': { adverb: 'temporal' },
114
+ // Moods
115
+ 'meli': { mood: 'necessity' },
116
+ 'malı': { mood: 'necessity' },
117
+ 'se': { mood: 'conditional' },
118
+ 'sa': { mood: 'conditional' }
119
+ };
120
+
121
+ // Known suffixes ordered by length descending for greedy matching
122
+ this.suffixesKnown = Object.keys(this.suffixFeatures).sort((a, b) => b.length - a.length);
123
+ }
124
+
125
+ // Allow dynamic roots fallback
126
+ get roots() {
127
+ if (this.customRoots && this.customRoots.length > 0) {
128
+ return this.customRoots;
129
+ }
130
+ if (typeof globalThis !== 'undefined' && globalThis.TR_CORPUS_ROOTS) {
131
+ return globalThis.TR_CORPUS_ROOTS;
132
+ }
133
+ // Minimal fallback root list
134
+ return [
135
+ // Genel yaygın kökler
136
+ 'ev', 'göl', 'araba', 'kitap', 'el', 'baş', 'git', 'gel', 'yap', 'al', 'ver', 'gör',
137
+ 'bilgisayar', 'bil', 'öğren', 'anla', 'düşün', 'büyük', 'küçük', 'güzel', 'iyi', 'kötü',
138
+ 'yapay', 'zeka', 'beyin', 'sinir', 'dil', 'kelime', 'cümle', 'türkçe', 'bilim', 'teknik',
139
+ 'okul', 'öğrenci', 'öğretmen', 'yazılım', 'donanım', 'renk',
140
+ // Vücut / anatomi
141
+ 'ayak', 'göz', 'kulak', 'diz', 'omuz', 'karın', 'göğüs', 'sırt', 'boyun', 'kol',
142
+ 'bacak', 'parmak', 'bilek', 'topuk', 'alın', 'bel', 'diş', 'burun', 'ağız', 'ciğer',
143
+ 'kalp', 'akciğer', 'mide', 'böbrek', 'karaciğer', 'damar',
144
+ // Tıbbi terimler
145
+ 'hasta', 'doktor', 'hemşire', 'ilaç', 'doz', 'tanı', 'tedavi', 'ameliyat',
146
+ 'hastalık', 'ağrı', 'şişlik', 'ateş', 'nabız', 'tansiyon', 'kan', 'idrar',
147
+ 'reçete', 'aşı', 'virüs', 'enfeksiyon', 'yara', 'kırık', 'ödem', 'alerji',
148
+ 'şeker', 'diyabet', 'gebelik', 'doğum', 'ölçüm', 'muayene', 'rapor', 'tahlil',
149
+ // Genel isimler
150
+ 'yol', 'su', 'ekmek', 'para', 'iş', 'gün', 'yıl', 'saat', 'hafta', 'ay',
151
+ 'yer', 'şehir', 'ülke', 'köy', 'insan', 'kadın', 'erkek', 'çocuk', 'anne', 'baba',
152
+ 'ad', 'isim', 'aile', 'soru', 'cevap', 'kayıt', 'bilgi', 'durum', 'sonuç',
153
+ // Sıfatlar ve zarflar
154
+ 'yeni', 'eski', 'uzun', 'kısa', 'sık', 'az', 'çok', 'son', 'ilk', 'her',
155
+ // Fiiller (ek kökler)
156
+ 'oku', 'yaz', 'bak', 'çalış', 'ye', 'iç', 'uyu', 'kalk', 'otur', 'koş',
157
+ 'sor', 'söyle', 'dinle', 'bekle', 'başla', 'bitir', 'aç', 'kapat', 'getir', 'götür',
158
+ 'koy', 'çıkar', 'kaydet', 'güncelle', 'kontrol'
159
+ ];
160
+ }
161
+
162
+ determineVowelHarmony(root) {
163
+ const lv = this.getLastVowel(root);
164
+ return lv && this.onVowels.has(lv) ? 'front' : 'back';
165
+ }
166
+
167
+ getLastVowel(word) {
168
+ for (let i = word.length - 1; i >= 0; i--) {
169
+ if (this.allVowels.has(word[i])) return word[i];
170
+ }
171
+ return null;
172
+ }
173
+
174
+ normalize(text) {
175
+ return text.toLowerCase().trim()
176
+ .replace(/i̇/g, 'i') // fix combined i characters
177
+ .replace(/[^a-zçgğıoöşuüâîû\s]/g, ''); // keep valid Turkish characters
178
+ }
179
+
180
+ analyze(word) {
181
+ const w = this.normalize(word).split(/\s+/)[0];
182
+ const result = {
183
+ word: w,
184
+ root: w,
185
+ suffixes: [],
186
+ harmony: this.determineVowelHarmony(w),
187
+ morphemes: [],
188
+ features: {},
189
+ vowelCount: 0,
190
+ syllables: []
191
+ };
192
+
193
+ if (!w) return result;
194
+
195
+ result.vowelCount = [...w].filter(c => this.allVowels.has(c)).length;
196
+ result.syllables = this.syllabify(w);
197
+
198
+ // Greedy root-suffix splitter matching longest root with reverse consonant mutation support
199
+ let foundRoot = w;
200
+ let foundSuffs = [];
201
+
202
+ for (const root of this.roots) {
203
+ let isMatch = false;
204
+ let matchedLength = root.length;
205
+
206
+ if (w.startsWith(root)) {
207
+ isMatch = true;
208
+ } else {
209
+ // Consonant mutation check (e.g. kitap -> kitabım, git -> gidiyor)
210
+ const last = root[root.length - 1];
211
+ if (['p', 'ç', 't', 'k'].includes(last)) {
212
+ const stem = root.slice(0, -1);
213
+ let mutatedStem = '';
214
+ if (last === 'p') mutatedStem = stem + 'b';
215
+ else if (last === 'ç') mutatedStem = stem + 'c';
216
+ else if (last === 't') mutatedStem = stem + 'd';
217
+ else if (last === 'k') mutatedStem = stem + 'ğ';
218
+
219
+ // Renk -> rengi exception
220
+ if (root === 'renk') mutatedStem = stem + 'g';
221
+
222
+ if (w.startsWith(mutatedStem) && w.length > root.length) {
223
+ // Next char in word must be a vowel for mutation to happen
224
+ const nextChar = w[mutatedStem.length];
225
+ if (this.allVowels.has(nextChar)) {
226
+ isMatch = true;
227
+ matchedLength = mutatedStem.length;
228
+ }
229
+ }
230
+ }
231
+ }
232
+
233
+ if (isMatch && root.length >= 2 && root.length < w.length) {
234
+ const suffix = w.slice(matchedLength);
235
+ if (root.length > (foundRoot === w ? 0 : foundRoot.length)) {
236
+ foundRoot = root;
237
+ foundSuffs = this.splitSuffix(suffix);
238
+ }
239
+ }
240
+ }
241
+
242
+ result.root = foundRoot;
243
+ result.suffixes = foundSuffs;
244
+ result.morphemes = [foundRoot, ...foundSuffs].filter(Boolean);
245
+ result.harmony = this.determineVowelHarmony(foundRoot);
246
+
247
+ // Enrich features
248
+ foundSuffs.forEach(suff => {
249
+ const feat = this.suffixFeatures[suff];
250
+ if (feat) {
251
+ Object.assign(result.features, feat);
252
+ }
253
+ });
254
+
255
+ return result;
256
+ }
257
+
258
+ splitSuffix(suffix) {
259
+ if (!suffix) return [];
260
+ const result = [];
261
+ let rem = suffix;
262
+ while (rem.length > 0) {
263
+ let matched = false;
264
+ for (const s of this.suffixesKnown) {
265
+ if (rem.endsWith(s)) {
266
+ result.unshift(s);
267
+ rem = rem.slice(0, rem.length - s.length);
268
+ matched = true;
269
+ break;
270
+ }
271
+ }
272
+ if (!matched) {
273
+ result.unshift(rem); // fallback for unmatched leading segment
274
+ break;
275
+ }
276
+ }
277
+ return result;
278
+ }
279
+
280
+ syllabify(word) {
281
+ const w = this.normalize(word);
282
+ if (!w) return [];
283
+
284
+ // Find indices of all vowels
285
+ const vowelIndices = [];
286
+ for (let i = 0; i < w.length; i++) {
287
+ if (this.allVowels.has(w[i])) {
288
+ vowelIndices.push(i);
289
+ }
290
+ }
291
+
292
+ if (vowelIndices.length <= 1) {
293
+ return [w];
294
+ }
295
+
296
+ const syllables = [];
297
+ let start = 0;
298
+
299
+ // Split word step-by-step based on vowel gaps according to Turkish grammar rules
300
+ for (let k = 0; k < vowelIndices.length - 1; k++) {
301
+ const v1 = vowelIndices[k];
302
+ const v2 = vowelIndices[k + 1];
303
+ const consonantCount = v2 - v1 - 1;
304
+
305
+ let splitPoint;
306
+ if (consonantCount === 0) {
307
+ // V-V -> e.g. fi-il
308
+ splitPoint = v1 + 1;
309
+ } else if (consonantCount === 1) {
310
+ // V-C-V -> division before the consonant ( liaison / ulama rule )
311
+ splitPoint = v1 + 1;
312
+ } else if (consonantCount === 2) {
313
+ // V-C-C-V -> division between the two consonants
314
+ splitPoint = v1 + 2;
315
+ } else {
316
+ // V-C-C-C-V or more -> division before the last consonant
317
+ splitPoint = v2 - 1;
318
+ }
319
+
320
+ syllables.push(w.slice(start, splitPoint));
321
+ start = splitPoint;
322
+ }
323
+ syllables.push(w.slice(start));
324
+ return syllables.filter(Boolean);
325
+ }
326
+
327
+ syllabifyPhrase(phrase) {
328
+ const words = phrase.toLowerCase().trim().split(/\s+/).filter(Boolean);
329
+ if (words.length === 0) return [];
330
+ if (words.length === 1) return this.syllabify(words[0]);
331
+
332
+ const resultSyllables = [];
333
+ const processedWords = [...words];
334
+
335
+ for (let i = 0; i < processedWords.length - 1; i++) {
336
+ const w1 = processedWords[i];
337
+ const w2 = processedWords[i + 1];
338
+ if (w1.length === 0 || w2.length === 0) continue;
339
+
340
+ const lastChar = w1[w1.length - 1];
341
+ const firstChar = w2[0];
342
+
343
+ // Liaison: ends in consonant, starts in vowel
344
+ if (!this.allVowels.has(lastChar) && this.allVowels.has(firstChar)) {
345
+ processedWords[i] = w1.slice(0, -1);
346
+ processedWords[i + 1] = lastChar + w2;
347
+ }
348
+ }
349
+
350
+ for (const w of processedWords) {
351
+ if (w.length > 0) {
352
+ resultSyllables.push(...this.syllabify(w));
353
+ }
354
+ }
355
+ return resultSyllables;
356
+ }
357
+ }
package/core/phase.js ADDED
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Phase Engine Module
3
+ * Analyzes phase components, phase coherence, alignment, and differences in phase-coded HDC vectors.
4
+ */
5
+
6
+ export class PhaseEngine {
7
+ constructor() {}
8
+
9
+ extractPhase(representation) {
10
+ const D = representation.D;
11
+ const phase = new Float32Array(D);
12
+ if (representation.type === 'complex') {
13
+ phase.set(representation.values);
14
+ } else {
15
+ // For real, bipolar, binary, compute phase of complex projection (z = x + 0i)
16
+ for (let i = 0; i < D; i++) {
17
+ phase[i] = representation.values[i] >= 0.0 ? 0.0 : Math.PI;
18
+ }
19
+ }
20
+ return phase;
21
+ }
22
+
23
+ phaseDifference(a, b) {
24
+ const D = a.D;
25
+ const diff = new Float32Array(D);
26
+ const pA = this.extractPhase(a);
27
+ const pB = this.extractPhase(b);
28
+
29
+ for (let i = 0; i < D; i++) {
30
+ let d = pA[i] - pB[i];
31
+ // Wrap to [-PI, PI)
32
+ while (d < -Math.PI) d += 2 * Math.PI;
33
+ while (d >= Math.PI) d -= 2 * Math.PI;
34
+ diff[i] = d;
35
+ }
36
+ return diff;
37
+ }
38
+
39
+ phaseAlignment(a, b) {
40
+ // Calculates circular variance of phase difference.
41
+ // 1 - variance ranges from 0 (no alignment/random) to 1 (perfect alignment).
42
+ const diff = this.phaseDifference(a, b);
43
+ let sumCos = 0;
44
+ let sumSin = 0;
45
+ const D = diff.length;
46
+
47
+ for (let i = 0; i < D; i++) {
48
+ sumCos += Math.cos(diff[i]);
49
+ sumSin += Math.sin(diff[i]);
50
+ }
51
+ const R = Math.sqrt(sumCos * sumCos + sumSin * sumSin) / D;
52
+ return R; // Resulting vector length
53
+ }
54
+
55
+ phaseCoherence(a, b) {
56
+ // Phase Coherence is the magnitude of the mean phase difference vector:
57
+ // C = | (1/D) * sum( e^{i * (theta_a - theta_b)} ) |
58
+ const pA = this.extractPhase(a);
59
+ const pB = this.extractPhase(b);
60
+ const D = a.D;
61
+
62
+ let sumCos = 0;
63
+ let sumSin = 0;
64
+
65
+ for (let i = 0; i < D; i++) {
66
+ const diff = pA[i] - pB[i];
67
+ sumCos += Math.cos(diff);
68
+ sumSin += Math.sin(diff);
69
+ }
70
+
71
+ // Magnitude of average complex phase difference
72
+ return Math.sqrt(sumCos * sumCos + sumSin * sumSin) / D;
73
+ }
74
+
75
+ phaseSimilarity(a, b) {
76
+ // Cosine similarity in phase domain: (1/D) * sum( cos(theta_a - theta_b) )
77
+ const pA = this.extractPhase(a);
78
+ const pB = this.extractPhase(b);
79
+ const D = a.D;
80
+
81
+ let sumCos = 0;
82
+ for (let i = 0; i < D; i++) {
83
+ sumCos += Math.cos(pA[i] - pB[i]);
84
+ }
85
+ return sumCos / D;
86
+ }
87
+ }