Delete pattern_constructor_q.cpp
Browse files- 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)],<=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()&¤t.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 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|