package bsearch.app ;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Writer;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.AttributesImpl;
import bsearch.util.GeneralUtils;
public strictfp class SearchProtocol
{
//TODO: Add a "documentation" field to store text/comments about the search protocol.
public final double bsearchVersionNumber;
public final String modelFile ;
public final String modelStepCommands ;
public final String modelSetupCommands ;
public final String modelStopCondition ;
public final int modelStepLimit ;
public final String modelMetricReporter ;
public final String modelMeasureIf;
public final List<String> paramSpecStrings;
public final boolean fitnessMinimized ;
public final int fitnessSamplingReplications ; // if == 0, use adaptive sampling.
public final FITNESS_COLLECTING fitnessCollecting;
public final FITNESS_COMBINE_REPLICATIONS fitnessCombineReplications;
public final String fitnessDerivativeParameter;
public final double fitnessDerivativeDelta;
public final boolean fitnessDerivativeUseAbs;
public String searchMethodType ;
public final HashMap<String,String> searchMethodParams;
public final String chromosomeType;
public final boolean caching;
public final int evaluationLimit;
public final int bestCheckingNumReplications; // if == 0, no best checking is done
/**
* A SearchProtocol stores the information about what is being searched, and how it should be done.
* These fields map to the BehaviorSearch GUI for designing an experiment/protocol in a
* relatively straightforward fashion.
*/
public SearchProtocol(String modelFile, List<String> paramSpecStrings,
String modelStepCommands, String modelSetupCommands, String modelStopCondition,
int modelStepLimit, String modelMetricReporter, String modelMeasureIf,
boolean fitnessMinimized,
int fitnessSamplingRepetitions,
FITNESS_COLLECTING fitnessCollecting,
FITNESS_COMBINE_REPLICATIONS fitnessCombineReplications,
String fitnessDerivativeParameter,
double fitnessDerivativeDelta,
boolean fitnessDerivativeUseAbs,
String searchMethodType,
HashMap<String, String> searchMethodParams,
String chromosomeType,
boolean caching,
int evaluationLimit,
int bestCheckingNumReplications) {
super();
this.bsearchVersionNumber = GeneralUtils.getVersionNumber();
this.modelFile = modelFile;
this.paramSpecStrings = paramSpecStrings;
this.modelStepCommands = modelStepCommands;
this.modelSetupCommands = modelSetupCommands;
this.modelStopCondition = modelStopCondition;
this.modelStepLimit = modelStepLimit;
this.modelMetricReporter = modelMetricReporter;
this.modelMeasureIf = modelMeasureIf;
this.fitnessMinimized = fitnessMinimized;
this.fitnessSamplingReplications = fitnessSamplingRepetitions;
this.fitnessCollecting = fitnessCollecting;
this.fitnessCombineReplications = fitnessCombineReplications;
this.fitnessDerivativeParameter = fitnessDerivativeParameter;
this.fitnessDerivativeDelta = fitnessDerivativeDelta;
this.fitnessDerivativeUseAbs = fitnessDerivativeUseAbs;
this.searchMethodType = searchMethodType;
this.searchMethodParams = searchMethodParams;
this.chromosomeType = chromosomeType;
this.caching = caching;
this.evaluationLimit = evaluationLimit;
this.bestCheckingNumReplications = bestCheckingNumReplications;
}
private static String loadOrGetDefault(XPath xpath, Document xmlDoc, String path, String defaultVal)
{
try {
String s = (String) xpath.evaluate(path , xmlDoc , XPathConstants.STRING );
if (s.equals(""))
{
return defaultVal;
}
return s;
} catch (XPathExpressionException e) {
return defaultVal;
}
}
private static int loadOrGetDefaultInt(XPath xpath, Document xmlDoc, String path, int defaultVal)
{
try {
String s = (String) xpath.evaluate(path , xmlDoc , XPathConstants.STRING );
if (s.equals(""))
{
return defaultVal;
}
return Integer.valueOf(s.trim());
} catch (XPathExpressionException e) {
return defaultVal;
} catch (NumberFormatException e) {
return defaultVal;
}
}
private static double loadOrGetDefaultDouble(XPath xpath, Document xmlDoc, String path, double defaultVal)
{
try {
String s = (String) xpath.evaluate(path , xmlDoc , XPathConstants.STRING );
if (s.equals(""))
{
return defaultVal;
}
return Double.valueOf(s.trim());
} catch (XPathExpressionException e) {
return defaultVal;
} catch (NumberFormatException e) {
return defaultVal;
}
}
private SearchProtocol(Document xmlDoc) throws XPathExpressionException
{
// Admittedly, XPath isn't the most efficient approach, but this code isn't performance critical,
// and I find XPath nice and readable. ~Forrest (10/28/2008)
XPathFactory factory = XPathFactory.newInstance();
XPath xpath = factory.newXPath();
bsearchVersionNumber = loadOrGetDefaultDouble(xpath, xmlDoc, "/search/bsearchVersionNumber/text()" , 0.71); // 0.71 since that was the version before we added this XML tag
modelFile = loadOrGetDefault(xpath, xmlDoc, "/search/modelInfo/modelFile/text()" , "");
modelSetupCommands = loadOrGetDefault(xpath, xmlDoc, "/search/modelInfo/modelSetupCommands/text()", "");
modelStepCommands = loadOrGetDefault(xpath, xmlDoc, "/search/modelInfo/modelStepCommands/text()" , "" );
modelStopCondition = loadOrGetDefault(xpath, xmlDoc, "/search/modelInfo/modelStopCondition/text()", "");
modelStepLimit = loadOrGetDefaultInt(xpath, xmlDoc, "/search/modelInfo/modelStepLimit/text()", 100);
modelMetricReporter = loadOrGetDefault(xpath, xmlDoc, "/search/modelInfo/modelMetricReporter/text()" , "");
modelMeasureIf = loadOrGetDefault(xpath, xmlDoc, "/search/modelInfo/modelMeasureIf/text()" , "true");
fitnessMinimized = Boolean.valueOf(loadOrGetDefault(xpath, xmlDoc, "/search/fitnessInfo/fitnessMinimized/text()" , "false").trim());
String fcollect = loadOrGetDefault(xpath, xmlDoc, "/search/fitnessInfo/fitnessCollecting/text()" , FITNESS_COLLECTING.AT_FINAL_STEP.toString() );
fitnessCollecting = FITNESS_COLLECTING.valueOf( fcollect.trim() );
fitnessSamplingReplications = loadOrGetDefaultInt(xpath, xmlDoc, "/search/fitnessInfo/fitnessSamplingReplications/text()" , 10);
String fcombine = loadOrGetDefault(xpath, xmlDoc,"/search/fitnessInfo/fitnessCombineReplications/text()" , FITNESS_COMBINE_REPLICATIONS.MEAN.toString());
fitnessCombineReplications = FITNESS_COMBINE_REPLICATIONS.valueOf( fcombine.trim() );
fitnessDerivativeParameter = loadOrGetDefault(xpath, xmlDoc,"/search/fitnessInfo/fitnessDerivative/@parameter" , "");
fitnessDerivativeDelta = loadOrGetDefaultDouble(xpath, xmlDoc,"/search/fitnessInfo/fitnessDerivative/@delta" , 0.0);
fitnessDerivativeUseAbs = Boolean.valueOf(loadOrGetDefault(xpath, xmlDoc, "/search/fitnessInfo/fitnessDerivative/@useabs" , "false").trim());
NodeList paramSpecNodes = (NodeList) xpath.evaluate("/search/searchSpace/paramSpec/text()" , xmlDoc , XPathConstants.NODESET );
paramSpecStrings = new LinkedList<String>();
for (int i = 0; i < paramSpecNodes.getLength(); i++)
{
Node n = paramSpecNodes.item( i );
paramSpecStrings.add( n.getNodeValue().trim() );
}
searchMethodType = loadOrGetDefault(xpath, xmlDoc, "/search/searchMethod/@type" , "RandomSearch" );
NodeList methodParamNodes = (NodeList) xpath.evaluate("/search/searchMethod/searchMethodParameter" , xmlDoc , XPathConstants.NODESET );
searchMethodParams = new LinkedHashMap<String,String>();
for (int i = 0; i < methodParamNodes.getLength(); i++)
{
Element n = (Element) methodParamNodes.item( i );
searchMethodParams.put( n.getAttribute( "name" ) , n.getAttribute("value") );
}
chromosomeType = loadOrGetDefault(xpath, xmlDoc, "/search/chromosomeRepresentation/@type" , "MixedTypeChromosome" );
caching = Boolean.valueOf(loadOrGetDefault(xpath, xmlDoc, "/search/caching/text()" , "true"));
evaluationLimit = loadOrGetDefaultInt(xpath, xmlDoc, "/search/evaluationLimit/text()" , 300);
bestCheckingNumReplications = loadOrGetDefaultInt(xpath, xmlDoc, "/search/bestCheckingNumReplications/text()" , 0);
updateForVersionChanges();
}
/*
* Do a bit of processing so that old protocol files can be loaded...
*/
private void updateForVersionChanges()
{
if (bsearchVersionNumber < 0.72)
{
if (searchMethodType.equals("GenerationalGA"))
{
searchMethodType = "StandardGA";
}
}
}
// convenience method...
private static void xmlElementNoAtts(TransformerHandler hd, String elementName, String cData) throws SAXException
{
xmlElementWithAtts(hd,elementName,new AttributesImpl(), cData);
}
private static void xmlElementWithAtts(TransformerHandler hd, String elementName, AttributesImpl atts) throws SAXException
{
xmlElementWithAtts(hd,elementName,atts, "");
}
// another convenience method...
private static void xmlElementWithAtts(TransformerHandler hd, String elementName, AttributesImpl atts, String cData) throws SAXException
{
hd.startElement( "" , "" , elementName , atts);
if (cData.length() > 0) {
hd.characters( cData.toCharArray() , 0 , cData.length() );
}
hd.endElement( "" , "" , elementName );
}
/** Note: This method was adapted from
* http://www.javazoom.net/services/newsletter/xmlgeneration.html
*
* @param out PrintWriter to send the xml data to.
*/
public void save(Writer out)
{
try
{
StreamResult streamResult = new StreamResult( out ) ;
SAXTransformerFactory tf = (SAXTransformerFactory) TransformerFactory.newInstance() ;
// SAX2.0 ContentHandler.
TransformerHandler hd = tf.newTransformerHandler() ;
Transformer serializer = hd.getTransformer() ;
serializer.setOutputProperty( OutputKeys.ENCODING , "us-ascii" ) ;
serializer.setOutputProperty( OutputKeys.DOCTYPE_SYSTEM ,
"behaviorsearch.dtd" ) ;
serializer.setOutputProperty( OutputKeys.INDENT , "yes" ) ;
hd.setResult( streamResult ) ;
hd.startDocument() ;
AttributesImpl noAtts = new AttributesImpl() ;
AttributesImpl atts = new AttributesImpl() ;
hd.startElement( "" , "" , "search" , noAtts ) ;
xmlElementNoAtts(hd, "bsearchVersionNumber", String.format("%.2f", GeneralUtils.getVersionNumber()));
hd.startElement( "" , "" , "modelInfo" , noAtts );
xmlElementNoAtts(hd, "modelFile", modelFile ) ;
xmlElementNoAtts(hd, "modelSetupCommands", modelSetupCommands ) ;
xmlElementNoAtts(hd, "modelStepCommands", modelStepCommands ) ;
xmlElementNoAtts(hd, "modelStopCondition", modelStopCondition ) ;
xmlElementNoAtts(hd, "modelStepLimit", Integer.toString( modelStepLimit )) ;
xmlElementNoAtts(hd, "modelMetricReporter", modelMetricReporter) ;
xmlElementNoAtts(hd, "modelMeasureIf", modelMeasureIf) ;
hd.endElement( "" , "" , "modelInfo" );
hd.startElement( "" , "" , "fitnessInfo" , noAtts );
xmlElementNoAtts(hd, "fitnessMinimized", Boolean.toString(fitnessMinimized)) ;
xmlElementNoAtts(hd, "fitnessCollecting", fitnessCollecting.toString()) ;
xmlElementNoAtts(hd, "fitnessSamplingReplications", Integer.toString( fitnessSamplingReplications )) ;
xmlElementNoAtts(hd, "fitnessCombineReplications", fitnessCombineReplications.toString()) ;
if (fitnessDerivativeParameter.length() > 0)
{
atts.clear();
atts.addAttribute( "" , "" , "parameter" , "CDATA" , fitnessDerivativeParameter);
atts.addAttribute( "" , "" , "delta" , "CDATA" , Double.toString(fitnessDerivativeDelta));
atts.addAttribute( "" , "" , "useabs" , "CDATA" , Boolean.toString(fitnessDerivativeUseAbs));
xmlElementWithAtts(hd, "fitnessDerivative", atts);
}
hd.endElement( "" , "" , "fitnessInfo" );
hd.startElement( "" , "" , "searchSpace" , noAtts );
for (String ps: paramSpecStrings)
{
xmlElementNoAtts( hd , "paramSpec" , ps );
}
hd.endElement( "" , "" , "searchSpace");
atts.clear();
atts.addAttribute( "" , "" , "type" , "CDATA" , searchMethodType);
hd.startElement( "" , "" , "searchMethod" , atts );
for (String pName : searchMethodParams.keySet())
{
atts.clear();
atts.addAttribute( "" , "" , "name" , "CDATA" , pName );
atts.addAttribute( "" , "" , "value" , "CDATA" , searchMethodParams.get( pName ) );
hd.startElement( "" , "" , "searchMethodParameter" , atts );
hd.endElement( "" , "" , "searchMethodParameter" );
}
hd.endElement( "" , "" , "searchMethod" );
atts.clear();
atts.addAttribute( "" , "" , "type" , "CDATA" ,chromosomeType);
xmlElementWithAtts(hd, "chromosomeRepresentation", atts);
xmlElementNoAtts(hd, "caching", Boolean.toString(caching));
xmlElementNoAtts(hd, "evaluationLimit", Integer.toString(evaluationLimit));
xmlElementNoAtts(hd, "bestCheckingNumReplications", Integer.toString(bestCheckingNumReplications));
hd.endElement( "" , "" , "search" ) ;
hd.endDocument() ;
}
catch( SAXException e )
{
e.printStackTrace() ;
}
catch( TransformerConfigurationException e )
{
e.printStackTrace() ;
}
}
public String toXMLString()
{
java.io.StringWriter sw = new java.io.StringWriter();
save(sw);
sw.flush();
return sw.toString();
}
public static SearchProtocol load(String xmlStr)
throws java.io.IOException , SAXException
{
InputSource inputSource = new InputSource(
new java.io.StringReader(//"<!DOCTYPE search SYSTEM \"behaviorsearch.dtd\">\n" +
xmlStr));
return load(inputSource);
}
public static SearchProtocol load(InputSource inputSource)
throws java.io.IOException , SAXException
{
File resourcesDir = new File( GeneralUtils.attemptResolvePathFromBSearchRoot("resources/" ));
inputSource.setSystemId(resourcesDir.toURI().toString() ) ;
DocumentBuilderFactory fac = DocumentBuilderFactory.newInstance() ;
Document doc = null ;
try
{
// To validate, we may need to help it find the DTD file...
// inputSource.setSystemId( getClass().getResource( "/" ).toString() ) ;
fac = DocumentBuilderFactory.newInstance() ;
//TODO: Perhaps turn xml DTD validation back on? Or is that too strict? Is the DTD up to date?
//fac.setValidating( true ) ;
DocumentBuilder builder = fac.newDocumentBuilder() ;
// Choke on any error or warning during parsing, not just fatal errors.
builder.setErrorHandler( new ErrorHandler()
{
public void error( SAXParseException ex ) throws SAXException
{ throw ex ; }
public void fatalError( SAXParseException ex ) throws SAXException
{ throw ex ; }
public void warning( SAXParseException ex ) throws SAXException
{ throw ex ; }
} ) ;
doc = builder.parse( inputSource ) ;
return new SearchProtocol(doc);
}
catch( ParserConfigurationException ex )
{
ex.printStackTrace() ;
System.exit( 1 ) ;
}
catch( XPathExpressionException e )
{
e.printStackTrace();
System.exit( 1 ) ;
}
throw new IllegalStateException ("Unreachable code.");
}
public static SearchProtocol loadFile(String fullPathFilename) throws IOException, SAXException
{
return loadFile(new java.io.File(fullPathFilename));
}
public static SearchProtocol loadFile(java.io.File file) throws IOException, SAXException
{
BufferedReader reader = new BufferedReader(new FileReader(file));
try { return load(new InputSource(reader)); }
finally { reader.close(); }
}
public static enum FITNESS_COLLECTING {
AT_FINAL_STEP {
@Override
public double collectFrom(List<Double> results) { return results.get(results.size() - 1); }
@Override
public boolean requiresAllSteps() { return false; }
},
MEAN_ACROSS_STEPS {
@Override
public double collectFrom(List<Double> results) {
return bsearch.util.Stats.mean(results);
}},
MEDIAN_ACROSS_STEPS {
@Override
public double collectFrom(List<Double> results) {
return bsearch.util.Stats.median(results);
}},
MIN_ACROSS_STEPS {
@Override
public double collectFrom(List<Double> results) {
return Collections.min(results);
}},
MAX_ACROSS_STEPS {
@Override
public double collectFrom(List<Double> results) {
return Collections.max(results);
}},
VARIANCE_ACROSS_STEPS {
@Override
public double collectFrom(List<Double> results) {
return bsearch.util.Stats.variance(results);
}},
SUM_ACROSS_STEPS {
@Override
public double collectFrom(List<Double> results) {
return bsearch.util.Stats.sum(results);
}}
;
public abstract double collectFrom(List<Double> stepResults);
public boolean requiresAllSteps() { return true; }
}
public static enum FITNESS_COMBINE_REPLICATIONS {
MEAN {
@Override
public double combine(List<Double> results) {
return bsearch.util.Stats.mean(results);
}},
MEDIAN {
@Override
public double combine(List<Double> results) {
return bsearch.util.Stats.median(results);
}},
MIN {
@Override
public double combine(List<Double> results) {
return Collections.min(results);
}},
MAX {
@Override
public double combine(List<Double> results) {
return Collections.max(results);
}},
VARIANCE {
@Override
public double combine(List<Double> results) {
return bsearch.util.Stats.variance(results);
}},
STDEV {
@Override
public double combine(List<Double> results) {
return bsearch.util.Stats.stdev(results);
}}
;
public abstract double combine(List<Double> replicationResults);
}
public boolean useBestChecking() {
return bestCheckingNumReplications > 0;
}
}