technician1 commited on
Commit
896d0bb
·
verified ·
1 Parent(s): 000a8e4

Delete pattern_constructor_q.cpp

Browse files
Files changed (1) hide show
  1. pattern_constructor_q.cpp +0 -1296
pattern_constructor_q.cpp DELETED
@@ -1,1296 +0,0 @@
1
- #include <algorithm>
2
- #include <array>
3
- #include <cctype>
4
- #include <cstdint>
5
- #include <cmath>
6
- #include <deque>
7
- #include <cstdio>
8
- #include <fstream>
9
- #include <iostream>
10
- #include <limits>
11
- #include <numeric>
12
- #include <stdexcept>
13
- #include <string>
14
- #include <string_view>
15
- #include <tuple>
16
- #include <unordered_map>
17
- #include <unordered_set>
18
- #include <utility>
19
- #include <vector>
20
-
21
- #ifdef _WIN32
22
- #include <windows.h>
23
- #endif
24
-
25
- #ifdef _OPENMP
26
- #include <omp.h>
27
- #else
28
- static int omp_get_max_threads(){ return 1; }
29
- static int omp_get_thread_num(){ return 0; }
30
- #endif
31
-
32
- // ============================================================================
33
- // Configuration
34
- // ============================================================================
35
- struct Config {
36
- std::size_t max_response_tokens = 32;
37
- std::size_t top_k = 3;
38
- std::size_t chunk_size = 8192;
39
- bool show_retrieval = false;
40
- bool chat_mode = false;
41
- };
42
- static Config config;
43
-
44
- // ============================================================================
45
- // Character helpers
46
- // ============================================================================
47
- static inline unsigned char uchar(char c){ return static_cast<unsigned char>(c); }
48
- static inline bool is_alpha(char c){ return std::isalpha(uchar(c)) != 0; }
49
- static inline bool is_digit(char c){ return std::isdigit(uchar(c)) != 0; }
50
- static inline bool is_alnum(char c){ return std::isalnum(uchar(c)) != 0; }
51
- static inline bool is_space(char c){ return std::isspace(uchar(c)) != 0; }
52
- static inline bool is_lower(char c){ return std::islower(uchar(c)) != 0; }
53
- static inline bool is_upper(char c){ return std::isupper(uchar(c)) != 0; }
54
-
55
- // ============================================================================
56
- // Deterministic hashes
57
- // ============================================================================
58
- static constexpr std::uint64_t FNV_OFFSET = 14695981039346656037ULL;
59
- static constexpr std::uint64_t FNV_PRIME = 1099511628211ULL;
60
-
61
- static std::uint64_t fnv1a64(std::string_view s){
62
- std::uint64_t h = FNV_OFFSET;
63
- for(unsigned char c : s){ h ^= c; h *= FNV_PRIME; }
64
- return h;
65
- }
66
-
67
- static std::uint64_t mix64(std::uint64_t x){
68
- x += 0x9E3779B97F4A7C15ULL;
69
- x = (x ^ (x >> 30)) * 0xBF58476D1CE4E5B9ULL;
70
- x = (x ^ (x >> 27)) * 0x94D049BB133111EBULL;
71
- return x ^ (x >> 31);
72
- }
73
-
74
- static std::uint64_t combine_hashes(std::uint64_t a,std::uint64_t b){
75
- return mix64(a ^ (mix64(b)+0x9E3779B97F4A7C15ULL+(a<<6)+(a>>2)));
76
- }
77
-
78
- // ============================================================================
79
- // Compact string pool (memory optimization)
80
- // ============================================================================
81
- struct StringRef { std::uint32_t offset=0, length=0; };
82
-
83
- struct StringPool {
84
- std::string bytes;
85
- void clear(){ bytes.clear(); }
86
- std::uint32_t append(std::string_view s){
87
- const std::uint32_t off=static_cast<std::uint32_t>(bytes.size());
88
- bytes.append(s);
89
- return off;
90
- }
91
- std::string_view view(std::uint32_t offset,std::uint32_t length) const {
92
- if(static_cast<std::size_t>(offset)+length>bytes.size()) return {};
93
- return std::string_view(bytes.data()+offset,length);
94
- }
95
- };
96
- static StringPool string_pool;
97
-
98
- // ============================================================================
99
- // Integer token dictionary
100
- // ============================================================================
101
- struct TokenHash {
102
- std::size_t operator()(std::string_view s) const noexcept {
103
- return static_cast<std::size_t>(fnv1a64(s));
104
- }
105
- };
106
-
107
- struct TokenDictionary {
108
- std::deque<std::string> text;
109
- std::unordered_map<std::string_view,std::uint32_t,TokenHash> to_id;
110
-
111
- void clear(){ text.clear(); to_id.clear(); }
112
-
113
- std::uint32_t intern(std::string_view s){
114
- auto it=to_id.find(s);
115
- if(it!=to_id.end()) return it->second;
116
- const std::uint32_t id=static_cast<std::uint32_t>(text.size());
117
- text.emplace_back(s);
118
- to_id.emplace(std::string_view(text.back()),id);
119
- return id;
120
- }
121
-
122
- std::string_view view(std::uint32_t id) const {
123
- return id<text.size()?std::string_view(text[id]):std::string_view{};
124
- }
125
- };
126
- static TokenDictionary token_dict;
127
-
128
- // ============================================================================
129
- // Tokenization
130
- // ============================================================================
131
- struct RawToken {
132
- std::string_view text;
133
- std::string_view space_before;
134
- std::size_t start=0,end=0;
135
- };
136
-
137
- static bool is_identifier_char(char c){ return is_alnum(c) || c=='_'; }
138
-
139
- static std::vector<RawToken> tokenize_views(std::string_view text){
140
- std::vector<RawToken> out;
141
- out.reserve(text.size()/4+1);
142
- std::size_t i=0,space_begin=0;
143
- while(i<text.size()){
144
- if(is_space(text[i])){ ++i; continue; }
145
- const std::size_t start=i;
146
- if(is_identifier_char(text[i])){
147
- while(i<text.size() && is_identifier_char(text[i])) ++i;
148
- } else {
149
- ++i;
150
- }
151
- out.push_back({text.substr(start,i-start),text.substr(space_begin,start-space_begin),start,i});
152
- space_begin=i;
153
- }
154
- return out;
155
- }
156
-
157
- // ============================================================================
158
- // Structural classes
159
- // ============================================================================
160
- enum class TKind : std::uint8_t { EMPTY, PUNCT, MIXNUM, NUM, MIX, UPP, LOW, OTHER };
161
-
162
- enum class ShapeKind : std::uint8_t {
163
- EMPTY=0, ALPHA=1, DIGIT=2, ALNUM=3, SPACE=4, PUNCT=5,
164
- ALPHA_DIGIT=6, DIGIT_ALPHA=7, MIXED_ALPHA=8, OTHER=9
165
- };
166
-
167
- static TKind token_kind(std::string_view token){
168
- if(token.empty()) return TKind::EMPTY;
169
- bool alpha=false,digit=false,lower=false,upper=false;
170
- for(char c:token){
171
- if(is_alpha(c)){ alpha=true; lower|=is_lower(c); upper|=is_upper(c); }
172
- else if(is_digit(c)) digit=true;
173
- }
174
- if(!is_alnum(token.front()) && token.front()!='_') return TKind::PUNCT;
175
- if(alpha&&digit) return TKind::MIXNUM;
176
- if(digit) return TKind::NUM;
177
- if(upper&&lower) return TKind::MIX;
178
- if(upper) return TKind::UPP;
179
- if(lower) return TKind::LOW;
180
- return TKind::OTHER;
181
- }
182
-
183
- static bool variable_candidate(TKind k){
184
- return k==TKind::LOW || k==TKind::UPP || k==TKind::MIX || k==TKind::MIXNUM || k==TKind::NUM;
185
- }
186
-
187
- static ShapeKind shape_kind(std::string_view token){
188
- if(token.empty()) return ShapeKind::EMPTY;
189
- bool alpha=false,digit=false,space=false,punct=false,lower=false,upper=false;
190
- for(char c:token){
191
- alpha|=is_alpha(c); digit|=is_digit(c); space|=is_space(c); punct|=!is_alnum(c)&&!is_space(c);
192
- lower|=is_lower(c); upper|=is_upper(c);
193
- }
194
- if(punct && !alpha && !digit) return ShapeKind::PUNCT;
195
- if(space && !alpha && !digit && !punct) return ShapeKind::SPACE;
196
- if(alpha&&digit){
197
- bool first_alpha=is_alpha(token.front()), first_digit=is_digit(token.front());
198
- return first_alpha ? ShapeKind::ALPHA_DIGIT : (first_digit ? ShapeKind::DIGIT_ALPHA : ShapeKind::ALNUM);
199
- }
200
- if(alpha){
201
- if(lower&&upper) return ShapeKind::MIXED_ALPHA;
202
- return ShapeKind::ALPHA;
203
- }
204
- if(digit) return ShapeKind::DIGIT;
205
- return ShapeKind::OTHER;
206
- }
207
-
208
- static std::uint64_t morphology_hash(std::string_view token){
209
- std::uint64_t h=FNV_OFFSET;
210
- for(char c:token){
211
- std::uint8_t k=0;
212
- if(is_alpha(c)) k=is_lower(c)?1:2;
213
- else if(is_digit(c)) k=3;
214
- else if(is_space(c)) k=4;
215
- else k=5;
216
- h ^= k; h *= FNV_PRIME;
217
- }
218
- h=combine_hashes(h,static_cast<std::uint64_t>(token.size()));
219
- return h;
220
- }
221
-
222
- static std::uint64_t transition_hash(std::string_view token){
223
- std::uint64_t h=FNV_OFFSET;
224
- std::uint8_t prev=0;
225
- bool first=true;
226
- for(char c:token){
227
- std::uint8_t k=0;
228
- if(is_alpha(c)) k=is_lower(c)?1:2;
229
- else if(is_digit(c)) k=3;
230
- else if(is_space(c)) k=4;
231
- else k=5;
232
- if(first){ h=combine_hashes(h,k); first=false; }
233
- else { h=combine_hashes(h,(static_cast<std::uint64_t>(prev)<<8)|k); }
234
- prev=k;
235
- }
236
- return combine_hashes(h,token.size());
237
- }
238
-
239
- // ============================================================================
240
- // Profiles
241
- // ============================================================================
242
- struct Profile {
243
- float distinct_ratio=0.0f;
244
- float alpha_ratio=0.0f;
245
- float num_ratio=0.0f;
246
- float punct_ratio=0.0f;
247
- float space_ratio=0.0f;
248
- };
249
-
250
- static Profile extract_profile(std::string_view text){
251
- std::array<std::uint8_t,256> seen{};
252
- std::size_t distinct=0,alpha=0,num=0,punct=0,space=0;
253
- for(char c:text){
254
- const auto u=uchar(c);
255
- if(!seen[u]){seen[u]=1;++distinct;}
256
- if(is_alpha(c)) ++alpha;
257
- else if(is_digit(c)) ++num;
258
- else if(is_space(c)) ++space;
259
- else ++punct;
260
- }
261
- if(text.empty()) return {};
262
- const float total=static_cast<float>(text.size());
263
- return {static_cast<float>(distinct)/total,
264
- static_cast<float>(alpha)/total,
265
- static_cast<float>(num)/total,
266
- static_cast<float>(punct)/total,
267
- static_cast<float>(space)/total};
268
- }
269
-
270
- static float profile_distance(const Profile&a,const Profile&b){
271
- return std::fabs(a.distinct_ratio-b.distinct_ratio)+
272
- std::fabs(a.alpha_ratio-b.alpha_ratio)+
273
- std::fabs(a.num_ratio-b.num_ratio)+
274
- std::fabs(a.punct_ratio-b.punct_ratio)+
275
- std::fabs(a.space_ratio-b.space_ratio);
276
- }
277
-
278
- // ============================================================================
279
- // Generic CSR index
280
- // ============================================================================
281
- struct CSRIndex {
282
- std::vector<std::uint64_t> keys;
283
- std::vector<std::uint32_t> offsets;
284
- std::vector<std::uint32_t> postings;
285
- void clear(){keys.clear();offsets.clear();postings.clear();}
286
- std::pair<const std::uint32_t*,std::size_t> find(std::uint64_t key) const {
287
- auto it=std::lower_bound(keys.begin(),keys.end(),key);
288
- if(it==keys.end()||*it!=key) return {nullptr,0};
289
- const std::size_t idx=static_cast<std::size_t>(it-keys.begin());
290
- const std::size_t a=offsets[idx],b=offsets[idx+1];
291
- return {postings.data()+a,b-a};
292
- }
293
- };
294
-
295
- struct BuildIndex {
296
- std::unordered_map<std::uint64_t,std::vector<std::uint32_t>> postings;
297
- void clear(){postings.clear();}
298
- };
299
-
300
- static void merge_index(BuildIndex& dst,BuildIndex& src){
301
- for(auto& kv:src.postings){
302
- auto& v=dst.postings[kv.first];
303
- v.insert(v.end(),kv.second.begin(),kv.second.end());
304
- }
305
- }
306
-
307
- static void compile_csr(const BuildIndex& src,CSRIndex& dst){
308
- std::vector<std::uint64_t> keys;
309
- keys.reserve(src.postings.size());
310
- for(const auto& kv:src.postings) keys.push_back(kv.first);
311
- std::sort(keys.begin(),keys.end());
312
- dst.clear();
313
- dst.keys=std::move(keys);
314
- dst.offsets.reserve(dst.keys.size()+1);
315
- std::size_t total=0;
316
- for(std::uint64_t k:dst.keys) total+=src.postings.at(k).size();
317
- dst.postings.reserve(total);
318
- dst.offsets.push_back(0);
319
- for(std::uint64_t k:dst.keys){
320
- const auto& v=src.postings.at(k);
321
- dst.postings.insert(dst.postings.end(),v.begin(),v.end());
322
- dst.offsets.push_back(static_cast<std::uint32_t>(dst.postings.size()));
323
- }
324
- }
325
-
326
- // ============================================================================
327
- // Pattern graph / relations
328
- // ============================================================================
329
- struct Edge {
330
- std::uint32_t from=0,to=0;
331
- std::uint8_t type=0;
332
- };
333
-
334
- struct Relation {
335
- std::uint32_t a=0,b=0;
336
- std::int32_t distance=0;
337
- std::uint8_t type=0;
338
- };
339
-
340
- // ============================================================================
341
- // Canonical and structural pattern representation
342
- // ============================================================================
343
- struct Pattern {
344
- StringRef text;
345
- Profile profile;
346
- std::vector<std::uint32_t> token_ids;
347
- std::vector<std::uint32_t> canonical_ids;
348
- std::vector<std::uint8_t> kinds;
349
- std::vector<std::uint8_t> shapes;
350
- std::vector<std::uint64_t> token_morph_hashes;
351
- std::vector<std::uint64_t> token_transition_hashes;
352
- std::vector<Relation> relations;
353
- std::vector<Edge> graph;
354
- std::uint64_t canonical_hash=0;
355
- std::uint64_t graph_hash=0;
356
- std::uint64_t relation_hash=0;
357
- std::uint64_t transition_hash=0;
358
- std::uint32_t observations=1;
359
- std::uint32_t structural_variants=1;
360
- std::uint32_t exact_matches=0;
361
- std::uint32_t parent=std::numeric_limits<std::uint32_t>::max();
362
- std::vector<std::uint32_t> children;
363
- };
364
-
365
- static std::vector<std::uint32_t> canonicalize(const std::vector<std::uint32_t>& ids){
366
- std::unordered_map<std::uint32_t,std::uint32_t> renumber;
367
- renumber.reserve(ids.size()*2+1);
368
- std::vector<std::uint32_t> out(ids.size());
369
- std::uint32_t next=0;
370
- for(std::size_t i=0;i<ids.size();++i){
371
- auto it=renumber.find(ids[i]);
372
- if(it==renumber.end()) it=renumber.emplace(ids[i],next++).first;
373
- out[i]=it->second;
374
- }
375
- return out;
376
- }
377
-
378
- static std::uint64_t canonical_hash(const std::vector<std::uint32_t>& c,
379
- const std::vector<std::uint8_t>& k){
380
- std::uint64_t h=FNV_OFFSET;
381
- for(std::size_t i=0;i<c.size();++i){
382
- h=combine_hashes(h,c[i]);
383
- h=combine_hashes(h,k[i]);
384
- }
385
- return h;
386
- }
387
-
388
- static std::vector<Relation> build_relations(const std::vector<std::uint32_t>& c){
389
- std::vector<Relation> r;
390
- const std::size_t n=c.size();
391
- if(n<2) return r;
392
- r.reserve(n*2);
393
- for(std::size_t i=0;i<n;++i){
394
- for(std::size_t j=i+1;j<n;++j){
395
- if(c[i]==c[j]) r.push_back({static_cast<std::uint32_t>(i),static_cast<std::uint32_t>(j),static_cast<std::int32_t>(j-i),0});
396
- }
397
- }
398
- for(std::size_t i=0;i+1<n;++i)
399
- r.push_back({static_cast<std::uint32_t>(i),static_cast<std::uint32_t>(i+1),1,1});
400
- std::sort(r.begin(),r.end(),[](const Relation&a,const Relation&b){
401
- return std::tie(a.type,a.distance,a.a,a.b)<std::tie(b.type,b.distance,b.a,b.b);
402
- });
403
- return r;
404
- }
405
-
406
- static std::uint64_t relation_hash(const std::vector<Relation>& r){
407
- std::uint64_t h=FNV_OFFSET;
408
- for(const auto& x:r){
409
- h=combine_hashes(h,x.type);
410
- h=combine_hashes(h,x.a);
411
- h=combine_hashes(h,x.b);
412
- h=combine_hashes(h,static_cast<std::uint32_t>(x.distance));
413
- }
414
- return h;
415
- }
416
-
417
- static std::vector<Edge> build_graph(const std::vector<std::uint32_t>& c){
418
- std::vector<Edge> g;
419
- if(c.size()<2) return g;
420
- g.reserve(c.size()*3);
421
- for(std::size_t i=0;i+1<c.size();++i) g.push_back({static_cast<std::uint32_t>(i),static_cast<std::uint32_t>(i+1),0});
422
- for(std::size_t i=0;i<c.size();++i){
423
- for(std::size_t j=i+1;j<c.size();++j){
424
- if(c[i]==c[j]) g.push_back({static_cast<std::uint32_t>(i),static_cast<std::uint32_t>(j),1});
425
- }
426
- }
427
- return g;
428
- }
429
-
430
- static std::uint64_t graph_hash(const std::vector<Edge>& g){
431
- std::uint64_t h=FNV_OFFSET;
432
- for(const auto&e:g){h=combine_hashes(h,e.from);h=combine_hashes(h,e.to);h=combine_hashes(h,e.type);}
433
- return h;
434
- }
435
-
436
- static std::uint64_t sequence_transition_hash(const std::vector<std::uint64_t>& h){
437
- std::uint64_t x=FNV_OFFSET;
438
- for(std::uint64_t v:h) x=combine_hashes(x,v);
439
- return x;
440
- }
441
-
442
- // ============================================================================
443
- // Generic local token analysis
444
- // ============================================================================
445
- static void analyze_token_sequence(std::string_view text,
446
- std::vector<std::uint32_t>& ids,
447
- std::vector<std::uint8_t>& kinds,
448
- std::vector<std::uint8_t>& shapes,
449
- std::vector<std::uint64_t>& morph,
450
- std::vector<std::uint64_t>& transitions){
451
- auto raw=tokenize_views(text);
452
- ids.resize(raw.size()); kinds.resize(raw.size()); shapes.resize(raw.size());
453
- morph.resize(raw.size()); transitions.resize(raw.size());
454
- for(std::size_t i=0;i<raw.size();++i){
455
- ids[i]=token_dict.intern(raw[i].text);
456
- kinds[i]=static_cast<std::uint8_t>(token_kind(raw[i].text));
457
- shapes[i]=static_cast<std::uint8_t>(shape_kind(raw[i].text));
458
- morph[i]=morphology_hash(raw[i].text);
459
- transitions[i]=transition_hash(raw[i].text);
460
- }
461
- }
462
-
463
- static std::vector<std::uint32_t> longest_common_structure(const std::vector<std::uint32_t>& a,
464
- const std::vector<std::uint32_t>& b){
465
- if(a.empty()||b.empty()) return {};
466
- if(a.size()*b.size()>2000000ULL) return {};
467
- std::vector<std::uint32_t> prev(b.size()+1),cur(b.size()+1);
468
- for(std::size_t i=1;i<=a.size();++i){
469
- for(std::size_t j=1;j<=b.size();++j)
470
- cur[j]=(a[i-1]==b[j-1])?prev[j-1]+1:std::max(prev[j],cur[j-1]);
471
- prev.swap(cur); std::fill(cur.begin(),cur.end(),0);
472
- }
473
- std::vector<std::uint32_t> out(prev.back());
474
- std::iota(out.begin(),out.end(),0u);
475
- return out;
476
- }
477
-
478
- // ============================================================================
479
- // Pattern construction
480
- // ============================================================================
481
- static Pattern construct_pattern(const std::string& text,StringRef ref){
482
- Pattern p;
483
- p.text=ref;
484
- p.profile=extract_profile(text);
485
- analyze_token_sequence(text,p.token_ids,p.kinds,p.shapes,p.token_morph_hashes,p.token_transition_hashes);
486
- p.canonical_ids=canonicalize(p.token_ids);
487
- p.canonical_hash=canonical_hash(p.canonical_ids,p.kinds);
488
- p.relations=build_relations(p.canonical_ids);
489
- p.relation_hash=relation_hash(p.relations);
490
- p.graph=build_graph(p.canonical_ids);
491
- p.graph_hash=graph_hash(p.graph);
492
- p.transition_hash=sequence_transition_hash(p.token_transition_hashes);
493
- return p;
494
- }
495
-
496
- // ============================================================================
497
- // Efficient Suffix Arrays for variable-length prefix lookups
498
- // ============================================================================
499
- enum class SAType : std::uint8_t { TEXT=0, CANONICAL=1, SHAPE=2 };
500
-
501
- struct SAElem {
502
- std::uint32_t pat_id=0;
503
- std::uint32_t offset=0;
504
- };
505
-
506
- static std::vector<SAElem> text_sa;
507
- static std::vector<SAElem> canonical_sa;
508
- static std::vector<SAElem> shape_sa;
509
-
510
- static std::size_t sa_sequence_size(const Pattern& p,SAType type){
511
- if(type==SAType::TEXT) return p.token_ids.size();
512
- if(type==SAType::CANONICAL) return p.canonical_ids.size();
513
- return p.shapes.size();
514
- }
515
-
516
- static std::uint32_t sa_sequence_value(const Pattern& p,SAType type,std::size_t i){
517
- if(type==SAType::TEXT) return p.token_ids[i];
518
- if(type==SAType::CANONICAL) return p.canonical_ids[i];
519
- return p.shapes[i];
520
- }
521
-
522
- static void build_suffix_array(const std::vector<Pattern>& pats,std::vector<SAElem>& sa,SAType type){
523
- std::size_t total=0;
524
- for(const Pattern& p:pats) total+=sa_sequence_size(p,type);
525
- sa.clear(); sa.resize(total);
526
- std::unordered_map<std::uint32_t,std::vector<SAElem>> buckets;
527
- buckets.reserve(total?std::min<std::size_t>(total,1u<<20):0);
528
- for(std::uint32_t id=0;id<pats.size();++id){
529
- const Pattern& p=pats[id];
530
- const std::size_t n=sa_sequence_size(p,type);
531
- for(std::uint32_t off=0;off<n;++off) buckets[sa_sequence_value(p,type,off)].push_back({id,off});
532
- }
533
- std::vector<std::vector<SAElem>*> bucket_ptrs;
534
- bucket_ptrs.reserve(buckets.size());
535
- for(auto& kv:buckets) bucket_ptrs.push_back(&kv.second);
536
- #ifdef _OPENMP
537
- #pragma omp parallel for schedule(dynamic)
538
- #endif
539
- for(int i=0;i<static_cast<int>(bucket_ptrs.size());++i){
540
- auto* b=bucket_ptrs[static_cast<std::size_t>(i)];
541
- std::sort(b->begin(),b->end(),[&](const SAElem&a,const SAElem&z){
542
- const Pattern&pa=pats[a.pat_id],&pb=pats[z.pat_id];
543
- const std::size_t na=sa_sequence_size(pa,type)-a.offset,nz=sa_sequence_size(pb,type)-z.offset,m=std::min(na,nz);
544
- for(std::size_t k=0;k<m;++k){
545
- const std::uint32_t va=sa_sequence_value(pa,type,a.offset+k),vz=sa_sequence_value(pb,type,z.offset+k);
546
- if(va!=vz) return va<vz;
547
- }
548
- if(na!=nz) return na<nz;
549
- return a.pat_id<z.pat_id || (a.pat_id==z.pat_id && a.offset<z.offset);
550
- });
551
- }
552
- std::vector<std::uint32_t> keys;
553
- keys.reserve(buckets.size());
554
- for(const auto& kv:buckets) keys.push_back(kv.first);
555
- std::sort(keys.begin(),keys.end());
556
- std::size_t out=0;
557
- for(std::uint32_t key:keys){
558
- const auto& b=buckets.find(key)->second;
559
- std::copy(b.begin(),b.end(),sa.begin()+static_cast<std::ptrdiff_t>(out));
560
- out+=b.size();
561
- }
562
- }
563
-
564
- static std::tuple<std::size_t,std::size_t,std::size_t> search_sa_longest(const std::vector<SAElem>&sa,const std::vector<Pattern>&pats,const std::vector<std::uint32_t>&query,std::size_t query_offset,SAType type){
565
- if(query_offset>=query.size()||sa.empty()) return {0,0,0};
566
- const std::size_t query_size=query.size()-query_offset;
567
- auto cmp_lower=[&](const SAElem&a,const std::vector<std::uint32_t>&q){
568
- const Pattern&p=pats[a.pat_id];const std::size_t n=sa_sequence_size(p,type)-a.offset,m=std::min(n,query_size);
569
- for(std::size_t k=0;k<m;++k){const std::uint32_t v=sa_sequence_value(p,type,a.offset+k);if(v!=q[query_offset+k])return v<q[query_offset+k];}
570
- return n<query_size;
571
- };
572
- auto it_low=std::lower_bound(sa.begin(),sa.end(),query,cmp_lower);
573
- auto lcp_at=[&](auto it){
574
- if(it==sa.end())return std::size_t(0);
575
- const Pattern&p=pats[it->pat_id];const std::size_t n=sa_sequence_size(p,type)-it->offset,m=std::min(n,query_size);std::size_t k=0;
576
- while(k<m&&sa_sequence_value(p,type,it->offset+k)==query[query_offset+k]) ++k;
577
- return k;
578
- };
579
- const std::size_t lcp_low=lcp_at(it_low),lcp_prev=(it_low!=sa.begin())?lcp_at(it_low-1):0,max_lcp=std::max(lcp_low,lcp_prev);
580
- if(!max_lcp)return {0,0,0};
581
- auto cmp_prefix_lower=[&](const SAElem&a,const std::vector<std::uint32_t>&q){
582
- const Pattern&p=pats[a.pat_id];const std::size_t n=sa_sequence_size(p,type)-a.offset,m=std::min(n,max_lcp);
583
- for(std::size_t k=0;k<m;++k){const std::uint32_t v=sa_sequence_value(p,type,a.offset+k);if(v!=q[query_offset+k])return v<q[query_offset+k];}
584
- return n<max_lcp;
585
- };
586
- auto cmp_prefix_upper=[&](const std::vector<std::uint32_t>&q,const SAElem&a){
587
- const Pattern&p=pats[a.pat_id];const std::size_t m=std::min(sa_sequence_size(p,type)-a.offset,max_lcp);
588
- for(std::size_t k=0;k<m;++k){const std::uint32_t v=sa_sequence_value(p,type,a.offset+k);if(q[query_offset+k]!=v)return q[query_offset+k]<v;}
589
- return false;
590
- };
591
- auto first=std::lower_bound(sa.begin(),sa.end(),query,cmp_prefix_lower),last=std::upper_bound(first,sa.end(),query,cmp_prefix_upper);
592
- return {static_cast<std::size_t>(first-sa.begin()),static_cast<std::size_t>(last-sa.begin()),max_lcp};
593
- }
594
-
595
- // ============================================================================
596
- // Global indexes
597
- // ============================================================================
598
- static std::vector<Pattern> patterns;
599
- static CSRIndex morph_csr,transition_csr,relation_csr,graph_csr;
600
- static std::unordered_map<std::uint64_t,std::uint32_t> canonical_pattern_map;
601
-
602
- static void build_pattern_indexes(){
603
- BuildIndex morph,transition,relation,graph;
604
- canonical_pattern_map.clear();
605
- const std::size_t n=patterns.size();
606
- const int threads=std::max(1,omp_get_max_threads());
607
- std::vector<BuildIndex> locals_morph(static_cast<std::size_t>(threads)),locals_transition(static_cast<std::size_t>(threads)),locals_rel(static_cast<std::size_t>(threads)),locals_graph(static_cast<std::size_t>(threads));
608
- #ifdef _OPENMP
609
- #pragma omp parallel
610
- #endif
611
- {
612
- const int tid=omp_get_thread_num();
613
- BuildIndex&lm=locals_morph[static_cast<std::size_t>(tid)],&lt=locals_transition[static_cast<std::size_t>(tid)],&lr=locals_rel[static_cast<std::size_t>(tid)],&lg=locals_graph[static_cast<std::size_t>(tid)];
614
- #ifdef _OPENMP
615
- #pragma omp for schedule(dynamic,16)
616
- #endif
617
- for(int ii=0;ii<static_cast<int>(n);++ii){
618
- const std::uint32_t id=static_cast<std::uint32_t>(ii); const Pattern&p=patterns[id];
619
- for(std::uint64_t h:p.token_morph_hashes) lm.postings[h].push_back(id);
620
- if(p.transition_hash) lt.postings[p.transition_hash].push_back(id);
621
- if(p.relation_hash) lr.postings[p.relation_hash].push_back(id);
622
- if(p.graph_hash) lg.postings[p.graph_hash].push_back(id);
623
- }
624
- }
625
- for(auto&x:locals_morph) merge_index(morph,x);
626
- for(auto&x:locals_transition) merge_index(transition,x);
627
- for(auto&x:locals_rel) merge_index(relation,x);
628
- for(auto&x:locals_graph) merge_index(graph,x);
629
- compile_csr(morph,morph_csr); compile_csr(transition,transition_csr); compile_csr(relation,relation_csr); compile_csr(graph,graph_csr);
630
- #ifdef _OPENMP
631
- #pragma omp parallel sections
632
- #endif
633
- {
634
- #ifdef _OPENMP
635
- #pragma omp section
636
- #endif
637
- build_suffix_array(patterns,text_sa,SAType::TEXT);
638
- #ifdef _OPENMP
639
- #pragma omp section
640
- #endif
641
- build_suffix_array(patterns,canonical_sa,SAType::CANONICAL);
642
- #ifdef _OPENMP
643
- #pragma omp section
644
- #endif
645
- build_suffix_array(patterns,shape_sa,SAType::SHAPE);
646
- }
647
- canonical_pattern_map.reserve(n*2+1);
648
- for(std::uint32_t i=0;i<n;++i) canonical_pattern_map.emplace(patterns[i].canonical_hash,i);
649
- }
650
-
651
- // ============================================================================
652
- // Input cleaning and file loading
653
- // ============================================================================
654
- static bool read_file(const std::string& filename,std::string& output){
655
- std::ifstream is(filename.c_str(),std::ios::binary|std::ios::ate);
656
- if(!is) return false;
657
- const std::streampos end=is.tellg();
658
- if(end<0) return false;
659
- output.clear(); output.resize(static_cast<std::size_t>(end));
660
- is.seekg(0,std::ios::beg);
661
- return output.empty() || static_cast<bool>(is.read(output.data(),static_cast<std::streamsize>(output.size())));
662
- }
663
-
664
- static void split_text(const std::string& text,std::size_t chunk_size,std::vector<std::string>& out){
665
- if(text.empty()) return;
666
- if(!chunk_size) chunk_size=8192;
667
- std::string current; current.reserve(chunk_size);
668
- std::size_t line_start=0;
669
- while(line_start<text.size()){
670
- std::size_t line_end=text.find('\n',line_start);
671
- if(line_end==std::string::npos) line_end=text.size();
672
- std::string line=text.substr(line_start,line_end-line_start);
673
- if(line_end<text.size()) line.push_back('\n');
674
- if(line.size()>chunk_size){
675
- if(!current.empty()){out.push_back(std::move(current));current.clear();current.reserve(chunk_size);}
676
- for(std::size_t pos=0;pos<line.size();){
677
- const std::size_t take=std::min(chunk_size,line.size()-pos);
678
- out.push_back(line.substr(pos,take)); pos+=take;
679
- }
680
- } else {
681
- if(!current.empty()&&current.size()+line.size()>chunk_size){out.push_back(std::move(current));current.clear();current.reserve(chunk_size);}
682
- current+=line;
683
- }
684
- if(line_end==text.size()) break;
685
- line_start=line_end+1;
686
- }
687
- if(!current.empty()) out.push_back(std::move(current));
688
- }
689
-
690
- static bool load_learning_file(const std::string& filename,std::vector<std::string>& dataset){
691
- std::string text;
692
- if(!read_file(filename,text)){std::cerr<<"Could not read file: "<<filename<<'\n';return false;}
693
- if(text.empty()) return false;
694
- const std::size_t before=dataset.size();
695
- split_text(text,config.chunk_size,dataset);
696
- std::cerr<<"Learn source: "<<filename<<" -> "<<dataset.size()-before<<" pattern(s)\n";
697
- return dataset.size()>before;
698
- }
699
-
700
- static bool load_learning_files(const std::vector<std::string>& files,std::vector<std::string>& dataset){
701
- if(files.empty()) return false;
702
- std::vector<std::vector<std::string>> local(files.size());
703
- #ifdef _OPENMP
704
- #pragma omp parallel for schedule(dynamic)
705
- #endif
706
- for(int i=0;i<static_cast<int>(files.size());++i)
707
- load_learning_file(files[static_cast<std::size_t>(i)],local[static_cast<std::size_t>(i)]);
708
- std::size_t total=dataset.size();
709
- for(const auto&v:local) total+=v.size();
710
- dataset.reserve(total);
711
- for(auto&v:local) dataset.insert(dataset.end(),std::make_move_iterator(v.begin()),std::make_move_iterator(v.end()));
712
- return !dataset.empty();
713
- }
714
-
715
- static void deduplicate(std::vector<std::string>& data){
716
- if(data.size()<2) return;
717
- std::unordered_set<std::string> seen; seen.reserve(data.size()*2);
718
- std::vector<std::string> unique; unique.reserve(data.size());
719
- for(std::string&s:data) if(seen.insert(s).second) unique.push_back(std::move(s));
720
- data.swap(unique);
721
- }
722
-
723
- // ============================================================================
724
- // Pattern merging / specialization tree
725
- // ============================================================================
726
- static std::uint32_t invalid_pattern_id(){ return std::numeric_limits<std::uint32_t>::max(); }
727
-
728
- static void insert_pattern_incremental(Pattern&& candidate,std::vector<Pattern>&store,std::unordered_map<std::uint64_t,std::uint32_t>&map){
729
- auto it=map.find(candidate.canonical_hash);
730
- if(it==map.end()){
731
- const std::uint32_t id=static_cast<std::uint32_t>(store.size());
732
- map.emplace(candidate.canonical_hash,id);
733
- store.push_back(std::move(candidate));
734
- return;
735
- }
736
- Pattern& q=store[it->second];
737
- const auto common=longest_common_structure(q.canonical_ids,candidate.canonical_ids);
738
- q.observations+=candidate.observations;
739
- q.structural_variants+=static_cast<std::uint32_t>(std::max<std::size_t>(1,common.size()));
740
- q.exact_matches+=candidate.exact_matches;
741
- if(profile_distance(candidate.profile,{})<profile_distance(q.profile,{})) q.profile=candidate.profile;
742
- for(std::size_t i=0;i<q.token_morph_hashes.size()&&i<candidate.token_morph_hashes.size();++i)
743
- if(q.token_morph_hashes[i]!=candidate.token_morph_hashes[i]) q.exact_matches++;
744
- }
745
-
746
- static void merge_equivalent_patterns(){
747
- if(patterns.empty()) return;
748
- std::vector<Pattern> merged; merged.reserve(patterns.size());
749
- canonical_pattern_map.clear(); canonical_pattern_map.reserve(patterns.size()*2+1);
750
- for(Pattern&p:patterns) insert_pattern_incremental(std::move(p),merged,canonical_pattern_map);
751
- patterns.swap(merged);
752
- }
753
-
754
- static std::vector<std::uint32_t> generalized_canonical(const Pattern&p){
755
- if(p.canonical_ids.empty()) return {};
756
- std::vector<std::uint32_t> g=p.canonical_ids;
757
- const std::uint32_t target=g.back();
758
- g.back()=0;
759
- if(target==0) return g;
760
- std::unordered_map<std::uint32_t,std::uint32_t> remap; remap.reserve(g.size()*2+1);
761
- std::uint32_t next=0;
762
- for(std::uint32_t&x:g){auto it=remap.find(x);if(it==remap.end())it=remap.emplace(x,next++).first;x=it->second;}
763
- return g;
764
- }
765
-
766
- static void build_specialization_tree(){
767
- if(patterns.empty()) return;
768
- for(Pattern&p:patterns){p.parent=invalid_pattern_id();p.children.clear();}
769
- if(canonical_pattern_map.size()!=patterns.size()){canonical_pattern_map.clear();canonical_pattern_map.reserve(patterns.size()*2+1);for(std::uint32_t i=0;i<patterns.size();++i)canonical_pattern_map.emplace(patterns[i].canonical_hash,i);}
770
- for(std::uint32_t i=0;i<patterns.size();++i){
771
- const Pattern&p=patterns[i]; const std::vector<std::uint32_t> g=generalized_canonical(p); if(g.empty()) continue;
772
- const std::uint64_t h=canonical_hash(g,p.kinds);
773
- auto it=canonical_pattern_map.find(h);
774
- if(it!=canonical_pattern_map.end()&&it->second!=i){patterns[i].parent=it->second;patterns[it->second].children.push_back(i);}
775
- }
776
- }
777
-
778
- static void expand_specialization_candidates(const std::vector<std::uint32_t>& seeds,std::vector<std::uint32_t>& ids){
779
- if(seeds.empty()||patterns.empty()) return;
780
- std::unordered_set<std::uint32_t> seen;seen.reserve(seeds.size()*4+1);
781
- std::vector<std::uint32_t> frontier;frontier.reserve(seeds.size()*2+1);
782
- for(std::uint32_t id:seeds) if(id<patterns.size()&&seen.insert(id).second) frontier.push_back(id);
783
- constexpr unsigned max_depth=2;
784
- for(unsigned depth=0;depth<max_depth&&!frontier.empty();++depth){
785
- std::vector<std::uint32_t> next;next.reserve(frontier.size()*2+1);
786
- for(std::uint32_t id:frontier){
787
- const Pattern&p=patterns[id];
788
- if(p.parent<patterns.size()&&seen.insert(p.parent).second) next.push_back(p.parent);
789
- for(std::uint32_t child:p.children) if(child<patterns.size()&&seen.insert(child).second) next.push_back(child);
790
- }
791
- ids.insert(ids.end(),next.begin(),next.end());frontier.swap(next);
792
- }
793
- }
794
-
795
- static void build_knowledge_base(const std::vector<std::string>& dataset){
796
- patterns.clear(); string_pool.clear(); token_dict.clear();
797
- morph_csr.clear(); transition_csr.clear(); relation_csr.clear(); graph_csr.clear();
798
- text_sa.clear(); canonical_sa.clear(); shape_sa.clear();
799
-
800
- if(dataset.empty()) return;
801
-
802
- std::vector<StringRef> refs(dataset.size());
803
- string_pool.bytes.reserve(std::accumulate(dataset.begin(),dataset.end(),std::size_t(0),[](std::size_t n,const std::string&s){return n+s.size();}));
804
- std::unordered_set<std::string_view,TokenHash> unique_tokens;
805
- unique_tokens.reserve(dataset.size()*8+1);
806
- for(std::size_t i=0;i<dataset.size();++i){
807
- refs[i]={string_pool.append(dataset[i]),static_cast<std::uint32_t>(dataset[i].size())};
808
- const auto raw=tokenize_views(dataset[i]);
809
- for(const auto&t:raw) unique_tokens.insert(t.text);
810
- }
811
- token_dict.to_id.reserve(unique_tokens.size()*2+1);
812
- for(const auto&t:unique_tokens) token_dict.intern(t);
813
-
814
- patterns.resize(dataset.size());
815
- #ifdef _OPENMP
816
- #pragma omp parallel for schedule(dynamic,16)
817
- #endif
818
- for(int i=0;i<static_cast<int>(dataset.size());++i)
819
- patterns[static_cast<std::size_t>(i)]=construct_pattern(dataset[static_cast<std::size_t>(i)],refs[static_cast<std::size_t>(i)]);
820
- merge_equivalent_patterns();
821
- build_specialization_tree();
822
- build_pattern_indexes();
823
- }
824
-
825
- // ============================================================================
826
- // Query representation
827
- // ============================================================================
828
- struct Query {
829
- std::string text;
830
- std::vector<std::uint32_t> token_ids;
831
- std::vector<std::uint8_t> kinds;
832
- std::vector<std::uint8_t> shapes;
833
- std::vector<std::uint64_t> morph;
834
- std::vector<std::uint64_t> transitions;
835
- std::vector<std::uint32_t> canonical_ids;
836
- Profile profile;
837
- std::uint64_t relation_hash=0;
838
- std::uint64_t graph_hash=0;
839
- std::uint64_t transition_hash_value=0;
840
- };
841
-
842
- static Query make_query(const std::string& text){
843
- Query q; q.text=text;
844
- analyze_token_sequence(text,q.token_ids,q.kinds,q.shapes,q.morph,q.transitions);
845
- q.canonical_ids=canonicalize(q.token_ids);
846
- q.profile=extract_profile(text);
847
- q.relation_hash=relation_hash(build_relations(q.canonical_ids));
848
- q.graph_hash=graph_hash(build_graph(q.canonical_ids));
849
- q.transition_hash_value=sequence_transition_hash(q.transitions);
850
- return q;
851
- }
852
-
853
- // ============================================================================
854
- // Deterministic scoring
855
- // ============================================================================
856
- struct MatchScore {
857
- std::uint32_t exact=0,structural=0,relational=0,contextual=0,profile=0,length=0,observations=0,id=0;
858
- };
859
-
860
- static bool better_score(const MatchScore&a,const MatchScore&b){
861
- return std::tie(a.exact,a.structural,a.relational,a.contextual,a.profile,a.length,a.observations)>std::tie(b.exact,b.structural,b.relational,b.contextual,b.profile,b.length,b.observations)||
862
- (a.exact==b.exact&&a.structural==b.structural&&a.relational==b.relational&&a.contextual==b.contextual&&a.profile==b.profile&&a.length==b.length&&a.observations==b.observations&&a.id<b.id);
863
- }
864
-
865
- static std::uint32_t profile_score(const Profile&a,const Profile&b){
866
- const double d=profile_distance(a,b); return static_cast<std::uint32_t>(std::max(0.0,1000000.0*(1.0-d)));
867
- }
868
-
869
- static std::uint32_t sequence_exact_prefix(const std::vector<std::uint32_t>&a,const std::vector<std::uint32_t>&b){
870
- const std::size_t n=std::min(a.size(),b.size()); std::uint32_t x=0; for(std::size_t i=0;i<n;++i){if(a[i]!=b[i])break;++x;} return x;
871
- }
872
-
873
- static std::uint32_t common_structure_count(const std::vector<std::uint32_t>&a,const std::vector<std::uint32_t>&b){
874
- const std::size_t n=std::min(a.size(),b.size()); std::uint32_t x=0; for(std::size_t i=0;i<n;++i)if(a[i]==b[i])++x; return x;
875
- }
876
-
877
- struct CandidateAccumulator {
878
- std::vector<std::uint32_t> stamp;
879
- std::vector<MatchScore> score;
880
- std::vector<std::uint32_t> touched;
881
- std::vector<std::uint32_t> anchor_pattern,anchor_query,anchor_lcp;
882
- std::uint32_t epoch=0;
883
- void prepare(std::size_t n){
884
- if(stamp.size()<n){stamp.resize(n,0);score.resize(n);anchor_pattern.resize(n);anchor_query.resize(n);anchor_lcp.resize(n);}
885
- if(++epoch==0){std::fill(stamp.begin(),stamp.end(),0);epoch=1;}
886
- touched.clear();
887
- }
888
- MatchScore& touch(std::uint32_t id){
889
- if(stamp[id]!=epoch){stamp[id]=epoch;score[id]={0,0,0,0,0,0,0,id};anchor_pattern[id]=anchor_query[id]=anchor_lcp[id]=0;touched.push_back(id);}
890
- return score[id];
891
- }
892
- void anchor(std::uint32_t id,std::size_t pattern_off,std::size_t query_off,std::size_t lcp,bool exact_channel){
893
- touch(id);
894
- if(lcp>anchor_lcp[id] || (lcp==anchor_lcp[id]&&exact_channel)){anchor_lcp[id]=static_cast<std::uint32_t>(lcp);anchor_pattern[id]=static_cast<std::uint32_t>(pattern_off);anchor_query[id]=static_cast<std::uint32_t>(query_off);}
895
- }
896
- };
897
-
898
- static void add_csr_structural_hits(const CSRIndex&idx,const std::vector<std::uint64_t>&keys,CandidateAccumulator&c){
899
- for(std::uint64_t key:keys){auto [ptr,count]=idx.find(key);for(std::size_t i=0;i<count;++i)++c.touch(ptr[i]).structural;}
900
- }
901
-
902
- static std::uint32_t consecutive_equal_left(const std::vector<std::uint32_t>&a,std::size_t ai,const std::vector<std::uint32_t>&b,std::size_t bi){
903
- std::uint32_t n=0;while(ai&&bi&&a[ai-1]==b[bi-1]){--ai;--bi;++n;}return n;
904
- }
905
-
906
- static std::uint32_t consecutive_equal_right(const std::vector<std::uint32_t>&a,std::size_t ai,const std::vector<std::uint32_t>&b,std::size_t bi){
907
- const std::size_t na=a.size(),nb=b.size();std::uint32_t n=0;while(ai<na&&bi<nb&&a[ai]==b[bi]){++ai;++bi;++n;}return n;
908
- }
909
-
910
- static std::uint32_t structural_context_score(const Query&q,const Pattern&p,std::size_t qo,std::size_t po,std::size_t lcp){
911
- const std::size_t ql=std::min(qo,q.canonical_ids.size()),pl=std::min(po,p.canonical_ids.size());
912
- const std::uint32_t left=consecutive_equal_left(q.canonical_ids,ql,p.canonical_ids,pl);
913
- const std::size_t qr=std::min(q.canonical_ids.size(),qo+lcp),pr=std::min(p.canonical_ids.size(),po+lcp);
914
- const std::uint32_t right=consecutive_equal_right(q.canonical_ids,qr,p.canonical_ids,pr);
915
- const std::size_t qden=std::max<std::size_t>(1,q.canonical_ids.size()?q.canonical_ids.size()-1:1),pden=std::max<std::size_t>(1,p.canonical_ids.size()?p.canonical_ids.size()-1:1);
916
- const std::uint64_t qpos=(1000000ULL*qo)/qden,ppos=(1000000ULL*po)/pden;
917
- const std::uint32_t position=static_cast<std::uint32_t>(1000000ULL-(qpos>ppos?qpos-ppos:ppos-qpos));
918
- return left*left+right*right+position;
919
- }
920
-
921
- static int shape_distance(std::uint8_t a,std::uint8_t b){ return a==b?0:1; }
922
-
923
- static int substitution_level(const Pattern&p,std::size_t pi,const Query&q,std::size_t qi){
924
- const std::uint32_t s=p.token_ids[pi],t=q.token_ids[qi];
925
- if(s==t) return 3;
926
- const TKind ks=token_kind(token_dict.view(s)),kt=token_kind(token_dict.view(t));
927
- if(variable_candidate(ks)&&variable_candidate(kt)&&ks==kt) return 3;
928
- if(variable_candidate(ks)&&variable_candidate(kt)&&shape_distance(p.shapes[pi],q.shapes[qi])==0) return 2;
929
- if(!variable_candidate(ks)||!variable_candidate(kt)) return -1;
930
- // Structural role: same edge status and same repetition cardinality.
931
- auto role=[&](const std::vector<std::uint32_t>&v,std::size_t i){
932
- std::uint32_t count=0;for(std::uint32_t x:v)if(x==v[i])++count;
933
- const std::uint8_t edge=static_cast<std::uint8_t>((i==0?1:0)|(i+1==v.size()?2:0));
934
- return std::pair<std::uint32_t,std::uint8_t>{count,edge};
935
- };
936
- return role(p.token_ids,pi)==role(q.token_ids,qi)?1:-1;
937
- }
938
-
939
- struct BindingState {
940
- std::unordered_map<std::uint32_t,std::uint32_t> forward;
941
- std::unordered_set<std::uint32_t> used_targets;
942
- void reserve(std::size_t n){forward.reserve(n*2+1);used_targets.reserve(n*2+1);}
943
- };
944
-
945
- static bool add_binding(BindingState&b,std::uint32_t s,std::uint32_t t){
946
- if(s==t) return true;
947
- auto it=b.forward.find(s);
948
- if(it!=b.forward.end()) return it->second==t;
949
- if(!b.used_targets.insert(t).second) return false;
950
- b.forward.emplace(s,t);return true;
951
- }
952
-
953
- struct Alignment {
954
- bool valid=false;std::size_t position=0;BindingState bindings;std::uint32_t exact=0,structural=0,context=0;
955
- };
956
-
957
- static bool align_at(const Pattern&p,const Query&q,std::size_t pos,Alignment&out){
958
- if(pos+q.token_ids.size()>p.token_ids.size()) return false;
959
- Alignment a;a.valid=true;a.position=pos;a.bindings.reserve(q.token_ids.size());
960
- for(std::size_t i=0;i<q.token_ids.size();++i){
961
- const int level=substitution_level(p,pos+i,q,i);
962
- if(level<0) return false;
963
- if(level==3&&p.token_ids[pos+i]==q.token_ids[i]) ++a.exact;
964
- else a.structural+=static_cast<std::uint32_t>(level);
965
- if(p.token_ids[pos+i]!=q.token_ids[i]&&!add_binding(a.bindings,p.token_ids[pos+i],q.token_ids[i])) return false;
966
- }
967
- a.context=static_cast<std::uint32_t>(std::min(pos,p.token_ids.size()-pos-q.token_ids.size()));
968
- out=std::move(a);return true;
969
- }
970
-
971
- static bool better_alignment(const Alignment&a,const Alignment&b){
972
- if(a.exact!=b.exact)return a.exact>b.exact;
973
- if(a.structural!=b.structural)return a.structural>b.structural;
974
- if(a.context!=b.context)return a.context>b.context;
975
- return a.position<b.position;
976
- }
977
-
978
- static bool infer_best_alignment(const Pattern&p,const Query&q,Alignment&best){
979
- if(q.token_ids.empty()||p.token_ids.size()<q.token_ids.size()) return false;
980
- std::size_t pivot=0;int selectivity=-1;
981
- for(std::size_t i=0;i<q.token_ids.size();++i){const TKind k=token_kind(token_dict.view(q.token_ids[i]));const int s=(k==TKind::PUNCT?4:(variable_candidate(k)?1:3));if(s>selectivity){selectivity=s;pivot=i;}}
982
- bool found=false;
983
- for(std::size_t s=pivot;s<p.token_ids.size();++s){const int level=substitution_level(p,s,q,pivot);if(level<0)continue;const std::size_t pos=s-pivot;if(pos+q.token_ids.size()>p.token_ids.size())continue;Alignment a;if(align_at(p,q,pos,a)&&(!found||better_alignment(a,best))){best=std::move(a);found=true;}}
984
- if(!found){
985
- // Shape-aware second pass for unseen token IDs.
986
- for(std::size_t s=pivot;s<p.token_ids.size();++s){if(p.shapes[s]!=q.shapes[pivot])continue;const std::size_t pos=s-pivot;if(pos+q.token_ids.size()>p.token_ids.size())continue;Alignment a;if(align_at(p,q,pos,a)&&(!found||better_alignment(a,best))){best=std::move(a);found=true;}}
987
- }
988
- return found;
989
- }
990
-
991
- static std::vector<MatchScore> search_matches(const Query&q,std::size_t k){
992
- if(patterns.empty()||q.token_ids.empty()||!k) return {};
993
- CandidateAccumulator acc;acc.prepare(patterns.size());
994
- auto collect=[&](const std::vector<SAElem>&sa,SAType type,const std::vector<std::uint32_t>&query,bool exact_channel){
995
- for(std::size_t qi=0;qi<query.size();++qi){
996
- auto [start,end,lcp]=search_sa_longest(sa,patterns,query,qi,type);
997
- for(std::size_t j=start;j<end;++j){const SAElem&e=sa[j];if(exact_channel)acc.touch(e.pat_id).exact+=static_cast<std::uint32_t>(lcp*lcp);else acc.touch(e.pat_id).structural+=static_cast<std::uint32_t>(lcp*lcp);acc.anchor(e.pat_id,e.offset,qi,lcp,exact_channel);}
998
- }
999
- };
1000
- collect(text_sa,SAType::TEXT,q.token_ids,true);
1001
- collect(canonical_sa,SAType::CANONICAL,q.canonical_ids,false);
1002
- const std::vector<std::uint32_t> query_shapes(q.shapes.begin(),q.shapes.end());
1003
- collect(shape_sa,SAType::SHAPE,query_shapes,false);
1004
- add_csr_structural_hits(morph_csr,q.morph,acc);
1005
- if(q.transition_hash_value){auto [p,n]=transition_csr.find(q.transition_hash_value);for(std::size_t i=0;i<n;++i)++acc.touch(p[i]).structural;}
1006
- if(q.relation_hash){auto [p,n]=relation_csr.find(q.relation_hash);for(std::size_t i=0;i<n;++i)++acc.touch(p[i]).relational;}
1007
- if(q.graph_hash){auto [p,n]=graph_csr.find(q.graph_hash);for(std::size_t i=0;i<n;++i)++acc.touch(p[i]).structural;}
1008
- const std::vector<std::uint32_t> seeds=acc.touched;
1009
- std::vector<std::uint32_t> candidate_ids=seeds;
1010
- expand_specialization_candidates(seeds,candidate_ids);
1011
- for(std::uint32_t id:candidate_ids) acc.touch(id);
1012
- std::vector<MatchScore> tmp(acc.touched.size());
1013
- #ifdef _OPENMP
1014
- #pragma omp parallel for schedule(static)
1015
- #endif
1016
- for(int i=0;i<static_cast<int>(acc.touched.size());++i){
1017
- const std::uint32_t id=acc.touched[static_cast<std::size_t>(i)];MatchScore&s=acc.score[id];const Pattern&p=patterns[id];
1018
- s.exact+=sequence_exact_prefix(q.token_ids,p.token_ids);s.structural+=common_structure_count(q.canonical_ids,p.canonical_ids);s.relational+=static_cast<std::uint32_t>(std::min<std::size_t>(p.relations.size(),q.canonical_ids.size()));s.profile=profile_score(q.profile,p.profile);s.length=static_cast<std::uint32_t>(std::min(q.token_ids.size(),p.token_ids.size()));s.observations=p.observations;
1019
- if(id<acc.anchor_pattern.size()&&acc.anchor_lcp[id]) s.contextual=structural_context_score(q,p,acc.anchor_query[id],acc.anchor_pattern[id],acc.anchor_lcp[id]);
1020
- tmp[static_cast<std::size_t>(i)]=s;
1021
- }
1022
- if(tmp.size()>k){std::nth_element(tmp.begin(),tmp.begin()+static_cast<std::ptrdiff_t>(k),tmp.end(),better_score);tmp.resize(k);}
1023
- std::sort(tmp.begin(),tmp.end(),better_score);return tmp;
1024
- }
1025
-
1026
- // ============================================================================
1027
- // Rendering without repeated tokenization
1028
- // ============================================================================
1029
- static std::string render_suffix(const Pattern&p,std::size_t begin,const BindingState&bindings,std::size_t max_tokens){
1030
- if(begin>=p.token_ids.size()) return {};
1031
- const auto raw=tokenize_views(string_pool.view(p.text.offset,p.text.length));
1032
- const std::size_t end=std::min(p.token_ids.size(),begin+max_tokens);
1033
- std::size_t reserve=0;
1034
- for(std::size_t i=begin;i<end;++i) reserve+=raw[i].space_before.size()+raw[i].text.size();
1035
- std::string result; result.reserve(reserve);
1036
- for(std::size_t i=begin;i<end;++i){
1037
- result.append(raw[i].space_before);
1038
- auto it=bindings.forward.find(p.token_ids[i]);
1039
- if(it!=bindings.forward.end()){
1040
- std::string src(token_dict.view(p.token_ids[i]));
1041
- std::string tgt(token_dict.view(it->second));
1042
-
1043
- std::cout << "\n[Approval Required] Apply variable binding '" << src << "' -> '" << tgt << "'? (y/n): ";
1044
- std::string ans;
1045
- if (std::getline(std::cin, ans) && (ans == "y" || ans == "Y")) {
1046
- result.append(tgt);
1047
- } else {
1048
- result.append(raw[i].text);
1049
- }
1050
- }
1051
- else {
1052
- result.append(raw[i].text);
1053
- }
1054
- }
1055
- return result;
1056
- }
1057
-
1058
- static std::string produce_completion(const Query&q,const std::vector<MatchScore>&matches){
1059
- for(const auto&m:matches){
1060
- const Pattern&p=patterns[m.id];
1061
- Alignment a;
1062
- if(!infer_best_alignment(p,q,a)) continue;
1063
- const std::size_t begin=a.position+q.token_ids.size();
1064
- if(begin>=p.token_ids.size()) continue;
1065
- std::string suffix=render_suffix(p,begin,a.bindings,config.max_response_tokens);
1066
- std::string output=q.text;
1067
- output.reserve(q.text.size()+suffix.size());
1068
- output+=suffix;
1069
- return output;
1070
- }
1071
- // Dynamic variable-length suffix fallback utilizing Suffix Array LCP
1072
- if(!q.token_ids.empty()){
1073
- for (std::size_t i = 0; i < q.token_ids.size(); ++i) {
1074
- auto [start,end,lcp]=search_sa_longest(text_sa,patterns,q.token_ids,i,SAType::TEXT);
1075
- if(lcp==q.token_ids.size()-i){
1076
- for(std::size_t j = start; j < end; ++j){
1077
- const Pattern& p = patterns[text_sa[j].pat_id];
1078
- Alignment a;
1079
- if(!infer_best_alignment(p, q, a)) continue;
1080
- const std::size_t begin = a.position + q.token_ids.size();
1081
- if(begin >= p.token_ids.size()) continue;
1082
- std::string suffix = render_suffix(p, begin, a.bindings, config.max_response_tokens);
1083
- if(!suffix.empty()){
1084
- std::string output = q.text;
1085
- output += suffix;
1086
- return output;
1087
- }
1088
- }
1089
- }
1090
- }
1091
- }
1092
- return {};
1093
- }
1094
-
1095
- // ============================================================================
1096
- // Binary persistence
1097
- // ============================================================================
1098
- static constexpr std::uint64_t KB_MAGIC=0x5041545445524E55ULL;
1099
- static constexpr std::uint64_t KB_VERSION=12ULL;
1100
-
1101
- static void write_varuint(std::ostream&os,std::uint64_t v){while(v>=0x80){os.put(static_cast<char>((v&0x7F)|0x80));v>>=7;}os.put(static_cast<char>(v));if(!os)throw std::runtime_error("KB write failed");}
1102
- static std::uint64_t read_varuint(std::istream&is){std::uint64_t v=0;unsigned shift=0;for(;;){unsigned char c=0;if(!is.read(reinterpret_cast<char*>(&c),1))throw std::runtime_error("truncated KB");v|=static_cast<std::uint64_t>(c&0x7F)<<shift;if(!(c&0x80))return v;shift+=7;if(shift>=64)throw std::runtime_error("invalid KB varint");}}
1103
- static void write_float(std::ostream&os,float x){os.write(reinterpret_cast<const char*>(&x),sizeof(x));if(!os)throw std::runtime_error("KB write failed");}
1104
- static float read_float(std::istream&is){float x=0;if(!is.read(reinterpret_cast<char*>(&x),sizeof(x)))throw std::runtime_error("truncated KB");return x;}
1105
- static void write_string(std::ostream&os,std::string_view s){write_varuint(os,s.size());if(!s.empty())os.write(s.data(),static_cast<std::streamsize>(s.size()));}
1106
- static std::string read_string(std::istream&is){const auto n=read_varuint(is);if(n>1024ULL*1024ULL*1024ULL)throw std::runtime_error("KB string too large");std::string s(static_cast<std::size_t>(n),'\0');if(n&&!is.read(s.data(),static_cast<std::streamsize>(n)))throw std::runtime_error("truncated KB");return s;}
1107
-
1108
- static void save_u32_vector(std::ostream&os,const std::vector<std::uint32_t>&v){write_varuint(os,v.size());for(auto x:v)write_varuint(os,x);}
1109
- static void load_u32_vector(std::istream&is,std::vector<std::uint32_t>&v){v.resize(static_cast<std::size_t>(read_varuint(is)));for(auto&x:v)x=static_cast<std::uint32_t>(read_varuint(is));}
1110
- static void save_u8_vector(std::ostream&os,const std::vector<std::uint8_t>&v){write_varuint(os,v.size());for(auto x:v)write_varuint(os,x);}
1111
- static void load_u8_vector(std::istream&is,std::vector<std::uint8_t>&v){v.resize(static_cast<std::size_t>(read_varuint(is)));for(auto&x:v)x=static_cast<std::uint8_t>(read_varuint(is));}
1112
- static void save_u64_vector(std::ostream&os,const std::vector<std::uint64_t>&v){write_varuint(os,v.size());for(auto x:v)write_varuint(os,x);}
1113
- static void load_u64_vector(std::istream&is,std::vector<std::uint64_t>&v){v.resize(static_cast<std::size_t>(read_varuint(is)));for(auto&x:v)x=read_varuint(is);}
1114
- static void save_relations(std::ostream&os,const std::vector<Relation>&v){write_varuint(os,v.size());for(auto x:v){write_varuint(os,x.a);write_varuint(os,x.b);write_varuint(os,static_cast<std::uint32_t>(x.distance));write_varuint(os,x.type);}}
1115
- static void load_relations(std::istream&is,std::vector<Relation>&v){v.resize(static_cast<std::size_t>(read_varuint(is)));for(auto&x:v){x.a=static_cast<std::uint32_t>(read_varuint(is));x.b=static_cast<std::uint32_t>(read_varuint(is));x.distance=static_cast<std::int32_t>(read_varuint(is));x.type=static_cast<std::uint8_t>(read_varuint(is));}}
1116
- static void save_edges(std::ostream&os,const std::vector<Edge>&v){write_varuint(os,v.size());for(auto x:v){write_varuint(os,x.from);write_varuint(os,x.to);write_varuint(os,x.type);}}
1117
- static void load_edges(std::istream&is,std::vector<Edge>&v){v.resize(static_cast<std::size_t>(read_varuint(is)));for(auto&x:v){x.from=static_cast<std::uint32_t>(read_varuint(is));x.to=static_cast<std::uint32_t>(read_varuint(is));x.type=static_cast<std::uint8_t>(read_varuint(is));}}
1118
-
1119
- static bool save_knowledge_base(const std::string&filename){
1120
- const std::string tmp=filename+".tmp";
1121
- try{
1122
- std::ofstream os(tmp.c_str(),std::ios::binary|std::ios::trunc);
1123
- if(!os)throw std::runtime_error("cannot open temporary KB");
1124
- write_varuint(os,KB_MAGIC);write_varuint(os,KB_VERSION);write_varuint(os,config.chunk_size);
1125
- write_varuint(os,token_dict.text.size());
1126
- for(const auto&s:token_dict.text) write_string(os,s);
1127
- write_varuint(os,patterns.size());
1128
- for(const auto&p:patterns){
1129
- write_string(os,string_pool.view(p.text.offset,p.text.length));
1130
- write_float(os,p.profile.distinct_ratio);write_float(os,p.profile.alpha_ratio);write_float(os,p.profile.num_ratio);write_float(os,p.profile.punct_ratio);write_float(os,p.profile.space_ratio);
1131
- save_u32_vector(os,p.token_ids);save_u32_vector(os,p.canonical_ids);save_u8_vector(os,p.kinds);save_u8_vector(os,p.shapes);save_u64_vector(os,p.token_morph_hashes);save_u64_vector(os,p.token_transition_hashes);
1132
- save_relations(os,p.relations);save_edges(os,p.graph);
1133
- write_varuint(os,p.canonical_hash);write_varuint(os,p.graph_hash);write_varuint(os,p.relation_hash);write_varuint(os,p.transition_hash);write_varuint(os,p.observations);write_varuint(os,p.structural_variants);write_varuint(os,p.exact_matches);
1134
- }
1135
- os.flush();if(!os)throw std::runtime_error("KB write failed");os.close();std::remove(filename.c_str());
1136
- if(std::rename(tmp.c_str(),filename.c_str())!=0){std::remove(tmp.c_str());throw std::runtime_error("KB rename failed");}
1137
- return true;
1138
- }catch(const std::exception&e){std::cerr<<"Save error: "<<e.what()<<'\n';std::remove(tmp.c_str());return false;}
1139
- }
1140
-
1141
- static bool load_knowledge_base(const std::string&filename){
1142
- try{
1143
- std::ifstream is(filename.c_str(),std::ios::binary);if(!is)throw std::runtime_error("cannot open KB");
1144
- if(read_varuint(is)!=KB_MAGIC)throw std::runtime_error("invalid KB magic");
1145
- if(read_varuint(is)!=KB_VERSION)throw std::runtime_error("unsupported KB version");
1146
- config.chunk_size=static_cast<std::size_t>(read_varuint(is));
1147
- token_dict.clear();string_pool.clear();patterns.clear();
1148
- const std::size_t token_count=static_cast<std::size_t>(read_varuint(is));token_dict.to_id.reserve(token_count*2+1);
1149
- for(std::size_t i=0;i<token_count;++i) token_dict.intern(read_string(is));
1150
- const std::size_t count=static_cast<std::size_t>(read_varuint(is));if(count>100000000ULL)throw std::runtime_error("unreasonable KB pattern count");
1151
- patterns.resize(count);
1152
- for(Pattern&p:patterns){
1153
- const std::string s=read_string(is);const std::uint32_t off=string_pool.append(s);p.text={off,static_cast<std::uint32_t>(s.size())};
1154
- p.profile.distinct_ratio=read_float(is);p.profile.alpha_ratio=read_float(is);p.profile.num_ratio=read_float(is);p.profile.punct_ratio=read_float(is);p.profile.space_ratio=read_float(is);
1155
- load_u32_vector(is,p.token_ids);load_u32_vector(is,p.canonical_ids);load_u8_vector(is,p.kinds);load_u8_vector(is,p.shapes);load_u64_vector(is,p.token_morph_hashes);load_u64_vector(is,p.token_transition_hashes);
1156
- load_relations(is,p.relations);load_edges(is,p.graph);
1157
- p.canonical_hash=read_varuint(is);p.graph_hash=read_varuint(is);p.relation_hash=read_varuint(is);p.transition_hash=read_varuint(is);p.observations=static_cast<std::uint32_t>(read_varuint(is));p.structural_variants=static_cast<std::uint32_t>(read_varuint(is));p.exact_matches=static_cast<std::uint32_t>(read_varuint(is));
1158
- }
1159
- build_pattern_indexes();build_specialization_tree();
1160
- std::cerr<<"Loaded "<<patterns.size()<<" pattern(s), "<<token_dict.text.size()<<" token type(s)\n";
1161
- return true;
1162
- }catch(const std::exception&e){std::cerr<<"Load error: "<<e.what()<<'\n';return false;}
1163
- }
1164
-
1165
- static bool print_kb_info(const std::string&filename){
1166
- try{std::ifstream is(filename.c_str(),std::ios::binary);if(!is)throw std::runtime_error("cannot open KB");if(read_varuint(is)!=KB_MAGIC)throw std::runtime_error("invalid KB magic");
1167
- const auto version=read_varuint(is),chunk=read_varuint(is);const auto tokens=read_varuint(is);std::cout<<"KB: "<<filename<<"\nVersion: "<<version<<"\nChunk size: "<<chunk<<"\nToken types: "<<tokens<<'\n';return true;
1168
- }catch(const std::exception&e){std::cerr<<"KB info error: "<<e.what()<<'\n';return false;}
1169
- }
1170
-
1171
- // ============================================================================
1172
- // CLI
1173
- // ============================================================================
1174
- struct CommandLine{std::vector<std::string>learn_files;std::string load_kb,save_kb,info_kb,prompt,retrieve;bool help=false,chat=false,show_retrieval=false;};
1175
-
1176
- static bool parse_size(const std::string&s,std::size_t&out){try{std::size_t pos=0;const auto n=std::stoull(s,&pos,10);if(pos!=s.size()||!n)return false;out=static_cast<std::size_t>(n);return true;}catch(...){return false;}}
1177
-
1178
- static void print_help(const char* program) {
1179
- std::cout << "\n" << program << "\n"
1180
- << "================================\n\n"
1181
- << "Learning:\n"
1182
- << " --learn FILE [FILE ...] Ingest text files into the knowledge base.\n"
1183
- << " --chunk-size N Bytes per processed chunk.\n\n"
1184
- << "Knowledge base:\n"
1185
- << " --save-kb FILE Save the current knowledge base to disk.\n"
1186
- << " --load-kb FILE Load an existing knowledge base.\n"
1187
- << " --info-kb FILE Display metadata about a saved knowledge base.\n\n"
1188
- << "Retrieval:\n"
1189
- << " --top-k N Number of matching archetypes to retrieve.\n"
1190
- << " --retrieve TEXT Retrieve and display top-k patterns for a prompt.\n"
1191
- << " --show-retrieval Print retrieved patterns before generating a response.\n\n"
1192
- << "Interaction:\n"
1193
- << " --max-length N Maximum number of tokens to generate.\n"
1194
- << " --prompt TEXT Generate a single response to the provided text.\n"
1195
- << " --chat Interactive chat mode.\n"
1196
- << " * Multiline input; terminate with /end;\n"
1197
- << " * Exit with /quit\n\n"
1198
- << "Other:\n"
1199
- << " --help Display this help menu.\n\n";
1200
- }
1201
-
1202
- static bool parse_command_line(int argc,char**argv,CommandLine&cmd){
1203
- for(int i=1;i<argc;++i){
1204
- const std::string a=argv[i];
1205
- if(a=="--help"||a=="-h"){cmd.help=true;continue;}
1206
- if(a=="--chat"){cmd.chat=true;continue;}
1207
- if(a=="--show-retrieval"){cmd.show_retrieval=true;continue;}
1208
- if(a=="--learn"){if(i+1>=argc)return false;while(i+1<argc&&argv[i+1][0]!='-')cmd.learn_files.push_back(argv[++i]);continue;}
1209
- if(a=="--load-kb"){if(i+1>=argc)return false;cmd.load_kb=argv[++i];continue;}
1210
- if(a=="--save-kb"){if(i+1>=argc)return false;cmd.save_kb=argv[++i];continue;}
1211
- if(a=="--info-kb"){if(i+1>=argc)return false;cmd.info_kb=argv[++i];continue;}
1212
- if(a=="--retrieve"){if(i+1>=argc)return false;cmd.retrieve=argv[++i];continue;}
1213
- if(a=="--prompt"){if(i+1>=argc)return false;cmd.prompt=argv[++i];continue;}
1214
- if(a=="--top-k"){if(i+1>=argc||!parse_size(argv[++i],config.top_k))return false;continue;}
1215
- if(a=="--max-length"){if(i+1>=argc||!parse_size(argv[++i],config.max_response_tokens))return false;continue;}
1216
- if(a=="--chunk-size"){if(i+1>=argc||!parse_size(argv[++i],config.chunk_size))return false;continue;}
1217
- std::cerr<<"Unknown option: "<<a<<'\n';return false;
1218
- }
1219
- return true;
1220
- }
1221
-
1222
- static void print_retrieval(const std::vector<MatchScore>&results){
1223
- std::cout<<"\nRetrieved structural patterns:\n";
1224
- if(results.empty()){std::cout<<" None\n";return;}
1225
- for(std::size_t i=0;i<results.size();++i){
1226
- const auto&m=results[i];
1227
- std::cout<<"\nRank "<<i+1<<"\nExact: "<<m.exact<<"\nStructural: "<<m.structural<<"\nRelational: "<<m.relational<<"\nContextual: "<<m.contextual<<"\nProfile: "<<m.profile<<"\nObservations: "<<m.observations<<"\nPattern:\n"<<string_pool.view(patterns[m.id].text.offset,patterns[m.id].text.length)<<'\n';
1228
- }
1229
- }
1230
-
1231
- static void process_prompt(const std::string&prompt){
1232
- if(prompt.empty()||patterns.empty())return;
1233
- const Query q=make_query(prompt);
1234
- const auto results=search_matches(q,config.top_k);
1235
- if(config.show_retrieval)print_retrieval(results);
1236
- const std::string response=produce_completion(q,results);
1237
- std::cout<<(response.empty()?"No deterministic completion found.\n":response+'\n');
1238
- }
1239
-
1240
- static void retrieve_only(const std::string&query){if(patterns.empty())return;print_retrieval(search_matches(make_query(query),config.top_k));}
1241
-
1242
- static void chat_loop(){
1243
- std::cout<<"\nPattern Constructor Chat\n====================================\nFinish prompt with /end\nExit with /quit\n\n";
1244
- std::string prompt,line;
1245
- while(true){
1246
- std::cout<<"> "<<std::flush;
1247
- if(!std::getline(std::cin,line))break;
1248
- if(line=="/quit")break;
1249
- if(line=="/end"){if(!prompt.empty()){process_prompt(prompt);std::cout<<'\n';}prompt.clear();continue;}
1250
- if(!prompt.empty()) prompt.push_back('\n');
1251
- prompt+=line;
1252
- }
1253
- }
1254
-
1255
- int main(int argc,char**argv){
1256
- #ifdef _WIN32
1257
- SetConsoleCP(65001);SetConsoleOutputCP(65001);
1258
- #endif
1259
- CommandLine cmd;
1260
- if(!parse_command_line(argc,argv,cmd)){print_help(argv[0]);return 1;}
1261
- if(cmd.help){print_help(argv[0]);return 0;}
1262
- config.show_retrieval=cmd.show_retrieval;
1263
- config.chat_mode=cmd.chat;
1264
- if(!cmd.info_kb.empty()){return print_kb_info(cmd.info_kb)?0:1;}
1265
-
1266
- if(!cmd.load_kb.empty()){
1267
- std::cerr<<"Loading knowledge base: "<<cmd.load_kb<<'\n';
1268
- if(!load_knowledge_base(cmd.load_kb))return 1;
1269
- }
1270
-
1271
- if(!cmd.learn_files.empty()){
1272
- std::vector<std::string> dataset;
1273
- load_learning_files(cmd.learn_files,dataset);
1274
- const std::size_t before=dataset.size();
1275
- deduplicate(dataset);
1276
- std::cerr<<"Removed "<<before-dataset.size()<<" duplicate pattern(s)\n";
1277
- build_knowledge_base(dataset);
1278
- }
1279
-
1280
- std::cout<<"Knowledge base size: "<<patterns.size()<<'\n'
1281
- <<"Unique token types: "<<token_dict.text.size()<<'\n'
1282
- <<"Chunk size: "<<config.chunk_size<<" bytes\n"
1283
- <<"Top-K: "<<config.top_k<<'\n'
1284
- <<"Maximum generated tokens: "<<config.max_response_tokens<<'\n'
1285
- <<"OpenMP threads available: "<<omp_get_max_threads()<<'\n';
1286
-
1287
- if(!cmd.save_kb.empty()){
1288
- std::cerr<<"Saving knowledge base: "<<cmd.save_kb<<'\n';
1289
- if(!save_knowledge_base(cmd.save_kb))return 1;
1290
- }
1291
- if(!cmd.retrieve.empty()){retrieve_only(cmd.retrieve);return 0;}
1292
- if(!cmd.prompt.empty()){process_prompt(cmd.prompt);return 0;}
1293
- if(cmd.chat){chat_loop();return 0;}
1294
- std::cout<<(patterns.empty()?"\nNo knowledge base loaded. Use --learn, --load-kb, or --help.\n":"\nKnowledge base loaded successfully. Use --retrieve, --prompt, or --chat.\n");
1295
- return 0;
1296
- }