package parameters; import java.io.*; import main.BetterTokenizer; import java.awt.*; import main.GeneralInput; import affectors.Affector; import parameterrules.ParameterRuleSet; import main.MoreMath; /** ParameterSet.java This class stores a set of Parameter objects, along with variables that might be associated with such a set. The associated variables, called value fields, often include a score, a number of function calls, a group number and tag for sets from an output file. They can also include other variables as output from an iterator, each with its own unique tag. See the ParameterSetArray and the Iterator classes for more information on including additional variables (called Value Fields).

You can use this class in two ways, with or without a prototype class. If you pass a prototype ParameterSet to the constructor, then only parameters which are in that prototype set will be added to this set. Others are ignored. Without a prototype set, any parameter sent to this set will be added (provided it exists in the model - see Parameter class). If a prototype is used, this class will look up the names for additional variables in that prototype class and will only store the values locally. Otherwise, this parameter set will ignore additional variables unless its load() or addOutputTag() functions are explicitly called.

@see Parameter @version 2/1/00 @author Eli */ public class ParameterSet { /** Get the position of a parameter in this array using the getPosition function */ public Parameter [] params; /** Note that not all params below numParams neccesarily exist. You have to check whether params[i] == null */ public int numParams; private int numSlots; private int groupNum = 0; protected String tag; private boolean pass = false; private float score = 0; private int numFuncCalls = 0; private ParameterSet itsPrototype = null; private final static int BLOCK_SIZE = 10; /** Stores values of variables associated with this parameter set. Note that at some point we may want to expand this to deal with non-numeric variables. Note - currently these will not work very well if there is a prototype but this parameter set tries to add extra value fields of its own. Also note that Score and NumFunctionCalls are automatically added to every parameter set that doesn't have a prototype - this is for legacy reasons. */ private float [] valueFields = null; /** Stores the names of the valueFields */ private String [] valueFieldNames = null; private int numValueFields = 0; private ParameterRuleSet rules = null; private ParameterRuleSet manipulations = null; /** Set to true if parameters should be put into the same order in this set as they are in them model. */ private boolean arrangeByModel = false; // Note - at some point I need to make all these settable private double minScore = 0.; private double maxScore = 1; private int goodred=255, goodgreen=0, goodblue=0; // defines colors for a perfect score of minScore; private int badred=20, badgreen=40,badblue=40; // defines color for a worst possible score of maxScore public ParameterSet() { this(null, 0, ""); } /** If you call a constructor with a prototype set, the only parameters that can be added to this set are those that are in the prototype. */ public ParameterSet(String str) { this(null,0,""); Parameter p; for(int i = 0; i < Affector.firstFreeParam; i++) { p = Affector.getParameter(i); if(p.name.charAt(0) != '+') addParameter(p); } } public ParameterSet(ParameterSet prototype) { this(prototype, 0, ""); } public ParameterSet(ParameterSet prototype, int group_num, String tag) { itsPrototype = prototype; groupNum = group_num; this.tag = tag; if(prototype == null) { params = new Parameter[BLOCK_SIZE]; for(int i = 0; i < BLOCK_SIZE; i++) params[i] = null; numSlots = BLOCK_SIZE; numParams = 0; // For legacy reasons, always have these around addValueField("Score", "number"); addValueField("NumFunctionCalls", "number"); } else { params = new Parameter[itsPrototype.numParams]; numSlots = itsPrototype.numParams; for(int i = 0; i < numSlots; i++) params[i] = null; numParams = 0; numValueFields = prototype.getNumValueFields(); valueFields = new float[numValueFields]; } } public ParameterSet copy() { ParameterSet ps = new ParameterSet(); for(int i = 0; i < numParams; i++) { ps.addParameter(params[i].copy()); } if(rules != null) ps.rules = rules.copy(); if(manipulations != null) ps.manipulations = manipulations.copy(); return ps; } /** If any of the passed in values are < 0, then the defaults are used (which come from the currently loaded model, via the Parameter constructor. This version makes a Parameter object and passes it to the other AddParameter. */ public void addParameter(String name, float value, float min, float max, int type) throws Exception { int pos; Parameter par = new Parameter(name); if(value >= 0) par.setValue(value); if(min >= 0 && max >= 0) par.setBounds(min, max); if(type == Parameter.LINEAR || type == Parameter.LOGARITHMIC) par.setVariationMode(type); addParameter(par); } /** If there is a prototype, this function tries to find the new parameter's name in the prototype. If it can't, then the new parameter is not added. If there is no prototype, checks whether there is already a parameter with this name in this set, and if so replaces it. Otherwise, the new parameter just gets added onto the end of the params array. */ public void addParameter(Parameter par) { int pos; if(itsPrototype != null) { // Check where the position of this one should be try { pos = itsPrototype.getPosition(par.name); params[pos] = par; } catch(Exception e) { return; } // If we have a prototype and this one doesn't exist in it, we don't add it } else { try { // Check if we already have this one, and if so, just update existing copy pos = getPosition(par.name); params[pos] = par; } catch(Exception e) { // It doesn't already exist, so add it if(numParams == numSlots) { // Need more memory Parameter [] temp_params = params; params = new Parameter[numSlots + BLOCK_SIZE]; System.arraycopy(temp_params, 0, params, 0, numSlots); for(int i = numSlots; i < numSlots + BLOCK_SIZE; i++) params[i] = null; numSlots += BLOCK_SIZE; } params[numParams] = par; pos = numParams; } } if(numParams <= pos) numParams = pos + 1; } public void removeParameter(String name) { try { int pos = getPosition(name); if(itsPrototype != null) params[pos] = null; else { // Move other params back for(int i = pos; i < numParams - 1; i++) { params[i] = params[i + 1]; } params[numParams - 1] = null; numParams--; } } catch(Exception e) { System.out.println("Error while trying to remove parameter from set: " + e.toString()); } } /** Gets the position of a parameter called "name" in this sets params array. If its not there, throws an exception */ public int getPosition(String name) throws Exception { int i = 0; while(i < numParams && (params[i] == null || !name.equals(params[i].name))) i++; if(i < numParams) return i; throw new Exception("Parameter <" + name + "> not found"); } /** Sets parameters in this set from the set that's passed in, for all parameters that appear in both */ public void setFrom(ParameterSet set) { for(int i = 0; i < set.numParams; i++) { if(set.params[i] != null) { try { int pos = getPosition(set.params[i].name); params[pos].setValue(set.params[i].value); } catch(Exception e) {} // Doesn't exist in this set - just ignore } } } /** This is for setting an Iterator's internal parameter vector p */ public float [] getParVals() { float [] vals = new float[numParams]; for(int pos = 0; pos < numParams; pos++) { if(params[pos] != null) vals[pos] = params[pos].value; else vals[pos] = 0; } return vals; } /** And this is for setting par values from the same vector. Note that these methods assume a fixed order and # of pars **/ public void setFrom(float [] vals) { for(int pos = 0; pos < numParams; pos++) { if(params[pos] != null) params[pos].setValue(vals[pos]); } } public boolean areParametersGood(float [] params, int mode) { if(rules == null) return true; return rules.areParametersGood(params, mode, this); } public float getFuzzyParametersScore(float [] params) { if(rules == null) return 0.0f; return rules.getFuzzyParametersScore(params); } public ParameterSet getPrototype() {return(itsPrototype); } public float getScore() { return getFieldValue("Score"); } public void setScore(float score) { setFieldValue("Score", score); } public int getGroupNum() { return groupNum; } public void setGroupNum(int num) { groupNum = num; } public String getTag() { return tag; } public void setTag(String tag) { this.tag = tag; } public int getNumFuncCalls() { return (int)getFieldValue("NumFunctionCalls"); } public void setNumFuncCalls(int num) { setFieldValue("NumFunctionCalls", num); } public int getVariationMode(int pos) { return params[pos].getVariationMode(); } public void setVariationMode(int pos, int mode) { params[pos].setVariationMode(mode); } public boolean didPass() { return pass; } public void setPass(boolean pass) { this.pass = pass; } public Parameter getParameter(int i) { if(params[i] == null) return null; else return params[i].copy(); } public void setParameter(Parameter par, int pos) { if(pos < params.length) params[pos] = par; } public String getName(int pos) { return params[pos].name; } public float getValue(int pos) { return params[pos].value; } public void setValue(int pos, float val) { params[pos].value = val; } public float getUpperBound(int i) { return(params[i].upperBound); } public void setUpperBound(int pos, float val) { params[pos].upperBound = val;} public float getLowerBound(int i) { return(params[i].lowerBound); } public void setLowerBound(int pos, float val) { params[pos].lowerBound = val;} public float [] getUpperBounds() { float [] ub = new float[numParams]; for(int i = 0; i < numParams; i++) ub[i] = params[i].upperBound; return ub; } public float [] getLowerBounds() { float [] lb = new float[numParams]; for(int i = 0; i < numParams; i++) lb[i] = params[i].lowerBound; return lb; } public int getNumParams() { return numParams; } public void getModel() { for(int i = 0; i < numParams; i++) { try { params[i].setValue(Affector.getParamValue(params[i].getName())); } catch(Exception e) { System.out.println("Tried to get parameter " + params[i].getName() + " from model in ParameterSet, and it doesn't exist"); } } } /** Sets the currently loaded model according to the parameters in this parameter set. Any parameters not in this set are left alone. */ public void setModel() { float [] vals = getParVals(); // If there are manipulations, make a copy of param set and manipulate that if(manipulations != null) { manipulations.areParametersGood(vals, MoreMath.UNIFORM, this); } for(int i = 0; i < numParams; i++) { if(params[i] != null) { Affector.setParamValue(params[i].getName(), vals[i]); } } } public boolean makeNewPoint(int mode, ParameterSet nochange) { boolean good = false; int times = 0; while(!good) { for(int i = 0; i < getNumParams(); i++) { int nochangepos = -1; try{ if(nochange != null) nochangepos = nochange.getPosition(getName(i)); } catch (Exception e) {} if(nochangepos < 0) { if(mode == MoreMath.UNIFORM) { if(getVariationMode(i) == Parameter.LINEAR) { setValue(i, MoreMath.pickRandomLinear(getLowerBound(i), getUpperBound(i))); } else if(getVariationMode(i) == Parameter.LOGARITHMIC) { setValue(i, MoreMath.pickRandomLogarithmic(getLowerBound(i), getUpperBound(i))); } } else if(mode == MoreMath.GAUSSIAN) { // Note - gaussian ought to change to deal better with logarithmic ones // Actually, I don't really trust this at all... float temp = MoreMath.randNeg(mode) * ((getLowerBound(i) +getUpperBound(i))/2); if(temp < getLowerBound(i)) temp = getLowerBound(i); if(temp > getUpperBound(i)) temp = getUpperBound(i); setValue(i, temp); } } } float [] values = getParVals(); good = areParametersGood(values, mode); if(good) setFrom(values); times++; if(times % 10000 == 0) System.out.println("Make New Point tried " + times + " times without success"); } return (good); } public boolean makeNewPoint(int mode) { return makeNewPoint(mode, null); } /** This function loads a parameter set from an output file, tagged with the FPARS tag. It will load not only the parameters, but also any associated score and numFunctionCalls. This function calls loadParameterValues with need_all = false to load the actual values.

To load from an input file, call loadParameterValues directly (with need_all = true for model files). */ public void loadParameters(BetterTokenizer tokenizer) throws Exception { boolean done = false; GeneralInput.indent++; String param_name = GeneralInput.findNextIDToken(tokenizer); while(!done && !param_name.equals("endParamsToVary") && !param_name.equals("endFPARS") && !param_name.equals("endIPARS") && !param_name.equals("endParameterValues")) { if(param_name.equals("FPARS") || param_name.equals("IPARS")) { loadParameterValues(tokenizer, false); done = true; // Cause loadParameterValues will only quit out after seeing an endPars } else if(param_name.equals("ParameterRules")) { rules = new ParameterRuleSet(this); rules.loadParameters(tokenizer); } else if(param_name.equals("ParameterManipulations")) { manipulations = new ParameterRuleSet(this); rules.loadParameters(tokenizer); } else { // Look up the value field location for this tag int pos = getFieldPos(param_name); if(pos >= 0) { GeneralInput.nextToken(tokenizer); setFieldValue(pos, (float)tokenizer.nval); } } if(!done) param_name = GeneralInput.findNextIDToken(tokenizer); } GeneralInput.indent--; } /** Loads a set of parameters from a stream. Will stop at either an endParamsToVary or an endFPARS tag. Any other tags it assumes are names of a parameter and it will try to load that parameter and then add it to this set using the addParameter function. If it encounters a ParameterRules tag it will make a ParameterRuleSet and call that objects load method. @param need_all Gets passed into the Parameter objects loadParameters function. */ public void loadParameterValues(BetterTokenizer tokenizer, boolean need_all) throws Exception { GeneralInput.indent++; String param_name = GeneralInput.findNextIDToken(tokenizer); // read to end of parameter list while(!param_name.equals("endParamsToVary") && !param_name.equals("endFPARS") && !param_name.equals("endIPARS") && !param_name.equals("endParameterValues")) { try { if(param_name.equals("ParameterRules")) { if(arrangeByModel) arrangeParametersByModel(); // Put them in the right order before loading param rule rules = new ParameterRuleSet(this); rules.loadParameters(tokenizer); } else if(param_name.equals("ParameterManipulations")) { if(arrangeByModel) arrangeParametersByModel(); // Put them in the right order before loading param rule manipulations = new ParameterRuleSet(this); manipulations.loadParameters(tokenizer); } else { Parameter par = new Parameter(param_name); par.loadParameters(tokenizer, need_all); addParameter(par); } } catch(Exception e) { System.out.println("Problem with parameter " + param_name + ": " + e.toString()); } param_name = GeneralInput.findNextIDToken(tokenizer); } GeneralInput.indent--; if(arrangeByModel) arrangeParametersByModel(); // Put them in the right order before loading param rule } /** Rearranges the parameters in this set so that the position in set corresponds to the position of the parameter in Affector. If any parameters are missing, those positions will be set to null. If there are extra parameters (not in Affector) they will be put at the end of set, after the last Affector parameter. */ public void arrangeParametersByModel() { int last_param = numParams; for(int pos = 0; pos < Affector.firstFreeParam; pos++) { try { int pos_in_params = getPosition(Affector.getParamName(pos)); // Swap the positions of the one that should be here with the one that is here if(pos_in_params != pos) { Parameter temp_param = params[pos]; params[pos] = params[pos_in_params]; params[pos_in_params] = temp_param; } } catch(Exception e) { // parameter not found in affectors so move it to end of array if(last_param == numSlots) { // Need more memory Parameter [] temp_params = params; params = new Parameter[numSlots + BLOCK_SIZE]; System.arraycopy(temp_params, 0, params, 0, numSlots); for(int i = numSlots; i < numSlots + BLOCK_SIZE; i++) params[i] = null; numSlots += BLOCK_SIZE; } params[last_param] = params[pos]; params[pos] = null; last_param++; } } // I think this is safe for not putting blank spaces in at the end ... numParams = last_param; } /** Set to true if parameters should be put into the same order in this parameter set as they are in the model. Note that this will only happen automatically when loading parameters from a file. If parameters are added using addParameter, you must call arrangeParametersByModel to arrange them. */ public void setArrangeByModel(boolean arrange) { arrangeByModel = arrange; } /********************** Value Fields **/ public void loadValueFieldNames(BetterTokenizer tokenizer) throws Exception { String param_name = GeneralInput.findNextIDToken(tokenizer); while( !param_name.equals("endValueFieldNames")) { GeneralInput.nextToken(tokenizer); // Because of legacy code, score and numfunctioncalls already included so // don't add them again. if(param_name.equals("Score") || param_name.equals("NumFunctionCalls")) {} else addValueField(param_name, tokenizer.sval); param_name = GeneralInput.findNextIDToken(tokenizer); } } public void copyValueFieldNames(ParameterSet ps) { numValueFields = ps.numValueFields; valueFieldNames = new String[numValueFields]; System.arraycopy(ps.valueFieldNames, 0, valueFieldNames, 0, numValueFields); valueFields = new float[numValueFields]; } public void addValueField(String name, String type) { if(valueFieldNames == null) { valueFieldNames = new String[3]; valueFields = new float[3]; } if(numValueFields == valueFieldNames.length) { String [] temp = new String[valueFieldNames.length + 3]; System.arraycopy(valueFieldNames, 0, temp, 0, valueFieldNames.length); valueFieldNames = temp; float [] temp_vals = new float[valueFields.length + 3]; System.arraycopy(valueFields, 0, temp_vals, 0, valueFields.length); valueFields = temp_vals; } valueFieldNames[numValueFields] = name; if(! type.equals("number")) { System.out.println("Value Fields are currently restricted to numbers only and cannot be of type <" + type + ">"); } numValueFields++; } public int getNumValueFields() { return numValueFields; } public float getFieldValue(int pos) { return valueFields[pos]; } public float getFieldValue(String name) { int pos = getFieldPos(name); if(pos >= 0) return getFieldValue(pos); else return -1; } public void setFieldValue(int pos, float value) { valueFields[pos] = value; } public void setFieldValue(String name, float value) { int pos = getFieldPos(name); if(pos >= 0) valueFields[pos] = value; } public String getFieldName(int pos) { if(itsPrototype != null) return itsPrototype.getFieldName(pos); else return valueFieldNames[pos]; } public int getFieldPos(String name) { if(itsPrototype != null) return itsPrototype.getFieldPos(name); else { int pos = 0; while(pos < numValueFields && !name.equals(valueFieldNames[pos])) pos++; if(pos < numValueFields) return pos; else return -1; } } /************************ Drawing **************************/ /** ParameterSet objects can draw themselves as cam's using this draw command. I should add in stuff to change what color they use, etc.. ¶m xcen The center x position of the cam ¶m ycen The center y position of the cam ¶m rad The length of each cam arm. ¶m cosThetas The cosine of the angle to draw each parameter at. ¶m sinThetas The sine of the angle to draw each parameter at. */ public void draw(Graphics g, int xcen, int ycen,int rad, double [] cosThetas, double [] sinThetas) { double val; // Figure out where last point is so we make a complete circle if(params[numParams - 1] != null) val = findRadius(numParams - 1); else val = 0.; int xto = xcoord(val,xcen, rad, cosThetas[numParams-1]); int yto = ycoord(val,ycen, rad, sinThetas[numParams-1]); int xfrom,yfrom; g.setColor(scoreColor(score)); for(int i = 0; i < numParams; i++) { if(params[i] != null) { val = findRadius(i); xfrom=xto; yfrom=yto; xto=xcoord(val,xcen, rad, cosThetas[i]); yto=ycoord(val,ycen, rad, sinThetas[i]); g.drawLine(xfrom,yfrom,xto,yto); } else { // In here, should break line apart somehow } } } private double findRadius(int pos) { if(params[pos].lowerBound == params[pos].upperBound) { return 0.25; } if(params[pos].variationMode == Parameter.LOGARITHMIC) { double logvar = Math.log(params[pos].value); double logmin = Math.log(params[pos].lowerBound); double logmax = Math.log(params[pos].upperBound); return .25 + .75*(logvar - logmin)/(logmax-logmin); } else { return .25 + .75*(params[pos].value - params[pos].lowerBound) / (params[pos].upperBound - params[pos].lowerBound); } } private int xcoord(double val, int xcen, int rad, double cosTheta) { return ((int )(xcen + rad*val*cosTheta)); } private int ycoord(double val, int ycen, int rad, double sinTheta) { return ((int )(ycen + rad*val*sinTheta)); } private Color scoreColor(double ascore) { double alpha = (ascore-minScore)/(maxScore-minScore); if(alpha<0) alpha=0; if(alpha>1.) alpha=1.; double beta=1.-alpha; int red = (int)(alpha*badred + beta*goodred); int blue = (int)(alpha*badblue + beta*goodblue); int green = (int)(alpha*badgreen + beta*goodgreen); return(new Color(red,green,blue)); } public String toString() { String s = "("; for(int i = 0; i < numParams; i++) { s = s + params[i].toString(false); } s = s + ")"; return s; } public void toString(PrintWriter ps, String indent) { for(int i = 0; i < numParams; i++) { ps.println(indent + params[i].toString(true)); } } }