engine_test.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  1. package engine
  2. import (
  3. "encoding/gob"
  4. "github.com/huichen/wukong/types"
  5. "github.com/huichen/wukong/utils"
  6. "os"
  7. "reflect"
  8. "testing"
  9. )
  10. type ScoringFields struct {
  11. A, B, C float32
  12. }
  13. func AddDocs(engine *Engine) {
  14. docId := uint64(1)
  15. // 因为需要保证文档全部被加入到索引中,所以 forceUpdate 全部设置成 true
  16. engine.IndexDocument(docId, types.DocumentIndexData{
  17. Content: "中国有十三亿人口人口",
  18. Fields: ScoringFields{1, 2, 3},
  19. }, false)
  20. docId++
  21. engine.IndexDocument(docId, types.DocumentIndexData{
  22. Content: "中国人口",
  23. Fields: nil,
  24. }, false)
  25. docId++
  26. engine.IndexDocument(docId, types.DocumentIndexData{
  27. Content: "有人口",
  28. Fields: ScoringFields{2, 3, 1},
  29. }, false)
  30. docId++
  31. engine.IndexDocument(docId, types.DocumentIndexData{
  32. Content: "有十三亿人口",
  33. Fields: ScoringFields{2, 3, 3},
  34. }, false)
  35. docId++
  36. engine.IndexDocument(docId, types.DocumentIndexData{
  37. Content: "中国十三亿人口",
  38. Fields: ScoringFields{0, 9, 1},
  39. }, false)
  40. engine.FlushIndex()
  41. }
  42. type RankByTokenProximity struct {
  43. }
  44. func (rule RankByTokenProximity) Score(
  45. doc types.IndexedDocument, fields interface{}) []float32 {
  46. if doc.TokenProximity < 0 {
  47. return []float32{}
  48. }
  49. return []float32{1.0 / (float32(doc.TokenProximity) + 1)}
  50. }
  51. func TestEngineIndexDocument(t *testing.T) {
  52. var engine Engine
  53. engine.Init(types.EngineInitOptions{
  54. SegmenterDictionaries: "../testdata/test_dict.txt",
  55. DefaultRankOptions: &types.RankOptions{
  56. OutputOffset: 0,
  57. MaxOutputs: 10,
  58. ScoringCriteria: &RankByTokenProximity{},
  59. },
  60. IndexerInitOptions: &types.IndexerInitOptions{
  61. IndexType: types.LocationsIndex,
  62. },
  63. })
  64. AddDocs(&engine)
  65. outputs := engine.Search(types.SearchRequest{Text: "中国人口"})
  66. utils.Expect(t, "2", len(outputs.Tokens))
  67. utils.Expect(t, "中国", outputs.Tokens[0])
  68. utils.Expect(t, "人口", outputs.Tokens[1])
  69. utils.Expect(t, "3", len(outputs.Docs))
  70. utils.Expect(t, "2", outputs.Docs[0].DocId)
  71. utils.Expect(t, "1000", int(outputs.Docs[0].Scores[0]*1000))
  72. utils.Expect(t, "[0 6]", outputs.Docs[0].TokenSnippetLocations)
  73. utils.Expect(t, "5", outputs.Docs[1].DocId)
  74. utils.Expect(t, "100", int(outputs.Docs[1].Scores[0]*1000))
  75. utils.Expect(t, "[0 15]", outputs.Docs[1].TokenSnippetLocations)
  76. utils.Expect(t, "1", outputs.Docs[2].DocId)
  77. utils.Expect(t, "76", int(outputs.Docs[2].Scores[0]*1000))
  78. utils.Expect(t, "[0 18]", outputs.Docs[2].TokenSnippetLocations)
  79. }
  80. func TestReverseOrder(t *testing.T) {
  81. var engine Engine
  82. engine.Init(types.EngineInitOptions{
  83. SegmenterDictionaries: "../testdata/test_dict.txt",
  84. DefaultRankOptions: &types.RankOptions{
  85. ReverseOrder: true,
  86. OutputOffset: 0,
  87. MaxOutputs: 10,
  88. ScoringCriteria: &RankByTokenProximity{},
  89. },
  90. IndexerInitOptions: &types.IndexerInitOptions{
  91. IndexType: types.LocationsIndex,
  92. },
  93. })
  94. AddDocs(&engine)
  95. outputs := engine.Search(types.SearchRequest{Text: "中国人口"})
  96. utils.Expect(t, "3", len(outputs.Docs))
  97. utils.Expect(t, "1", outputs.Docs[0].DocId)
  98. utils.Expect(t, "5", outputs.Docs[1].DocId)
  99. utils.Expect(t, "2", outputs.Docs[2].DocId)
  100. }
  101. func TestOffsetAndMaxOutputs(t *testing.T) {
  102. var engine Engine
  103. engine.Init(types.EngineInitOptions{
  104. SegmenterDictionaries: "../testdata/test_dict.txt",
  105. DefaultRankOptions: &types.RankOptions{
  106. ReverseOrder: true,
  107. OutputOffset: 1,
  108. MaxOutputs: 3,
  109. ScoringCriteria: &RankByTokenProximity{},
  110. },
  111. IndexerInitOptions: &types.IndexerInitOptions{
  112. IndexType: types.LocationsIndex,
  113. },
  114. })
  115. AddDocs(&engine)
  116. outputs := engine.Search(types.SearchRequest{Text: "中国人口"})
  117. utils.Expect(t, "2", len(outputs.Docs))
  118. utils.Expect(t, "5", outputs.Docs[0].DocId)
  119. utils.Expect(t, "2", outputs.Docs[1].DocId)
  120. }
  121. type TestScoringCriteria struct {
  122. }
  123. func (criteria TestScoringCriteria) Score(
  124. doc types.IndexedDocument, fields interface{}) []float32 {
  125. if reflect.TypeOf(fields) != reflect.TypeOf(ScoringFields{}) {
  126. return []float32{}
  127. }
  128. fs := fields.(ScoringFields)
  129. return []float32{float32(doc.TokenProximity)*fs.A + fs.B*fs.C}
  130. }
  131. func TestSearchWithCriteria(t *testing.T) {
  132. var engine Engine
  133. engine.Init(types.EngineInitOptions{
  134. SegmenterDictionaries: "../testdata/test_dict.txt",
  135. DefaultRankOptions: &types.RankOptions{
  136. ScoringCriteria: TestScoringCriteria{},
  137. },
  138. IndexerInitOptions: &types.IndexerInitOptions{
  139. IndexType: types.LocationsIndex,
  140. },
  141. })
  142. AddDocs(&engine)
  143. outputs := engine.Search(types.SearchRequest{Text: "中国人口"})
  144. utils.Expect(t, "2", len(outputs.Docs))
  145. utils.Expect(t, "1", outputs.Docs[0].DocId)
  146. utils.Expect(t, "18000", int(outputs.Docs[0].Scores[0]*1000))
  147. utils.Expect(t, "5", outputs.Docs[1].DocId)
  148. utils.Expect(t, "9000", int(outputs.Docs[1].Scores[0]*1000))
  149. }
  150. func TestCompactIndex(t *testing.T) {
  151. var engine Engine
  152. engine.Init(types.EngineInitOptions{
  153. SegmenterDictionaries: "../testdata/test_dict.txt",
  154. DefaultRankOptions: &types.RankOptions{
  155. ScoringCriteria: TestScoringCriteria{},
  156. },
  157. })
  158. AddDocs(&engine)
  159. outputs := engine.Search(types.SearchRequest{Text: "中国人口"})
  160. utils.Expect(t, "2", len(outputs.Docs))
  161. utils.Expect(t, "5", outputs.Docs[0].DocId)
  162. utils.Expect(t, "9000", int(outputs.Docs[0].Scores[0]*1000))
  163. utils.Expect(t, "1", outputs.Docs[1].DocId)
  164. utils.Expect(t, "6000", int(outputs.Docs[1].Scores[0]*1000))
  165. }
  166. type BM25ScoringCriteria struct {
  167. }
  168. func (criteria BM25ScoringCriteria) Score(
  169. doc types.IndexedDocument, fields interface{}) []float32 {
  170. if reflect.TypeOf(fields) != reflect.TypeOf(ScoringFields{}) {
  171. return []float32{}
  172. }
  173. return []float32{doc.BM25}
  174. }
  175. func TestFrequenciesIndex(t *testing.T) {
  176. var engine Engine
  177. engine.Init(types.EngineInitOptions{
  178. SegmenterDictionaries: "../testdata/test_dict.txt",
  179. DefaultRankOptions: &types.RankOptions{
  180. ScoringCriteria: BM25ScoringCriteria{},
  181. },
  182. IndexerInitOptions: &types.IndexerInitOptions{
  183. IndexType: types.FrequenciesIndex,
  184. },
  185. })
  186. AddDocs(&engine)
  187. outputs := engine.Search(types.SearchRequest{Text: "中国人口"})
  188. utils.Expect(t, "2", len(outputs.Docs))
  189. utils.Expect(t, "5", outputs.Docs[0].DocId)
  190. utils.Expect(t, "2349", int(outputs.Docs[0].Scores[0]*1000))
  191. utils.Expect(t, "1", outputs.Docs[1].DocId)
  192. utils.Expect(t, "2320", int(outputs.Docs[1].Scores[0]*1000))
  193. }
  194. func TestRemoveDocument(t *testing.T) {
  195. var engine Engine
  196. engine.Init(types.EngineInitOptions{
  197. SegmenterDictionaries: "../testdata/test_dict.txt",
  198. DefaultRankOptions: &types.RankOptions{
  199. ScoringCriteria: TestScoringCriteria{},
  200. },
  201. })
  202. AddDocs(&engine)
  203. engine.RemoveDocument(5, false)
  204. engine.RemoveDocument(6, true)
  205. engine.FlushIndex()
  206. engine.IndexDocument(6, types.DocumentIndexData{
  207. Content: "中国人口有十三亿",
  208. Fields: ScoringFields{0, 9, 1},
  209. }, false)
  210. engine.FlushIndex()
  211. outputs := engine.Search(types.SearchRequest{Text: "中国人口"})
  212. utils.Expect(t, "2", len(outputs.Docs))
  213. utils.Expect(t, "6", outputs.Docs[0].DocId)
  214. utils.Expect(t, "9000", int(outputs.Docs[0].Scores[0]*1000))
  215. utils.Expect(t, "1", outputs.Docs[1].DocId)
  216. utils.Expect(t, "6000", int(outputs.Docs[1].Scores[0]*1000))
  217. }
  218. func TestEngineIndexDocumentWithTokens(t *testing.T) {
  219. var engine Engine
  220. engine.Init(types.EngineInitOptions{
  221. SegmenterDictionaries: "../testdata/test_dict.txt",
  222. DefaultRankOptions: &types.RankOptions{
  223. OutputOffset: 0,
  224. MaxOutputs: 10,
  225. ScoringCriteria: &RankByTokenProximity{},
  226. },
  227. IndexerInitOptions: &types.IndexerInitOptions{
  228. IndexType: types.LocationsIndex,
  229. },
  230. })
  231. docId := uint64(1)
  232. engine.IndexDocument(docId, types.DocumentIndexData{
  233. Content: "",
  234. Tokens: []types.TokenData{
  235. {"中国", []int{0}},
  236. {"人口", []int{18, 24}},
  237. },
  238. Fields: ScoringFields{1, 2, 3},
  239. }, true)
  240. docId++
  241. engine.IndexDocument(docId, types.DocumentIndexData{
  242. Content: "",
  243. Tokens: []types.TokenData{
  244. {"中国", []int{0}},
  245. {"人口", []int{6}},
  246. },
  247. Fields: ScoringFields{1, 2, 3},
  248. }, true)
  249. docId++
  250. engine.IndexDocument(docId, types.DocumentIndexData{
  251. Content: "中国十三亿人口",
  252. Fields: ScoringFields{0, 9, 1},
  253. }, true)
  254. engine.FlushIndex()
  255. outputs := engine.Search(types.SearchRequest{Text: "中国人口"})
  256. utils.Expect(t, "2", len(outputs.Tokens))
  257. utils.Expect(t, "中国", outputs.Tokens[0])
  258. utils.Expect(t, "人口", outputs.Tokens[1])
  259. utils.Expect(t, "3", len(outputs.Docs))
  260. utils.Expect(t, "2", outputs.Docs[0].DocId)
  261. utils.Expect(t, "1000", int(outputs.Docs[0].Scores[0]*1000))
  262. utils.Expect(t, "[0 6]", outputs.Docs[0].TokenSnippetLocations)
  263. utils.Expect(t, "3", outputs.Docs[1].DocId)
  264. utils.Expect(t, "100", int(outputs.Docs[1].Scores[0]*1000))
  265. utils.Expect(t, "[0 15]", outputs.Docs[1].TokenSnippetLocations)
  266. utils.Expect(t, "1", outputs.Docs[2].DocId)
  267. utils.Expect(t, "76", int(outputs.Docs[2].Scores[0]*1000))
  268. utils.Expect(t, "[0 18]", outputs.Docs[2].TokenSnippetLocations)
  269. }
  270. func TestEngineIndexDocumentWithPersistentStorage(t *testing.T) {
  271. gob.Register(ScoringFields{})
  272. var engine Engine
  273. engine.Init(types.EngineInitOptions{
  274. SegmenterDictionaries: "../testdata/test_dict.txt",
  275. DefaultRankOptions: &types.RankOptions{
  276. OutputOffset: 0,
  277. MaxOutputs: 10,
  278. ScoringCriteria: &RankByTokenProximity{},
  279. },
  280. IndexerInitOptions: &types.IndexerInitOptions{
  281. IndexType: types.LocationsIndex,
  282. },
  283. UsePersistentStorage: true,
  284. PersistentStorageFolder: "wukong.persistent",
  285. PersistentStorageShards: 2,
  286. })
  287. AddDocs(&engine)
  288. engine.RemoveDocument(5, true)
  289. engine.Close()
  290. var engine1 Engine
  291. engine1.Init(types.EngineInitOptions{
  292. SegmenterDictionaries: "../testdata/test_dict.txt",
  293. DefaultRankOptions: &types.RankOptions{
  294. OutputOffset: 0,
  295. MaxOutputs: 10,
  296. ScoringCriteria: &RankByTokenProximity{},
  297. },
  298. IndexerInitOptions: &types.IndexerInitOptions{
  299. IndexType: types.LocationsIndex,
  300. },
  301. UsePersistentStorage: true,
  302. PersistentStorageFolder: "wukong.persistent",
  303. PersistentStorageShards: 2,
  304. })
  305. engine1.FlushIndex()
  306. outputs := engine1.Search(types.SearchRequest{Text: "中国人口"})
  307. utils.Expect(t, "2", len(outputs.Tokens))
  308. utils.Expect(t, "中国", outputs.Tokens[0])
  309. utils.Expect(t, "人口", outputs.Tokens[1])
  310. utils.Expect(t, "2", len(outputs.Docs))
  311. utils.Expect(t, "2", outputs.Docs[0].DocId)
  312. utils.Expect(t, "1000", int(outputs.Docs[0].Scores[0]*1000))
  313. utils.Expect(t, "[0 6]", outputs.Docs[0].TokenSnippetLocations)
  314. utils.Expect(t, "1", outputs.Docs[1].DocId)
  315. utils.Expect(t, "76", int(outputs.Docs[1].Scores[0]*1000))
  316. utils.Expect(t, "[0 18]", outputs.Docs[1].TokenSnippetLocations)
  317. engine1.Close()
  318. os.RemoveAll("wukong.persistent")
  319. }
  320. func TestCountDocsOnly(t *testing.T) {
  321. var engine Engine
  322. engine.Init(types.EngineInitOptions{
  323. SegmenterDictionaries: "../testdata/test_dict.txt",
  324. DefaultRankOptions: &types.RankOptions{
  325. ReverseOrder: true,
  326. OutputOffset: 0,
  327. MaxOutputs: 1,
  328. ScoringCriteria: &RankByTokenProximity{},
  329. },
  330. IndexerInitOptions: &types.IndexerInitOptions{
  331. IndexType: types.LocationsIndex,
  332. },
  333. })
  334. AddDocs(&engine)
  335. engine.RemoveDocument(5, true)
  336. engine.FlushIndex()
  337. outputs := engine.Search(types.SearchRequest{Text: "中国人口", CountDocsOnly: true})
  338. utils.Expect(t, "0", len(outputs.Docs))
  339. utils.Expect(t, "2", len(outputs.Tokens))
  340. utils.Expect(t, "2", outputs.NumDocs)
  341. }
  342. func TestSearchWithin(t *testing.T) {
  343. var engine Engine
  344. engine.Init(types.EngineInitOptions{
  345. SegmenterDictionaries: "../testdata/test_dict.txt",
  346. DefaultRankOptions: &types.RankOptions{
  347. ReverseOrder: true,
  348. OutputOffset: 0,
  349. MaxOutputs: 10,
  350. ScoringCriteria: &RankByTokenProximity{},
  351. },
  352. IndexerInitOptions: &types.IndexerInitOptions{
  353. IndexType: types.LocationsIndex,
  354. },
  355. })
  356. AddDocs(&engine)
  357. docIds := make(map[uint64]bool)
  358. docIds[5] = true
  359. docIds[1] = true
  360. outputs := engine.Search(types.SearchRequest{
  361. Text: "中国人口",
  362. DocIds: docIds,
  363. })
  364. utils.Expect(t, "2", len(outputs.Tokens))
  365. utils.Expect(t, "中国", outputs.Tokens[0])
  366. utils.Expect(t, "人口", outputs.Tokens[1])
  367. utils.Expect(t, "2", len(outputs.Docs))
  368. utils.Expect(t, "1", outputs.Docs[0].DocId)
  369. utils.Expect(t, "76", int(outputs.Docs[0].Scores[0]*1000))
  370. utils.Expect(t, "[0 18]", outputs.Docs[0].TokenSnippetLocations)
  371. utils.Expect(t, "5", outputs.Docs[1].DocId)
  372. utils.Expect(t, "100", int(outputs.Docs[1].Scores[0]*1000))
  373. utils.Expect(t, "[0 15]", outputs.Docs[1].TokenSnippetLocations)
  374. }
  375. func TestLookupWithLocations1(t *testing.T) {
  376. type Data struct {
  377. Id int
  378. Content string
  379. Labels []string
  380. }
  381. datas := make([]Data, 0)
  382. data0 := Data{Id: 0, Content: "此次百度收购将成中国互联网最大并购", Labels: []string{"百度", "中国"}}
  383. datas = append(datas, data0)
  384. data1 := Data{Id: 1, Content: "百度宣布拟全资收购91无线业务", Labels: []string{"百度"}}
  385. datas = append(datas, data1)
  386. data2 := Data{Id: 2, Content: "百度是中国最大的搜索引擎", Labels: []string{"百度"}}
  387. datas = append(datas, data2)
  388. data3 := Data{Id: 3, Content: "百度在研制无人汽车", Labels: []string{"百度"}}
  389. datas = append(datas, data3)
  390. data4 := Data{Id: 4, Content: "BAT是中国互联网三巨头", Labels: []string{"百度"}}
  391. datas = append(datas, data4)
  392. // 初始化
  393. searcher_locations := Engine{}
  394. searcher_locations.Init(types.EngineInitOptions{
  395. SegmenterDictionaries: "../data/dictionary.txt",
  396. IndexerInitOptions: &types.IndexerInitOptions{
  397. IndexType: types.LocationsIndex,
  398. },
  399. })
  400. defer searcher_locations.Close()
  401. for _, data := range datas {
  402. searcher_locations.IndexDocument(uint64(data.Id), types.DocumentIndexData{Content: data.Content, Labels: data.Labels}, true)
  403. }
  404. searcher_locations.FlushIndex()
  405. res_locations := searcher_locations.Search(types.SearchRequest{Text: "百度"})
  406. searcher_docids := Engine{}
  407. searcher_docids.Init(types.EngineInitOptions{
  408. SegmenterDictionaries: "../data/dictionary.txt",
  409. IndexerInitOptions: &types.IndexerInitOptions{
  410. IndexType: types.DocIdsIndex,
  411. },
  412. })
  413. defer searcher_docids.Close()
  414. for _, data := range datas {
  415. searcher_docids.IndexDocument(uint64(data.Id), types.DocumentIndexData{Content: data.Content, Labels: data.Labels}, true)
  416. }
  417. searcher_docids.FlushIndex()
  418. res_docids := searcher_docids.Search(types.SearchRequest{Text: "百度"})
  419. if res_docids.NumDocs != res_locations.NumDocs {
  420. t.Errorf("期待的搜索结果个数=\"%d\", 实际=\"%d\"", res_docids.NumDocs, res_locations.NumDocs)
  421. }
  422. }