Put local variables footer in all c, h files.
[idzebra-moved-to-github.git] / index / zvrank.c
1 /* $Id: zvrank.c,v 1.21 2006-05-10 08:13:26 adam Exp $
2    Copyright (C) 1995-2005
3    Index Data ApS
4
5    This file is part of the Zebra server.
6
7    Zebra is free software; you can redistribute it and/or modify it under
8    the terms of the GNU General Public License as published by the Free
9    Software Foundation; either version 2, or (at your option) any later
10    version.
11
12    Zebra is distributed in the hope that it will be useful, but WITHOUT ANY
13    WARRANTY; without even the implied warranty of MERCHANTABILITY or
14    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15    for more details.
16
17    You should have received a copy of the GNU General Public License
18    along with Zebra; see the file LICENSE.zebra.  If not, write to the
19    Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
20    02111-1307, USA.
21 */
22
23 /*
24 Zvrank: an experimental ranking algorithm. See doc/zvrank.txt and
25 source in index/zvrank.c. Enable this by using rank: zvrank in zebra.cfg.
26 Contributed by Johannes Leveling <Johannes.Leveling at
27 fernuni-hagen.de>
28 */
29
30 /* Zebra Vector Space Model RANKing 
31 **
32 ** six (seven) letter identifier for weighting scheme
33 ** best document weighting:
34 **  tfc nfc (tpc npc) [original naming]
35 **  ntc atc  npc apc  [SMART naming, used here]
36 ** best query weighting:
37 **  nfx tfx bfx (npx tpx bpx) [original naming]
38 **  atn ntn btn  apn npn bpn  [SMART naming]
39 ** -> should set zvrank.weighting-scheme to one of
40 ** "ntc-atn", "atc-atn", etc.
41 */
42
43
44 #include <math.h>  /* for log */
45
46 #include <stdio.h>
47 #include <assert.h>
48 #ifdef WIN32
49 #include <io.h>
50 #else
51 #include <unistd.h>
52 #endif
53
54 #include "index.h"
55 #include "rank.h"
56
57 static int log_level = 0;
58 static int log_initialized = 0;
59
60 static double blog(double x) { 
61     /* log_2, log_e or log_10 is used, best to change it here if necessary */
62     if (x <= 0)
63         return 0.0;
64     return log(x); /* / log(base) */
65 }
66
67 /* structures */
68
69 struct rank_class_info {
70     char rscheme[8];    /* name of weighting scheme */
71 };
72
73
74 struct rs_info {      /* for result set */
75     int db_docs;        /* number of documents in database (collection) */
76     int db_terms;       /* number of distinct terms in database (debugging?) */
77     int db_f_max;       /* maximum of f_t in database (debugging?) */
78     char *db_f_max_str; /* string (most frequent term) - for debugging */
79     /**/
80     char rscheme[8];    /* name of weighting scheme */
81     /**/
82     int veclen;
83     NMEM nmem;
84     void (*d_tf_fct)(void *, void *);   /* doc term frequency function */
85     void (*d_idf_fct)(void *, void *);  /* doc idf function */
86     void (*d_norm_fct)(void *, void *); /* doc normalization function */
87     /**/
88     void (*q_tf_fct)(void *, void *);   /* query term frequency function */
89     void (*q_idf_fct)(void *, void *);  /* query idf function */
90     void (*q_norm_fct)(void *, void *); /* query normalization function */
91     
92     double (*sim_fct)(void *, void *);  /* similarity function (scoring function) */
93     struct ds_info *qdoc;
94     struct ds_info *rdoc;
95 };
96 typedef struct rs_info *RS;
97
98 static void prn_rs(RS rs) { /* for debugging */
99     yaz_log(log_level, "* RS:");
100     yaz_log(log_level, " db_docs:   %d", rs->db_docs);
101     yaz_log(log_level, " db_terms:  %d", rs->db_terms);
102     yaz_log(log_level, " f_max:     %d", rs->db_f_max);
103     yaz_log(log_level, " f_max_str: %s", rs->db_f_max_str);
104     yaz_log(log_level, " veclen:    %d", rs->veclen);
105     /* rscheme implies functions */
106     yaz_log(log_level, " rscheme:   %s", rs->rscheme);
107     return;
108 }
109
110 struct ds_info {       /* document info */
111     char *docid;         /* unique doc identifier */
112     int  docno;          /* doc number */
113     int doclen;          /* document length */
114     int d_f_max;         /* maximum number of any term in doc (needed) */
115     char *d_f_max_str;   /* most frequent term in d - for debugging */
116     int veclen;          /* vector length */
117     struct ts_info *terms;
118     double docsim;       /* similarity in [0, ..., 1] (= score/1000) */
119 };
120 typedef struct ds_info* DS;
121
122 #if 0
123 static void prn_ds(DS ds) { /* for debugging */
124     yaz_log(log_level, " * DS:");
125     yaz_log(log_level, " docid:      %s", ds->docid);
126     yaz_log(log_level, " docno:      %d", ds->docno);
127     yaz_log(log_level, " doclen:     %d", ds->doclen);
128     yaz_log(log_level, " d_f_max:    %d", ds->d_f_max);
129     yaz_log(log_level, " d_f_max_str:%s", ds->d_f_max_str);
130     yaz_log(log_level, " veclen:     %d", ds->veclen);
131     return;
132 }
133 #endif
134
135 struct ts_info {       /* term info */
136     char *name;
137     int *id;
138     /**/
139     zint gocc;
140     int locc;
141     double tf;
142     double idf;
143     double wt;
144 };
145 typedef struct ts_info *TS;
146
147 #if 0
148 static void prn_ts(TS ts) { /* for debugging */
149     yaz_log(log_level, " * TERM:%s gocc:%d locc:%d  tf:%f idf:%f wt:%f",
150             ts->name, ts->gocc, ts->locc, ts->tf, ts->idf, ts->wt);
151     return;
152 }
153 #endif
154
155 /* end structures */
156
157 /* *** */
158
159 /* 
160 ** weighting functions 
161 ** check: RS is not needed anymore
162 */
163
164 /* calculate and store new term frequency vector */
165 static void tf_none(void *rsi, void *dsi) {
166     DS ds=(DS)dsi;
167     int i, veclen, freq;
168     /* no conversion. 1 <= tf */
169     veclen=ds->veclen;
170     for (i=0; i < veclen; i++) {
171         freq=ds->terms[i].locc;
172         ds->terms[i].tf=freq;
173     }
174     return;
175 }
176
177 static void tf_binary(void *rsi, void *dsi) {
178     DS ds=(DS)dsi;
179     int i, veclen, freq;
180     /* tf in {0, 1} */
181     veclen=ds->veclen;
182     for (i=0; i < veclen; i++) {
183         freq=ds->terms[i].locc;
184         if (freq > 0)
185             ds->terms[i].tf=1.0;
186         else
187             ds->terms[i].tf=0.0;
188     }
189     return;
190 }
191
192 static void tf_max_norm(void *rsi, void *dsi) {
193     DS ds=(DS)dsi;
194     double tf_max;
195     int i, veclen, freq;
196     /* divide each term by max, so 0 <= tf <= 1 */
197     tf_max=ds->d_f_max; /* largest frequency of t in document */
198     veclen=ds->veclen;
199     for (i=0; i < veclen; i++) {
200         freq=ds->terms[i].locc;
201         if ((freq > 0) &&
202             (tf_max > 0.0)) 
203             ds->terms[i].tf=freq/tf_max;
204         else
205             ds->terms[i].tf=0.0;
206     }
207     return;
208 }
209
210 static void tf_aug_norm(void *rsi, void *dsi) {
211     DS ds=(DS)dsi;
212     double K; 
213     double tf_max;
214     int i, veclen, freq;
215     /* augmented normalized tf. 0.5 <= tf <= 1  for K = 0.5 */
216     tf_max=ds->d_f_max; /* largest frequency of t in document */
217     veclen=ds->veclen;
218     K=0.5; /* zvrank.const-K */
219     for (i=0; i < veclen; i++) {
220         freq=ds->terms[i].locc;
221         if ((freq > 0) &&
222             (tf_max > 0.0)) 
223             ds->terms[i].tf=K+(1.0-K)*(freq/tf_max);
224         else
225             ds->terms[i].tf=0.0;
226     }
227     return;
228 }
229
230 static void tf_square(void *rsi, void *dsi) {
231     DS ds=(DS)dsi;
232     int i, veclen, freq;
233     /* tf ^ 2 */
234     veclen=ds->veclen;
235     for (i=0; i < veclen; i++) {
236         freq=ds->terms[i].locc;
237         if (freq > 0) 
238             ds->terms[i].tf=freq*freq;
239         else
240             ds->terms[i].tf=0.0;
241     }
242     return;
243 }
244
245 static void tf_log(void *rsi, void *dsi) {
246     DS ds=(DS)dsi;
247     int i, veclen, freq;
248     /* logarithmic tf */    
249     veclen=ds->veclen;
250     for (i=0; i < veclen; i++) {
251         freq=ds->terms[i].locc;
252         if (freq > 0) 
253             ds->terms[i].tf=1.0+blog(freq);
254         else
255             ds->terms[i].tf=0.0;
256     }
257     return;
258 }
259
260 /* calculate and store inverse document frequency vector */
261 static void idf_none(void *rsi, void *dsi) {
262     DS ds=(DS)dsi;
263     int i, veclen;
264     /* no conversion */
265     veclen=ds->veclen;
266     for (i=0; i < veclen; i++) {
267         ds->terms[i].idf=1.0;
268     }
269     return;
270 }
271
272 static void idf_tfidf(void *rsi, void *dsi) {
273     RS rs=(RS)rsi;
274     DS ds=(DS)dsi;
275     zint num_docs, gocc;
276     int i, veclen;
277     double idf;
278     /* normal tfidf weight */
279     veclen=ds->veclen;
280     num_docs=rs->db_docs;
281     for (i=0; i < veclen; i++) {
282         gocc=ds->terms[i].gocc;
283         if (gocc==0) 
284             idf=0.0; 
285         else
286             idf=blog((double) (num_docs/gocc));
287         ds->terms[i].idf=idf;
288     }
289     return;
290 }
291
292 static void idf_prob(void *rsi, void *dsi) {
293     RS rs=(RS)rsi;
294     DS ds=(DS)dsi;
295     zint num_docs, gocc;
296     int i, veclen;
297     double idf;
298     /* probabilistic formulation */
299     veclen=ds->veclen;
300     num_docs=rs->db_docs;
301     for (i=0; i < veclen; i++) {
302         gocc=ds->terms[i].gocc;
303         if (gocc==0)
304             idf=0.0; 
305         else
306             idf=blog((double) ((num_docs-gocc)/gocc));
307         ds->terms[i].idf=idf;
308     }
309     return;
310 }
311
312 static void idf_freq(void *rsi, void *dsi) {
313     RS rs=(RS)rsi;
314     DS ds=(DS)dsi;
315     int num_docs;
316     int i, veclen;
317     double idf;
318     /* frequency formulation */
319     veclen=ds->veclen;
320     num_docs=rs->db_docs;
321     if (num_docs==0)
322         idf=0.0;
323     else
324         idf=1.0/num_docs;
325     for (i=0; i < veclen; i++) {
326         ds->terms[i].idf=idf;
327     }
328     return;
329 }
330
331 static void idf_squared(void *rsi, void *dsi) {
332     RS rs=(RS)rsi;
333     DS ds=(DS)dsi;
334     zint num_docs, gocc;
335     int i, veclen;
336     double idf;
337     /* idf ^ 2 */
338     veclen=ds->veclen;
339     num_docs=rs->db_docs;
340     yaz_log(log_level, "idf_squared: db_docs required");
341     for (i=0; i < veclen; i++) {
342         gocc=ds->terms[i].gocc;
343         if (gocc==0)
344             idf=0.0;
345         else 
346             idf=blog(CAST_ZINT_TO_DOUBLE(num_docs/gocc));
347         idf=idf*idf;
348         ds->terms[i].idf=idf;
349     }
350     return;
351 }
352
353 /* calculate and store normalized weight (tf-idf) vector */
354 static void norm_none(void *rsi, void *dsi) {
355     DS ds=(DS)dsi;
356     int i, veclen;
357     /* no normalization */
358     veclen=ds->veclen;
359     for (i=0; i < veclen; i++) {
360         ds->terms[i].wt=ds->terms[i].tf*ds->terms[i].idf;
361     }
362     return;
363 }
364
365 static void norm_sum(void *rsi, void *dsi) {
366     DS ds=(DS)dsi;
367     int i, veclen;
368     double tfs=0.0;
369     /**/
370     veclen=ds->veclen;
371     for (i=0; i < veclen; i++) {
372         ds->terms[i].wt=ds->terms[i].tf*ds->terms[i].idf;
373         tfs+=ds->terms[i].wt;
374     } 
375     if (tfs > 0.0)
376         for (i=0; i < veclen; i++) {
377             ds->terms[i].wt=ds->terms[i].wt/tfs;
378         }
379     /* else: tfs==0 && ds->terms[i].wt==0 */
380     return;
381 }
382
383 static void norm_cosine(void *rsi, void *dsi) {
384     DS ds=(DS)dsi;
385     int i, veclen;
386     double tfs=0.0;
387     /**/
388     veclen=ds->veclen;
389     for (i=0; i < veclen; i++) {
390         ds->terms[i].wt=ds->terms[i].tf*ds->terms[i].idf;
391         tfs+=(ds->terms[i].wt*ds->terms[i].wt);
392     } 
393     tfs=sqrt(tfs); 
394     if (tfs > 0.0)
395         for (i=0; i < veclen; i++) {
396             ds->terms[i].wt=ds->terms[i].wt/tfs;
397         }
398     /* else: tfs==0 && ds->terms[i].wt==0 */
399     return;
400 }
401
402 static void norm_fourth(void *rsi, void *dsi) {
403     DS ds=(DS)dsi;
404     int i, veclen;
405     double tfs=0.0, fr;
406     /**/
407     veclen=ds->veclen;
408     for (i=0; i < veclen; i++) {
409         ds->terms[i].wt=ds->terms[i].tf*ds->terms[i].idf;
410         fr=(ds->terms[i].wt*ds->terms[i].wt);
411         fr=fr*fr; /* ^ 4 */
412         tfs+=fr; 
413     }
414     if (tfs > 0.0)
415         for (i=0; i < veclen; i++) {
416             ds->terms[i].wt=ds->terms[i].wt/tfs;
417         }
418     /* else: tfs==0 && ds->terms[i].wt==0 */
419     return;
420 }
421
422 static void norm_max(void *rsi, void *dsi) {
423     DS ds=(DS)dsi;
424     int i, veclen;
425     double tfm=0.0;
426     /**/
427     veclen=ds->veclen;
428     for (i=0; i < veclen; i++) {
429         ds->terms[i].wt=ds->terms[i].tf*ds->terms[i].idf;
430         if (ds->terms[i].wt > tfm)
431             tfm=ds->terms[i].wt;
432     }
433     if (tfm > 0.0)
434         for (i=0; i < veclen; i++) {
435             ds->terms[i].wt=ds->terms[i].wt/tfm;
436         }
437     /* else: tfs==0 && ds->terms[i].wt==0 */
438     return;
439 }
440
441 /* FIXME add norm_pivot, ... */
442
443 static double sim_cosine(void *dsi1, void *dsi2) {
444     DS ds1=(DS)dsi1;
445     DS ds2=(DS)dsi2;
446     int i, veclen;
447     double smul=0.0, sdiv=0.0, sqr11=0.0, sqr22=0.0;
448     double v1, v2;
449     /**/
450     veclen=ds1->veclen; /* and ds2->veclen */
451     for (i=0; i < veclen; i++) {
452         v1=ds1->terms[i].wt;
453         v2=ds2->terms[i].wt;
454         smul +=(v1*v2);
455         sqr11+=(v1*v1);
456         sqr22+=(v2*v2);
457     }
458     sdiv=sqrt(sqr11*sqr22);
459     if (sdiv==0.0)
460         return 0.0;
461     return (smul/sdiv);
462 }
463
464 /* FIXME: add norm_jaccard, norm_dice, ... */
465
466 /* end weighting functions */
467
468 /* *** */
469
470 static void zv_init_scheme(RS rs, const char *sname) {
471     int slen;
472     char c0, c1, c2, c3, c4, c5, c6;
473     const char *def_rscheme="ntc-atn"; /* a good default */
474     /**/
475     yaz_log(log_level, "zv_init_scheme");
476     slen=strlen(sname);
477     if (slen < 7) 
478         yaz_log(log_level, "zvrank: invalid weighting-scheme \"%s\"", sname);
479     if (slen > 0) c0=sname[0]; else c0=def_rscheme[0];
480     if (slen > 1) c1=sname[1]; else c1=def_rscheme[1];
481     if (slen > 2) c2=sname[2]; else c2=def_rscheme[2];
482     c3='-';
483     if (slen > 4) c4=sname[4]; else c4=def_rscheme[4];
484     if (slen > 5) c5=sname[5]; else c5=def_rscheme[5];
485     if (slen > 6) c6=sname[6]; else c6=def_rscheme[6];
486     
487     /* assign doc functions */
488     switch (c0) 
489     {
490         case 'b':
491             rs->d_tf_fct=tf_binary;
492             rs->rscheme[0]='b';
493             break;
494         case 'm':
495             rs->d_tf_fct=tf_max_norm;
496             rs->rscheme[0]='m';
497             yaz_log(log_level, "tf_max_norm: d_f_max required");
498             break;
499         case 'a':
500             rs->d_tf_fct=tf_aug_norm;
501             rs->rscheme[0]='a';
502             yaz_log(log_level, "tf_aug_norm: d_f_max required");
503             break;
504         case 's':
505             rs->d_tf_fct=tf_square;
506             rs->rscheme[0]='s';
507             break;
508         case 'l':
509             rs->d_tf_fct=tf_log;
510             rs->rscheme[0]='l';
511             break;
512         default: /* 'n' */
513             rs->d_tf_fct=tf_none;
514             rs->rscheme[0]='n';
515     }
516     switch (c1) 
517     {
518         case 't':
519             rs->d_idf_fct=idf_tfidf;
520             rs->rscheme[1]='t';
521             yaz_log(log_level, "idf_tfidf: db_docs required");
522             break;
523         case 'p':
524             rs->d_idf_fct=idf_prob;
525             rs->rscheme[1]='p';
526             yaz_log(log_level, "idf_prob: db_docs required");
527             break;
528         case 'f':
529             rs->d_idf_fct=idf_freq;
530             rs->rscheme[1]='f';
531             yaz_log(log_level, "idf_freq: db_docs required");
532             break;
533         case 's':
534             rs->d_idf_fct=idf_squared;
535             rs->rscheme[1]='s';
536             yaz_log(log_level, "idf_squared: db_docs required");
537             break;
538         default: /* 'n' */
539             rs->d_idf_fct=idf_none;
540             rs->rscheme[1]='n';
541     }
542     switch (c2) 
543     {
544         case 's':
545             rs->d_norm_fct=norm_sum;
546             rs->rscheme[2]='s';
547             break;
548         case 'c':
549             rs->d_norm_fct=norm_cosine;
550             rs->rscheme[2]='c';
551             break;
552         case 'f':
553             rs->d_norm_fct=norm_fourth;
554             rs->rscheme[2]='t';
555             break;
556         case 'm':
557             rs->d_norm_fct=norm_max;
558             rs->rscheme[2]='m';
559             break;
560         default: /* 'n' */
561             rs->d_norm_fct=norm_none;
562             rs->rscheme[2]='n';
563     }
564     rs->rscheme[3]='-';
565     /* assign query functions */
566     switch (c4) 
567     {
568         case 'b':
569             rs->q_tf_fct=tf_binary;
570             rs->rscheme[4]='b';
571             break;
572         case 'm':
573             rs->q_tf_fct=tf_max_norm;
574             yaz_log(log_level, "tf_max_norm: d_f_max required");
575             rs->rscheme[4]='m';
576             break;
577         case 'a':
578             rs->q_tf_fct=tf_aug_norm;
579             rs->rscheme[4]='a';
580             yaz_log(log_level, "tf_aug_norm: d_f_max required");
581             break;
582         case 's':
583             rs->q_tf_fct=tf_square;
584             rs->rscheme[4]='s';
585             break;
586         case 'l':
587             rs->q_tf_fct=tf_log;
588             rs->rscheme[4]='l';
589             break;
590         default: /* 'n' */
591             rs->q_tf_fct=tf_none;
592             rs->rscheme[4]='n';
593     }
594     switch (c5) 
595     {
596         case 't':
597             rs->q_idf_fct=idf_tfidf;
598             rs->rscheme[5]='t';
599             yaz_log(log_level, "idf_tfidf: db_docs required");
600             break;
601         case 'p':
602             rs->q_idf_fct=idf_prob;
603             rs->rscheme[5]='p';
604             yaz_log(log_level, "idf_prob: db_docs required");
605             break;
606         case 'f':
607             rs->q_idf_fct=idf_freq;
608             rs->rscheme[5]='f';
609             yaz_log(log_level, "idf_freq: db_docs required");
610             break;
611         case 's':
612             rs->q_idf_fct=idf_squared;
613             rs->rscheme[5]='s';
614             yaz_log(log_level, "idf_squared: db_docs required");
615             break;
616         default: /* 'n' */
617             rs->q_idf_fct=idf_none;
618             rs->rscheme[5]='n';
619     }
620     switch (c6) 
621     {
622         case 's':
623             rs->q_norm_fct=norm_sum;
624             rs->rscheme[6]='s';
625             break;
626         case 'c':
627             rs->q_norm_fct=norm_cosine;
628             rs->rscheme[6]='c';
629             break;
630         case 'f':
631             rs->q_norm_fct=norm_fourth;
632             rs->rscheme[6]='f';
633             break;
634         case 'm':
635             rs->q_norm_fct=norm_max;
636             rs->rscheme[6]='m';
637             break;
638         default: /* 'n' */
639             rs->q_norm_fct=norm_none;
640             rs->rscheme[6]='n';
641     }
642     rs->rscheme[7]='\0';
643     rs->sim_fct=sim_cosine;
644     yaz_log(log_level, "zv_scheme %s", rs->rscheme);
645 }
646
647 static void zv_init(RS rs, const char *rscheme) {
648     yaz_log(log_level, "zv_init");
649     /**/
650     rs->db_docs=100000;   /* assign correct value here */
651     rs->db_terms=500000;  /* assign correct value here (for debugging) */
652     rs->db_f_max=50;      /* assign correct value here */
653     rs->db_f_max_str="a"; /* assign correct value here (for debugging) */
654     /* FIXME - get those values from somewhere */
655     zv_init_scheme(rs, rscheme);
656     return;
657 }
658
659 /******/
660
661 /*
662  * zv_create: Creates/Initialises this rank handler. This routine is 
663  *  called exactly once. The routine returns the class_handle.
664  */
665 static void *zv_create (ZebraHandle zh) {
666     int i;
667     Res res = zh->res;
668     const char *wscheme;
669     struct rank_class_info *ci = (struct rank_class_info *)
670         xmalloc (sizeof(*ci));
671     if (!log_initialized)
672     {
673         log_level = yaz_log_module_level("zvrank");
674         log_initialized = 1;
675     }
676
677     yaz_log(log_level, "zv_create");
678     wscheme=  res_get_def(res, "zvrank.weighting-scheme", "");
679     for (i = 0; wscheme[i] && i < 8; i++) 
680         ci->rscheme[i]=wscheme[i];
681     ci->rscheme[i] = '\0';
682     return ci;
683 }
684
685 /*
686  * zv_destroy: Destroys this rank handler. This routine is called
687  *  when the handler is no longer needed - i.e. when the server
688  *  dies. The class_handle was previously returned by create.
689  */
690 static void zv_destroy (struct zebra_register *reg, void *class_handle) {
691     struct rank_class_info *ci = (struct rank_class_info *) class_handle;
692     yaz_log(log_level, "zv_destroy");
693     xfree (ci);
694 }
695
696
697 /*
698  * zv_begin: Prepares beginning of "real" ranking. Called once for
699  *  each result set. The returned handle is a "set handle" and
700  *  will be used in each of the handlers below.
701  */
702 static void *zv_begin(struct zebra_register *reg, void *class_handle, 
703                       RSET rset, NMEM nmem, TERMID *terms, int numterms)
704 {
705     struct rs_info *rs=(struct rs_info *)nmem_malloc(nmem,sizeof(*rs));
706     struct rank_class_info *ci=(struct rank_class_info *)class_handle;
707     int i;
708     int veclen;
709     int *ip;
710     zint gocc;
711     /**/
712     yaz_log(log_level, "zv_begin");
713     veclen= numterms;
714     zv_init(rs, ci->rscheme);
715     rs->nmem=nmem;
716     rs->veclen=veclen;
717     prn_rs(rs);
718   
719     rs->qdoc=(struct ds_info *)nmem_malloc(nmem,sizeof(*rs->qdoc));
720     rs->qdoc->terms=(struct ts_info *)nmem_malloc(nmem,
721                                 sizeof(*rs->qdoc->terms)*rs->veclen);
722     rs->qdoc->veclen=veclen;
723     rs->qdoc->d_f_max=1; /* no duplicates */ 
724     rs->qdoc->d_f_max_str=""; 
725
726     rs->rdoc=(struct ds_info *)nmem_malloc(nmem,sizeof(*rs->rdoc));
727     rs->rdoc->terms=(struct ts_info *)nmem_malloc(nmem,
728                          sizeof(*rs->rdoc->terms)*rs->veclen);
729     rs->rdoc->veclen=veclen;
730     rs->rdoc->d_f_max=10; /* just a guess */
731     rs->rdoc->d_f_max_str=""; 
732     /* yaz_log(log_level, "zv_begin_init"); */
733     for (i = 0; i < rs->veclen; i++)
734     {
735         gocc= rset_count(terms[i]->rset);
736         terms[i]->rankpriv=ip=nmem_malloc(nmem, sizeof(int));
737         *ip=i; /* save the index for add() */
738         /* yaz_log(log_level, "zv_begin_init i=%d gocc=%d", i, gocc); */
739         rs->qdoc->terms[i].gocc=gocc;
740         rs->qdoc->terms[i].locc=1;  /* assume query has no duplicate terms */
741         rs->rdoc->terms[i].gocc=gocc;
742         rs->rdoc->terms[i].locc=0;
743     }
744     (*rs->q_tf_fct)(rs, rs->qdoc); /* we do this once only */
745     (*rs->q_idf_fct)(rs, rs->qdoc);
746     (*rs->q_norm_fct)(rs, rs->qdoc);
747     return rs;
748 }
749
750 /*
751  * zv_end: Terminates ranking process. Called after a result set
752  *  has been ranked.
753  */
754 static void zv_end (struct zebra_register *reg, void *rsi)
755 {
756     yaz_log(log_level, "zv_end");
757     /* they all are nmem'd */
758     return;
759 }
760
761 /*
762  * zv_add: Called for each word occurence in a result set. This routine
763  *  should be as fast as possible. This routine should "incrementally"
764  *  update the score.
765  */
766 static void zv_add (void *rsi, int seqno, TERMID term) {
767     RS rs=(RS)rsi;
768     int *ip = term->rankpriv;
769     int i=*ip;
770     if (!term) 
771     {
772         yaz_log(log_level, "zvrank zv_add seqno=%d NULL term",seqno );
773         return;
774     }
775     rs->rdoc->terms[i].locc++;
776     yaz_log(log_level, "zvrank zv_add seqno=%d '%s' term_index=%d cnt=%d", 
777              seqno, term->name, i, rs->rdoc->terms[i].locc );
778 }
779
780 /*
781  * zv_calc: Called for each document in a result. This handler should 
782  *  produce a score based on previous call(s) to the add handler. The
783  *  score should be between 0 and 1000. If score cannot be obtained
784  *  -1 should be returned.
785  */
786 static int zv_calc (void *rsi, zint sysno, zint staticrank, int *stop_flag)
787 {
788     int i, veclen; 
789     int score=0;
790     double dscore=0.0;
791     RS rs=(RS)rsi;
792     /* yaz_log(log_level, "zv_calc"); */
793     /**/
794     veclen=rs->veclen;
795     if (veclen==0)
796         return -1;
797     for (i = 0; i < veclen; i++) {
798         /* qdoc weight has already been calculated */
799         (*rs->d_tf_fct)(rs, rs->rdoc);
800         (*rs->d_idf_fct)(rs, rs->rdoc);
801         (*rs->d_norm_fct)(rs, rs->rdoc);
802         dscore=rs->sim_fct(rs->qdoc, rs->rdoc);
803     }
804     score = (int) (dscore * 1000 +.5);
805     yaz_log (log_level, "zv_calc: sysno=" ZINT_FORMAT " score=%d", 
806             sysno, score);
807     if (score > 1000) /* should not happen */
808         score = 1000;
809     /* reset counts for the next record */
810     for (i = 0; i < rs->veclen; i++)
811         rs->rdoc->terms[i].locc=0;
812     return score;
813 }
814
815 /*
816  * Pseudo-meta code with sequence of calls as they occur in a
817  * server. Handlers are prefixed by --:
818  *
819  *     server init
820  *     -- create
821  *     foreach search
822  *        rank result set
823  *        -- begin
824  *        foreach record
825  *           foreach word
826  *              -- add
827  *           -- calc
828  *        -- end
829  *     -- destroy
830  *     server close
831  */
832
833 static struct rank_control rank_control_vsm = {
834     "zvrank",
835     zv_create,
836     zv_destroy,
837     zv_begin,
838     zv_end,
839     zv_calc,
840     zv_add,
841 };
842  
843 struct rank_control *rank_zv_class = &rank_control_vsm;
844
845 /*
846  * Local variables:
847  * c-basic-offset: 4
848  * indent-tabs-mode: nil
849  * End:
850  * vim: shiftwidth=4 tabstop=8 expandtab
851  */
852