package stoppers; import java.lang.*; import java.io.*; import main.BetterTokenizer; import main.Cell; import main.GeneralInput; import main.Model; /** A stopper that looks for a single cell with a high value of a Node, surrounded by cells with low values of that Node. The code in here borrows liberally from the Oscillating Stripe Stop. It should give the best score to a stable, non-oscillating single cell with a high value whose neighbors all have a low value for that Node. @author Eli @version 3/19/99 */ public class CellSurroundStop extends SimpleStop { float bullseyeScore=0f; float currentBullseyeScore=0f; boolean avgstripescore=false; String node; /** XPos parameter gives 0-based x position of focal cell. */ int xPos=0, /** YPos parameter gives 0-based y position of focal cell */ yPos = 0; // Position of the focal cell boolean lookForPeak=true; int navsteps=0; int maxavsteps=4; boolean averaging=false; boolean done=false; // oscillation tracking float thisval=-1f, lastval=-1f; float currentPeak=-1f, oldPeak=-1f, currentTrough=2f, oldTrough=2f; float thisAmp=-1f, oldAmp=-1f; float threshold=0; float interval=1f; int per=0; float alpha=0.2f; // stripe parameters float sMin=0.2f; // higher is better float sDif=5f; // higher is better float sVar=2f; // lower is better static final int AVERAGE_MODE = 1, MAX_MODE = 2; int differenceMode = AVERAGE_MODE; // No arg constructor for instantiating by name public CellSurroundStop() { stopTime = 1; } public CellSurroundStop(float stop_time) { stopTime = stop_time; } public boolean stop(float time) { // trivial reasons to return if(time < lastTime + interval) { return false; } if(done) return true; // can't remember why I put this in // MODIFIED 5/28 GvonD // KLUDGE TO STOP IT FROM GETTING STUCK // this is important - there are several ways this oscillation tracking scheme // can fail to "catch" and this bit handles them and makes sure the stopper // returns at least something relevant. It ought usually to be the case that // this handler is entered only when things are truly stable, but it will also // catch chaotic behavior (which to date has not been seen to occur) if((!averaging && (time > stopTime)) || (time > 2*stopTime)) { // MODIFIED 1/12 GvonD // This if statement used to be entered only when per<2, but the case where per>=2 is troublesome if(true) { // need to handle this situation far more gracefully thisval = model.cells[yPos * model.arrayWidth + xPos].nodePeek(node); if(!avgstripescore) { bullseyeScore = scoreBullseye(model.cells,time); } score = (float)Math.pow((double)alpha,(double)bullseyeScore); return done=true; } else averaging = true; // what are we REALLY supposed to do here? // this line causes a problem; if something is steadily declining but // has already made a few peaks, or the other way around, this statement // allows the thing to keep running "forever". At present this line is // not reachable. // END OF MODIFICATION } // END OF KLUDGE // END OF MODIFICATION if(avgstripescore) { currentBullseyeScore = scoreBullseye(model.cells, time); } thisval = model.cells[yPos * model.arrayWidth + xPos].nodePeek(node); if(lookForPeak) { // don't do anything if we're on our way up... if(thisval <= lastval) { // ...but if we're on our way down, oldPeak = currentPeak; currentPeak = lastval; // ...record the peak, if(!avgstripescore) { bullseyeScore = currentBullseyeScore = scoreBullseye(model.cells, time); } lookForPeak = false; // ...and start looking for a trough } } else { // don't do anything if we're on our way down... if(thisval >= lastval) { // ...but if we've turned up, per++; oldTrough = currentTrough; currentTrough = lastval; // ...record the trough, lookForPeak = true; // ...set to look for a peak next time, oldAmp = thisAmp; // ...and update the amplitudes thisAmp = currentPeak - currentTrough; //System.out.println("\t" + thisAmp + "\t" + currentPeak + "\tscore:\t" + score); if(averaging) { score = navsteps*score + (float)Math.pow((double)(alpha+(1-alpha)*thisAmp/currentPeak),(double)bullseyeScore); score /= (++navsteps); if(navsteps == maxavsteps) { //System.out.println(score); return done=true; } } else if(oldAmp != -1) { // if we've gotten at least two amplitudes... if(Math.abs(thisAmp - oldAmp) < threshold * currentPeak) { // ...and the amplitude is changing slowly, score = (float)Math.pow((double)(alpha+(1-alpha)*thisAmp/currentPeak),(double)bullseyeScore); averaging = true; navsteps = 1; } } } } lastval = thisval; lastTime = time; return false; } public float scoreBullseye(Cell [] cells, float time) { Cell cell = cells[yPos * model.arrayWidth + xPos]; float temp=0f; float [] surround = new float[cell.neighbors.length]; float surroundMean = 0f, centerValue = 0f; float surroundMax = 0f, centerMin = 1f, centerMax = 0f; for(int neighbor = 0; neighbor < cell.neighbors.length; neighbor++) { surround[neighbor] = cell.neighbors[neighbor].nodePeek(node); if(surroundMax < surround[neighbor]) surroundMax = surround[neighbor]; surroundMean += surround[neighbor]; } centerValue = cell.nodePeek(node); if(differenceMode == AVERAGE_MODE) surroundMean /= cell.neighbors.length; else surroundMean = surroundMax; temp = 2 * (centerValue/(sMin+centerValue)); // note that if sMin is high (i.e., near 1), the stripe score can't ever have much impact if(centerValue > 0) { // temp *= 2 * (centerMin/((centerMax/sVar)+centerMin)); if(surroundMean > 0.00001) { temp *= 2 * ((centerValue/(surroundMean))/(sDif+(centerValue/(surroundMean)))); } else temp *= 2; temp *= (1 + (centerMin - surroundMean)); } else temp = 0; // now merge the momentary score into the running average if(avgstripescore) { bullseyeScore *= lastTime; bullseyeScore += temp*(time-lastTime); bullseyeScore /= time; } return temp; } public void reset() { super.reset(); bullseyeScore=0f; thisval=-1f; lastval=-1f; currentPeak=-1f; oldPeak=-1f; currentTrough=2f; oldTrough=2f; thisAmp=-1f; oldAmp=-1f; lookForPeak=true; lastTime=0f; per = 0; averaging = false; done = false; navsteps = 0; } protected void loadParameter(String info, BetterTokenizer tokenizer) throws Exception { if(info.equals("Node")) { GeneralInput.nextToken(tokenizer); node = tokenizer.sval; } else if(info.equals("XPos")) { GeneralInput.nextToken(tokenizer); xPos = ((int)tokenizer.nval); } else if(info.equals("YPos")) { GeneralInput.nextToken(tokenizer); yPos = ((int)tokenizer.nval); } else if(info.equals("Interval")) { GeneralInput.nextToken(tokenizer); interval = (float)tokenizer.nval; } else if(info.equals("StabilityThreshold")) { GeneralInput.nextToken(tokenizer); threshold = (float)tokenizer.nval; } else if(info.equals("Alpha")) { GeneralInput.nextToken(tokenizer); alpha = (float)tokenizer.nval; } else if(info.equals("StripeThreshold")) { GeneralInput.nextToken(tokenizer); sMin = (float)tokenizer.nval; } else if(info.equals("StripeVariance")) { GeneralInput.nextToken(tokenizer); sVar = (float)tokenizer.nval; } else if(info.equals("StripeDifference")) { GeneralInput.nextToken(tokenizer); sDif = (float)tokenizer.nval; } else if(info.equals("AverageStripeScore")) { avgstripescore = true; } else if(info.equals("DifferenceMode")) { GeneralInput.nextToken(tokenizer); if(tokenizer.sval.equals("Max")) differenceMode = MAX_MODE; else differenceMode = AVERAGE_MODE; } else super.loadParameter(info,tokenizer); } }