/*
####################################################################################
Stephen W. Thomas
sthomas@cs.queensu.ca
Queen's University

VSMSimilarity.java

This class specifies how the Vector Space Model (VSM) would compute the similarity between
a query and a document. Lucene's default similarity is basic tf-idf (with a few minor, negligible tweaks).

See: http://lucene.apache.org/java/3_5_0/api/core/org/apache/lucene/search/Similarity.html

By overriding some of these functions and providing custom behavior,  
we can have our own arbitrary weighting and scoring function.
 
The ideas behind tf-idf, sublinear tf-idf, and boolean weightings are given in:
[1] "An Introduction to Information Retrieval", Manning et al., 2009


####################################################################################
*/

package ca.queensu.cs.sail.lucenelda;

import org.apache.lucene.index.FieldInvertState;
import org.apache.lucene.search.DefaultSimilarity;

public class VSMSimilarity extends DefaultSimilarity {
	
	// Weighting codes
	public boolean doBasic     = true;  // Basic tf-idf
	public boolean doSublinear = false; // Sublinear tf-idf
	public boolean doBoolean   = false; // Boolean
	
	//Scoring codes
	public boolean doCosine    = true;
	public boolean doOverlap   = false;

	private static final long serialVersionUID = 4697609598242172599L;

	// term frequency in document = measure of how often a term appears in the document
	public float tf(int freq) {		
		// Sublinear tf weighting. Equation taken from [1], pg 127, eq 6.13.
		if (doSublinear){
			if (freq > 0){
				return 1 + (float)Math.log(freq);
			} else {
				return 0;
			}
		} else if (doBoolean){
			return 1;
		}
		// else: doBasic
		// The default behaviour of Lucene is sqrt(freq), but we are implementing the basic VSM model
		return freq;
	}

	// inverse document frequency = measure of how often the term appears across the index
	public float idf(int docFreq, int numDocs) {
		if (doBoolean || doOverlap){
			return 1;
		}
		// The default behaviour of Lucene is 1 + log (numDocs/(docFreq+1)), which is what we want (default VSM model)
		return super.idf(docFreq, numDocs);	
	}

	// normalization factor so that queries can be compared 
	public float queryNorm(float sumOfSquaredWeights){
		if (doOverlap){
			return 1;
		} else if (doCosine){
			return super.queryNorm(sumOfSquaredWeights);
		}
		// else: can't get here
		return super.queryNorm(sumOfSquaredWeights);
	}

	// number of terms in the query that were found in the document
	public float coord(int overlap, int maxOverlap) {
		if (doOverlap){
			return 1;
		} else if (doCosine){
			return 1;
		}
		// else: can't get here
		return super.coord(overlap, maxOverlap);
	}
	
	// Note: this happens an index time, which we don't take advantage of (too many indices!)
	public float computeNorm(String fieldName, FieldInvertState state){
		if (doOverlap){
			return 1;
		} else if (doCosine){
			return super.computeNorm(fieldName, state);
		}
		// else: can't get here
		return super.computeNorm(fieldName, state);
	}
}
