| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 'use strict'; |
| const fs = require('fs'); |
| const path = require('path'); |
| const crypto = require('crypto'); |
|
|
| const DECAY = 0.97; |
| const REINFORCE = 1.0; |
| const PAIR_REINFORCE = 0.6; |
| const MAX_BONUS = 0.08; |
|
|
| function h(text) { return crypto.createHash('sha1').update(text).digest('hex').slice(0, 12); } |
|
|
| |
| |
| |
| |
| |
| const K_PLANES = 7; |
| let PLANES = null; |
| function mulberry32(a) { return function () { a |= 0; a = a + 0x6D2B79F5 | 0; let t = Math.imul(a ^ a >>> 15, 1 | a); t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t; return ((t ^ t >>> 14) >>> 0) / 4294967296; }; } |
| function ensurePlanes(d) { |
| if (PLANES && PLANES[0].length === d) return; |
| const rng = mulberry32(0x5EED1234); |
| PLANES = []; |
| for (let k = 0; k < K_PLANES; k++) { const p = new Float32Array(d); for (let j = 0; j < d; j++) p[j] = rng() - 0.5; PLANES.push(p); } |
| } |
| function bucketOf(vec) { |
| if (!vec || !vec.length) return 'g'; |
| ensurePlanes(vec.length); |
| let bits = ''; |
| for (const p of PLANES) { let s = 0; for (let j = 0; j < vec.length; j++) s += vec[j] * p[j]; bits += s >= 0 ? '1' : '0'; } |
| return bits; |
| } |
|
|
| |
| |
| function load(filePath) { |
| try { |
| const raw = JSON.parse(fs.readFileSync(filePath, 'utf8').replace(/^/, '')); |
| return { warmth: new Map(Object.entries(raw.warmth || {})), pairs: new Map(Object.entries(raw.pairs || {})), turns: raw.turns || 0 }; |
| } catch (_) { return { warmth: new Map(), pairs: new Map(), turns: 0 }; } |
| } |
|
|
| function save(filePath, heb) { |
| try { |
| fs.mkdirSync(path.dirname(filePath), { recursive: true }); |
| const obj = { warmth: Object.fromEntries(heb.warmth), pairs: Object.fromEntries(heb.pairs), turns: heb.turns }; |
| fs.writeFileSync(filePath, JSON.stringify(obj)); |
| } catch (_) {} |
| } |
|
|
| |
| |
| function reinforce(heb, fragmentTexts, bucket) { |
| bucket = bucket || 'g'; |
| for (const [k, v] of heb.warmth) { const nv = v * DECAY; if (nv < 0.02) heb.warmth.delete(k); else heb.warmth.set(k, nv); } |
| for (const [k, v] of heb.pairs) { const nv = v * DECAY; if (nv < 0.02) heb.pairs.delete(k); else heb.pairs.set(k, nv); } |
| for (let i = 0; i < fragmentTexts.length; i++) { |
| const a = h(fragmentTexts[i]); |
| heb.warmth.set(a, (heb.warmth.get(a) || 0) + REINFORCE); |
| const bk = bucket + '\t' + a; |
| heb.warmth.set(bk, (heb.warmth.get(bk) || 0) + REINFORCE); |
| if (i + 1 < fragmentTexts.length) { |
| const pk = a + ':' + h(fragmentTexts[i + 1]); |
| heb.pairs.set(pk, (heb.pairs.get(pk) || 0) + PAIR_REINFORCE); |
| } |
| } |
| heb.turns++; |
| } |
|
|
| |
| |
| |
| function bonusMap(heb, bucket) { |
| const m = new Map(); |
| if (!heb || !heb.warmth.size) return m; |
| bucket = bucket || 'g'; |
| const prefix = bucket + '\t'; |
| let gMax = 0, bMax = 0; |
| for (const [k, v] of heb.warmth) { |
| if (k.includes('\t')) { if (k.startsWith(prefix) && v > bMax) bMax = v; } |
| else if (v > gMax) gMax = v; |
| } |
| const acc = new Map(); |
| for (const [k, v] of heb.warmth) { |
| if (k.includes('\t')) { |
| if (!k.startsWith(prefix)) continue; |
| const a = k.slice(prefix.length); |
| acc.set(a, (acc.get(a) || 0) + 0.6 * (bMax ? v / bMax : 0)); |
| } else { |
| acc.set(k, (acc.get(k) || 0) + 0.4 * (gMax ? v / gMax : 0)); |
| } |
| } |
| for (const [a, raw] of acc) m.set(a, MAX_BONUS * Math.min(1, raw)); |
| return m; |
| } |
|
|
| module.exports = { load, save, reinforce, bonusMap, bucketOf, hashText: h }; |
|
|