package affectors; import java.util.*; import java.io.PrintWriter; import main.Node; import main.Cell; import main.Globals; import parameters.Parameter; /** Stores the formula for how a node changes over time. Each affector encapsulates a particular bit of the full formula for each node, generally an additive term of the differential equation. The base class Affector also stores the current values of all the parameters in the model. */ abstract public class Affector extends Object implements Cloneable { static int step_num; static float [] params; static float [] upperBounds; static float [] lowerBounds; static int [] variationMode; static String [] paramNames; public static int firstFreeParam = 0; // Used when constructing the network to stake out parameter space // The following static variables are used to store default ranges for different // kinds of parameters. Every time a new parameter is added, its prefix is checked // against all the names in paramDefaultNames. If its found, the new parameter is // given the corresponding default bounds and variation mode. These can still be // overridden. static Vector defaultParamNames = new Vector(), defaultValues = new Vector(), defaultUpperBounds = new Vector(), defaultLowerBounds = new Vector(), defaultVariationMode = new Vector(); /** This is used while constructing a network to figure out which cell each node comes from. If you make a new affector that is internal, set sides = null. For membrane affectors, allocate one array position in sides for each node used in the affector and set sides[node_pos] = -1 if the node is in the same cell as that being changed by the affector, or the side (passed into the initializer of the affector) if the node is on the opposite cell. */ int [] sides = null; int side, otherSide; /** This is the list of Nodes this affector uses to compute its formula, maybe including the Node that "owns" it. */ Node [] Nodes; /** The number of nodes used by this affector (number of nodes in the Nodes array). */ int numNodes = 0; /** The number of parameters used by this affector. */ int numParameters = 0; /** This is here to let input and output code (including the UI) figure out which parameters are being used by an affector. It contains a list of Integer objects corresponding to the parameters requested with getParamPos() from the initialization code. This vector is not used by the model running code. */ // Vector itsParameters = new Vector(); /** If an Affector's formula includes the target Node (the one that "owns" it), set containsTarget=true. */ boolean containsTarget = false; boolean notFixed = true; public float copyNumber = 1; // QUESTION: is this used by anyone? public int affectorType = CONVERSION; static final int PLUS_OP = 1; static final int MINUS_OP = 2; static final int TIMES_OP = 3; static final int DIVIDE_OP = 4; static final int EXP_OP = 5; /** The Type array is public only so it can be read without the cost of a method call... NO ONE should set any value in this array except in the constructor of and Affector subclass, and then only when you know what you're doing. */ public int [] Type; /** Position meanings for Type[]. */ public static final int TERMTYPE=0,MATHTYPE=1,CERTIFICATION=2,GUI_CAPABLE=3; /** Type.[] default value. */ public static final int UNSPECIFIED=0; /** Type.[MATHTYPE] values; FF refers to a nonlinear function of only *other* nodes, GG is a nonlinear function of other nodes times the target node, HH is a constant times the target node, KK is a constant times some other nodes, CC is a constant, TT is a function only of time. */ public static final int FF=1,GG=2,HH=3,KK=4,CC=5,TT=6; /** Type.[TERMTYPE] values; */ public static final int PRODUCTION=1,CONVERSION=2,DEGRADATION=3; /** Type.[CERTIFICATION] values; in the constructor of a subclass, once you have made sure the formula in getValue (and getNCValue if necessary) is correct, set this.Type.[CERTIFICATION]=Affector.RETURNS_DERIV, and once your subclass knows how to return a list of partial derivatives, set it to RETURNS_PARTIALS. */ public static final int RETURNS_DERIV=1,RETURNS_PARTIALS=2; /** Descriptors for use by the GUI; set these to point to static final Strings that say in as few words as possible what the Affector does (ADesc), what nodes need to be specified (NDesc), and what parameters it uses (PDesc). Once your Affector subclass does all this, set this.Type.[GUI_CAPABLE]=1*/ public String ADesc=""; public String [] NDesc=null; public String [] PDesc=null; static int debug_num = 0; int this_debug_num = debug_num++; static { clearStatic(); } static public void clearStatic() { params = new float[Globals.maxNumParams]; paramNames = new String[Globals.maxNumParams]; upperBounds = new float[Globals.maxNumParams]; lowerBounds = new float[Globals.maxNumParams]; variationMode = new int[Globals.maxNumParams]; firstFreeParam = 0; } Affector() { Type = new int[10]; setLabelsAndTypes(); } abstract protected void setLabelsAndTypes(); abstract public void setParameterNumbers(int [] param_nums); public int getNumNodes() { return numNodes; } public int getNumParameters() { return numParameters; } public void init(int side, int num_params, String [] params) { } protected void setDescriptions(String aff_desc, String [] node_desc, String [] param_desc) { ADesc = aff_desc; NDesc = node_desc; PDesc = param_desc; if(node_desc != null) numNodes = node_desc.length; else numNodes = 0; if(param_desc != null) numParameters = param_desc.length; else numParameters = 0; } protected void setSided(boolean sided, int [] which_sides) { if(sided) { sides = new int[numNodes]; for(int i = 0; i < numNodes; i++) sides[i] = which_sides[i]; } } public void setSide(int side) { this.side = side; otherSide = getOtherSide(side); if(sides != null) { for(int i = 0; i < sides.length; i++) if(sides[i] != -1) sides[i] = side; } } protected void setContainsTarget(boolean b) { containsTarget = b; } public boolean isLinearInTarget() { if(Type[MATHTYPE] == GG || Type[MATHTYPE] == HH) return true; else return false; } public float getValue(Node which_node) { System.out.println("Bad call: Affector.getValue()"); return(0.0f); } public float getNCValue(Node which_node) { System.out.println("Bad call: Affector.getNCValue()"); return(0.0f); } public boolean doesContainTarget(Node which_node) { return containsTarget; } public void copy_(Affector cp) { if(sides != null) { cp.sides = new int[sides.length]; System.arraycopy(sides, 0, cp.sides, 0, sides.length); } cp.Type = new int[10]; for(int i=0; i < 10; i++) cp.Type[i] = Type[i]; } public Affector copy() { Affector cp = null; try { cp = (Affector)this.clone(); } catch(CloneNotSupportedException e) {} copy_(cp); cp.this_debug_num = debug_num++; return(cp); } public void fixNodes(Cell cell, String [] node_names) throws Exception { if(notFixed) { if(node_names != null) { Nodes = new Node[node_names.length]; for(int i = 0; i < node_names.length; i++) { if(sides == null || sides[i] == -1) Nodes[i] = cell.getNode(node_names[i]); else Nodes[i] = cell.getNeighborNode(sides[i], node_names[i]); } } else Nodes = null; sides = null; notFixed = false; } } public static int getOtherSide(int side) { if(side != -1) { side += Globals.cellNumSides / 2; if(side > Globals.cellNumSides - 1) side -= Globals.cellNumSides; } return(side); } public final float Phi(float M, float K, float nu) { if(M < 0) M = 0; double temp = Math.pow((double)(M/K),(double)nu); return (float)(temp/(1 + temp)); } public final float Psi(float M, float K, float nu) { if(M < 0) M = 0; double temp = Math.pow((double)(M/K),(double)nu); return (float)(1/(1 + temp)); } public final float Chi(float M, float K, float nu) { if(M < 0) M = 0; double temp = Math.pow((double)(M/K),(double)nu); return (float)temp; } public static int findParam(String name) throws Exception { int i = 0; while(i < firstFreeParam && !paramNames[i].equals(name)) i++; if(i == firstFreeParam) throw new Exception("Parameter " + name + " not found (Affector.findParam())"); return(i); } /** for use of the CalculatorNode or other things that need to look up parameters if they already exist but make them if they do not... mimics getParamPos. Use with care. */ public static int findOrCreateParam(String name) throws Exception { int pos; try { pos = findParam(name); // If the param didn't exist, findParam throws exception and we don't get past here return pos; } catch(Exception e) { // This must be a new parameter if(name == "") throw new Exception("Can't make a parameter without a name"); pos = makeNewParameter(name); return pos; } } /** This function should only be called when constructing a model from the init() function of an iterator. If called from other places, it may screw stuff up. */ public static int getParamPos(String name) throws Exception { if(name == "") throw new Exception("Tried to make a parameter without a name"); int pos; try { pos = findParam(name); // If the param didn't exist, findParam throws exception and we don't get past here // itsParameters.addElement(new Integer(pos)); // Store param so it can be looked up outside of affector return pos; } catch(Exception e) { // This must be a new parameter pos = makeNewParameter(name); // itsParameters.addElement(new Integer(pos)); // Store param so it can be looked up outside of affector return pos; } } private static int makeNewParameter(String name) { params[firstFreeParam] = 1; if(name.charAt(0) == '+') { // This is a complex parameter, so break it apart StringTokenizer tokens = new StringTokenizer(name); String param_str = ""; while(tokens.hasMoreTokens()) { String str = tokens.nextToken(); if(str.equals("+") || str.equals("-") || str.equals("*") || str.equals("/") || str.equals("^")) param_str = param_str + str; else { int pos = makeNewParameter(str); param_str = param_str + (char)(pos / 256) + (char)(pos % 256); } } paramNames[firstFreeParam] = param_str; } else { paramNames[firstFreeParam] = new String(name); variationMode[firstFreeParam] = Parameter.NOT_SET; // Flag for input // See if the name matches anything on default list, and if so, fill in values Enumeration enum = defaultParamNames.elements(); int i = 0; while(enum.hasMoreElements()) { if(name.startsWith((String)enum.nextElement())) { params[firstFreeParam] = ((Float)defaultValues.elementAt(i)).floatValue(); lowerBounds[firstFreeParam] = ((Float)defaultLowerBounds.elementAt(i)).floatValue(); upperBounds[firstFreeParam] = ((Float)defaultUpperBounds.elementAt(i)).floatValue(); variationMode[firstFreeParam] = ((Integer)defaultVariationMode.elementAt(i)).intValue(); } i++; } } firstFreeParam++; return(firstFreeParam - 1); } public static String getParamName(int pos) { return paramNames[pos]; } public static int getParamVariationMode(int pos) { return variationMode[pos]; } public static void setParamRange(String name, float lower, float upper, String mode) throws Exception { if(mode.equals("Linear") || mode.equals("linear")) setParamRange(name, lower, upper, Parameter.LINEAR); else if(mode.equals("Logarithmic") || mode.equals("logarithmic")) setParamRange(name, lower, upper, Parameter.LOGARITHMIC); else { throw new Exception("Variation mode " + mode + " for parameter " + name + ": does not compute!"); } } public static void setParamRange(String name, float lower, float upper, int mode) { int pos; try { pos = findParam(name); } catch(Exception e) { System.out.println(e.toString()); return; } lowerBounds[pos] = lower; upperBounds[pos] = upper; variationMode[pos] = mode; } /** Use this to read a Parameter object, identified by name, from the static arrays held by Affector. */ public static Parameter getParameter(String name) { int pos; try { pos = findParam(name); } catch(Exception e) { return null; } Parameter p = new Parameter(name, params[pos], upperBounds[pos], lowerBounds[pos], variationMode[pos]); return p; } /** Use this to read a Parameter object, identified by number, from the static arrays held by Affector. */ public static Parameter getParameter(int pos) { Parameter p = new Parameter(paramNames[pos], params[pos], upperBounds[pos], lowerBounds[pos], variationMode[pos]); return p; } /** Use this to set a parameter using a Parameter object. */ public static void setParameter(Parameter p) { int pos; try { pos = findParam(p.name); setParamValue(pos, p.value); String mode = ""; if(p.variationMode == Parameter.LINEAR) mode = "Linear"; else if(p.variationMode == Parameter.LOGARITHMIC) mode = "Logarithmic"; setParamRange(p.name, p.lowerBound, p.upperBound, mode); } catch(Exception e) { System.out.println(e.toString()); return; } } /** Use this instead of getting a whole Parameter object if all you want is the value of a parameter identified by its position. */ public static float getParamValue(int pos) { return params[pos]; } /** Use this instead of getting a whole Parameter object if all you want is the named parameter's value. */ public static float getParamValue(String name) throws Exception{ return params[findParam(name)]; } public static void setParamValue(String name, float val) { try { setParamValue(findParam(name),val); } catch (Exception e) {} } public static void setParamValue(int param_pos, float val) { params[param_pos] = val; // Now we need to find any complex parameters that might include this one, and reset their values as well int i = 0; while(i < firstFreeParam) { if(paramNames[i].charAt(0) == '+') { // For now, + is marker for a complex parameter - yuck // Recalculate this parameter string in case it includes the newly set parameter // In the future, should probably put something in that only recalculates if necesary // ... or better yet, do something smarter altogether... int strpos = 0; float num = 0; while(strpos + 2 < paramNames[i].length()) { int pos = (int)paramNames[i].charAt(strpos + 1) * 256 + (int)paramNames[i].charAt(strpos + 2); switch(paramNames[i].charAt(strpos)) { case '+': num += params[pos]; break; case '-': num -= params[pos]; break; case '*': num *= params[pos]; break; case '/': num /= params[pos]; break; case '^': num = (float)Math.pow((double)num, (double)params[pos]); break; } strpos += 3; } params[i] = num; } i++; } } public static void addDefaultParam(String name, float val, float low, float high, String variation) throws Exception { defaultParamNames.addElement(name); defaultValues.addElement(new Float(val)); defaultLowerBounds.addElement(new Float(low)); defaultUpperBounds.addElement(new Float(high)); if(variation.equals("Linear")) defaultVariationMode.addElement(new Integer(Parameter.LINEAR)); else if(variation.equals("Logarithmic")) defaultVariationMode.addElement(new Integer(Parameter.LOGARITHMIC)); else { throw new Exception("Variation mode " + variation + " for default parameter " + name + ": does not compute!"); } } /** This function is here so that viewers can examine an affector. They should not be used by any of the modeling classes themselves. */ public Node [] getNodes() { return Nodes; } }