code wiki / _hdl_build / nx_library_search.nx

nx_library_search.nx source

↩ module page · 94 lines · 4889 B

1// nx_library_search.nx -- S-class search/retrieval over a LIBRARY of information (not one source). 2// The Researcher recognizes an index page as a LIBRARY, harvests every doc it lists, then answers a 3// SEARCH PROMPT by RANKING the whole corpus -- TF-IDF style, which exceeds naive substring search: 4// * substring search can only say present/absent in one blob and cannot rank or pick the best doc 5// * this ranks N docs by sum over query terms of tf(term,doc) * idf(term), where RARER terms (low 6// document-frequency) weigh MORE -- so a doc that matches the distinctive query terms wins over a 7// doc that merely mentions a common one. That is the retrieval quality real search engines use. 8// Corpus is held as parallel arrays: ptrs[k] = (doc text *u8 stored as i64), lens[k] = its length. 9// license_tier: ORIGINAL Pairs with nx_research_extract (re_count/re_has/re_find) + nx_library_cache. 10 11import "nx_research_extract.nx" // re_count / re_has / re_find / re_strlen 12import "nx_bm25.nx" // the Engineer-wired production ranker (supersedes ls_best's TF-IDF) 13import "nx_syscalls.nx" 14 15// ---- library detection + harvest (read the index, follow each entry) ---------------------------- 16 17// is this page a LIBRARY (an index of many docs) rather than a single source? >=3 "[doc]" entries. 18func ls_is_library(text: *u8, n: i64) -> i64 { if re_count(text, n, "[doc] " as *u8) >= 3 { return 1 } return 0 } 19func ls_entry_count(text: *u8, n: i64) -> i64 { return re_count(text, n, "[doc] " as *u8) } 20 21// write the k-th doc PATH listed in the index into out[0..cap); return its length or -1. 22// entries look like: [doc] /abandonment Abandonment and load time research. 23func ls_entry_path(text: *u8, n: i64, k: i64, out: *u8, cap: i64) -> i64 { 24 let mlen: i64 = 6 // strlen("[doc] ") 25 var start: i64 = 0; var cnt: i64 = 0; var found: i64 = 0 - 1; var searching: i64 = 1 26 while searching == 1 { 27 let rel: i64 = re_find(text + start, n - start, "[doc] " as *u8) 28 if rel < 0 { searching = 0 } else { 29 let at: i64 = start + rel 30 if cnt == k { found = at + mlen; searching = 0 } else { cnt = cnt + 1; start = at + mlen } 31 } 32 } 33 if found < 0 { return 0 - 1 } 34 var i: i64 = found; var j: i64 = 0; var copying: i64 = 1 35 while copying == 1 { 36 if i >= n { copying = 0 } else { 37 let c: i64 = text[i] 38 if c == 32 { copying = 0 } else { if c == 10 { copying = 0 } else { if j < cap { out[j] = c; j = j + 1 } i = i + 1 } } 39 } 40 } 41 return j 42} 43 44// ---- ranked retrieval over the corpus ----------------------------------------------------------- 45 46func ls_doc(ptrs: *i64, k: i64) -> *u8 { return ptrs[k] as *u8 } 47 48// term frequency: occurrences of `term` in doc k. 49func ls_tf(ptrs: *i64, lens: *i64, k: i64, term: *u8) -> i64 { return re_count(ls_doc(ptrs, k), lens[k], term) } 50 51// document frequency: in how many of the ndocs docs does `term` appear at least once? 52func ls_df(ptrs: *i64, lens: *i64, ndocs: i64, term: *u8) -> i64 { 53 var c: i64 = 0; var i: i64 = 0 54 while i < ndocs { if re_has(ls_doc(ptrs, i), lens[i], term) == 1 { c = c + 1 } i = i + 1 } 55 return c 56} 57 58// idf-lite: rarer term => higher weight. w = ndocs - df + 1 (df>=1 when present). 59func ls_idf(ndocs: i64, df: i64) -> i64 { return ndocs - df + 1 } 60 61// score doc k for a query of nq terms (qterms[t] = a *u8 needle stored as i64). 62func ls_score(ptrs: *i64, lens: *i64, ndocs: i64, k: i64, qterms: *i64, nq: i64) -> i64 { 63 var s: i64 = 0; var t: i64 = 0 64 while t < nq { 65 let term: *u8 = qterms[t] as *u8 66 let tf: i64 = ls_tf(ptrs, lens, k, term) 67 if tf > 0 { s = s + tf * ls_idf(ndocs, ls_df(ptrs, lens, ndocs, term)) } 68 t = t + 1 69 } 70 return s 71} 72 73// the best-ranked doc index for the query (-1 if no doc scores > 0). [original TF-IDF ranker] 74func ls_best(ptrs: *i64, lens: *i64, ndocs: i64, qterms: *i64, nq: i64) -> i64 { 75 var best: i64 = 0 - 1; var bestscore: i64 = 0; var i: i64 = 0 76 while i < ndocs { 77 let sc: i64 = ls_score(ptrs, lens, ndocs, i, qterms, nq) 78 if sc > bestscore { bestscore = sc; best = i } 79 i = i + 1 80 } 81 return best 82} 83 84// the Engineer-WIRED upgrade: same interface as ls_best, but ranks with production BM25 (tf-saturation 85// + length-normalization). Computes doc lengths from the corpus so it is a true drop-in replacement. 86func ls_best_bm25(ptrs: *i64, lens: *i64, ndocs: i64, qterms: *i64, nq: i64) -> i64 { 87 let dls: *i64 = sys_mmap(8 * 256) as *i64 88 var total: i64 = 0; var i: i64 = 0 89 while i < ndocs { dls[i] = bm_token_count(ptrs[i] as *u8, lens[i]); total = total + dls[i]; i = i + 1 } 90 var avgdl: i64 = 1 91 if ndocs > 0 { avgdl = total / ndocs } 92 if avgdl <= 0 { avgdl = 1 } 93 return bm_best(ptrs, lens, dls, ndocs, avgdl, qterms, nq) 94}