technician1 commited on
Commit
000a8e4
·
verified ·
1 Parent(s): 1785b63

Delete pattern_constructor_h.cpp

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