package edu.brown.markov;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONStringer;
import org.voltdb.catalog.Database;
import edu.brown.graphs.AbstractEdge;
import edu.brown.graphs.AbstractGraphElement;
import edu.brown.graphs.IGraph;
import edu.brown.graphs.exceptions.InvalidGraphElementException;
import edu.brown.utils.MathUtil;
/**
* There are only two important things in edge: hits - the number of times this edge has been traversed probability -
* calculated by the source of this edge. the probability of traversing this edge
*
* There are also 'instance' versions of these variables. This is for managing online updates to the edges
*
* @author svelagap
*
*/
public class MarkovEdge extends AbstractEdge implements MarkovHitTrackable {
enum Members {
PROBABILITY,
TOTALHITS,
INSTANCEHITS,
}
/**
* This is the probability that the source of the edge will transition to the destination vertex
*/
public float probability;
/**
* This is the total number of times that we have traversed over this edge
*/
public int totalhits;
/**
* This is the temporary number of times that we have traversed over this edge in the current "period" of the
* MarkovGraph. This will eventually get folded into the global hits count, but we need to keep it separate so that
* we can determine whether the current workload is deviating from the training set
*/
public transient int instancehits = 0;
/**
* Constructor
*
* @param graph
*/
public MarkovEdge(IGraph<MarkovVertex, MarkovEdge> graph) {
super(graph);
this.totalhits = 0;
this.probability = 0;
}
public MarkovEdge(IGraph<MarkovVertex, MarkovEdge> graph, int hits, float probability) {
super(graph);
this.totalhits = hits;
this.probability = (float)probability;
}
@Override
public int compareTo(AbstractGraphElement o) {
assert (o != null);
if (o instanceof MarkovEdge) {
MarkovEdge me = (MarkovEdge)o;
if (MathUtil.equals(this.probability, me.probability, MarkovGraph.PROBABILITY_EPSILON) == false) {
return (int) (me.probability * 100 - this.probability * 100);
}
return (this.hashCode() - me.hashCode());
}
return super.compareTo(o);
}
public float getProbability() {
return this.probability;
}
/**
* Calculates the probability for this edge.
* Divides the number of hits this edge has had by the parameter
* @param allHits number of hits of the vertex that is the source of this edge
*/
public void calculateProbability(long allHits) {
assert(this.totalhits <= allHits) : String.format("Edge hits is greater than new allHits: " + this.totalhits + " > " + allHits);
if (allHits == 0) {
this.probability = 0f;
} else {
this.probability = (float) (this.totalhits / (double)allHits);
}
assert(MathUtil.greaterThanEquals(this.probability, 0.0f, MarkovGraph.PROBABILITY_EPSILON) &&
MathUtil.lessThanEquals(this.probability, 1.0f, MarkovGraph.PROBABILITY_EPSILON)) :
String.format("Invalid new edge probability: %d / %d = %f", this.totalhits, allHits, this.probability);
}
// ----------------------------------------------------------------------------
// VALIDATION METHODS
// ----------------------------------------------------------------------------
public boolean isValid(MarkovGraph markov) {
try {
this.validate(markov);
} catch (InvalidGraphElementException ex) {
return (false);
}
return (true);
}
protected void validate(MarkovGraph markov) throws InvalidGraphElementException {
MarkovVertex v0 = markov.getSource(this);
MarkovVertex v1 = markov.getDest(this);
float e_prob = this.getProbability();
// Make sure the edge probability is between [0, 1]
if (MathUtil.greaterThanEquals(e_prob, 0.0f, MarkovGraph.PROBABILITY_EPSILON) == false ||
MathUtil.lessThanEquals(e_prob, 1.0f, MarkovGraph.PROBABILITY_EPSILON) == false) {
throw new InvalidGraphElementException(markov, this, String.format("Edge probability is %.4f", e_prob));
}
// Make sure that the special states are linked to each other
if (v0.isQueryVertex() == false && v1.isQueryVertex() == false) {
throw new InvalidGraphElementException(markov, this, "Invalid edge between non-query states");
}
}
// ----------------------------------------------------------------------------
// ONLINE UPDATE METHODS
// ----------------------------------------------------------------------------
@Override
public void applyInstanceHitsToTotalHits() {
this.totalhits += this.instancehits;
this.instancehits = 0;
}
@Override
public void incrementTotalHits() {
this.totalhits++;
}
@Override
public long getTotalHits() {
return this.totalhits;
}
@Override
public void setInstanceHits(int instancehits) {
this.instancehits = instancehits;
}
@Override
public int getInstanceHits() {
return this.instancehits;
}
@Override
public int incrementInstanceHits() {
return (++this.instancehits);
}
@Override
public String toString() {
return String.format("%.02f", this.probability); // FORMAT.format(this.probability);
}
@Override
public String toString(boolean verbose) {
if (verbose) {
return (String.format("ElementId:%d, Prob:%.02f, TotalHits:%d, InstanceHits:%d",
this.getElementId(), this.probability, this.totalhits, this.instancehits));
} else {
return this.toString();
}
}
// ----------------------------------------------------------------------------
// SERIALIZATION METHODS
// ----------------------------------------------------------------------------
public void toJSONStringImpl(JSONStringer stringer) throws JSONException {
super.toJSONStringImpl(stringer);
super.fieldsToJSONString(stringer, MarkovEdge.class, Members.values());
}
public void fromJSONObjectImpl(JSONObject object, Database catalog_db) throws JSONException {
super.fromJSONObjectImpl(object, catalog_db);
super.fieldsFromJSONObject(object, catalog_db, MarkovEdge.class, Members.values());
}
}