package main; import affectors.Affector; /** a CalculatorNode is a wrapper useful for doing arithmetic on a group of Nodes, like treating a group of similar Nodes as if it were a single pool. For instance, one might have several phosphoisoforms of a protein that all participate in some transcriptional regulatory process which is insensitive to the phosphorylation state. Or, one might find this handy for merely displaying the sum total of some protein in all its various forms. BE CAREFUL because one shouldn't use something like this to enter into reversible conversion reactions (e.g. ligand binding) of any kind. At present this thing only knows how to do a few things: sum a list of Nodes, make a ratio of two Nodes, and compute a weighted sum of several nodes. Which it does is based on the Node Type flag. If it is type RATIO and it has more than two Nodes to handle it will ignore all but the first two. The WEIGHTEDSUM type uses parameters, as many as twice the number of Nodes, to compute a sum over each node times a ratio of two parameters. If there are n Nodes, parameters 1 to n are the numerators, and parameters n+1 to 2n are the denominators, if they exist. BE CAREFUL because the order of the input Node references must match the order of input parameter references, and if not all terms are to be multiplied by parameters, but some terms have denominators, the input to this object needs to include references to a dummy parameter with value 1 to fill the empty numerator slots. Sorry, sort of kludgey, but it's easier to program that way. In the SUM and RATIO types, the parameters can also be used to make a ratio that multiplies the whole term; in this case the list is interpreted as numerator-denominator-numerator-denominator-etc..., and can be padded with dummy references to a value of 1 as needed. The CalculatorNode can also be set up with a "Normalized" option, either "Max" or "Min" that works like so: the RATIO type ignores it, the SUM type will divide the whole term by the value of either the maximum or minimum term, and the WEIGHTEDSUM by the maximum or mimimum *coefficient* that appears in the sum. */ public class CalculatorNode extends Node implements Cloneable { static final int MAXPARS=20, MAXNODES=10; static final int MIN=0, MAX=1; // for normalization int myNodeCount=0; Node [] myNodes; String [] myNodeNames; int myParCount=0; int [] myPars; String [] myParNames; boolean normalize=false; int minOrMax=MAX; // just guessing that will be the most common use float minimum = java.lang.Float.MAX_VALUE, maximum = 0.0f; int itsType = NodeTemplate.SUM; CalculatorNode(int node_num, int num_sides) { super(node_num, num_sides); } public void setType(int its_type) { itsType = its_type; } protected void init(int node_num) { super.init(node_num); myNodeNames = new String[MAXNODES]; myParNames = new String[MAXPARS]; } // overridden so that Cash-Karp arrays are never allocated public void setNumSides(int num_sides) { numSides = num_sides; values = new float[numSides]; integrationValues = new float[numSides]; } // overridden so that Cell doesn't have to know about this special Node type; this // hijacks the Cell reference to find Nodes handled by Affector-less CalculatorNodes, // and also finds the appropriate parameter references. public void fixAllAffectors(Cell cell) throws Exception { myNodes = new Node[myNodeCount]; for(int i = 0; i < myNodeCount; i++) { myNodes[i] = cell.getNode(myNodeNames[i]); } } // overridden so that Cells don't have to know anything about CalculatorNodes and // can treat these like Nodes but do no calculation. Integrators will collect the // data from CalculatorNodes, grind away at it (at this point), and set the values[] // and integrationValues[]. However, these arrays are never read by calls to // getIntegrationValue(), and when Integrators call getAffectorsValue(float[]) // they will get a load of zeros, so CalculatorNodes shouldn't contribute to the error // estimates. Nevertheless, Integrators will spin their wheels over these things // at present. This is messy and ought to change eventually. public void takeFPStep() { } public void updateFPStep() { } public void storeFinalValues() { } public void setFromFinalValues() { } public void ckIntegrate() { } public void ckUpdateToNewTimestep() { } public float ckGetErrorEstimate() { return 0.0f; } public float getIntegrationValue(int side) { float value = 0.0f, temp = 0.0f; if(normalize) { minimum = java.lang.Float.MAX_VALUE; maximum = 0.0f; } if(numSides == 1) { value = this.getIntegrationValue(); // if the Calculator is not sided but handles sided nodes, we want sums over the whole cell... } else { // ...but if the Calculator is sided, then we want side-specific sums EVEN THOUGH it may handle unsided nodes; the first line in Node.getIntegrationValue(int) makes sure that unsided Nodes respond as if they were passed 0 for the side if(itsType == NodeTemplate.SUM) { for(int i = 0; i < myNodeCount; i++) { temp = myNodes[i].getIntegrationValue(side); value += temp; if(normalize) { if(temp > maximum) maximum = temp; if(temp < minimum) minimum = temp; } } if(myPars != null) value *= makeRatio(); if(normalize) { if(minOrMax == MIN) value /= minimum; else value /= maximum; } } else if(itsType == NodeTemplate.RATIO) { value = myNodes[1].getIntegrationValue(side); value /= myNodes[2].getIntegrationValue(side); if(myPars != null) value *= makeRatio(); } else if(itsType == NodeTemplate.WEIGHTEDSUM) { int j = myNodeCount; for(int i = 0; i < myNodeCount; i++,j++) { temp = 1.0f; if(i < myParCount) temp *= Affector.getParamValue(myPars[i]); if(j < myParCount) temp /= Affector.getParamValue(myPars[j]); value += temp * myNodes[i].getIntegrationValue(side); if(normalize) { if(temp > maximum) maximum = temp; if(temp < minimum) minimum = temp; } } if(normalize) { if(minOrMax == MIN) value /= minimum; else value /= maximum; } } } return value; } public float getIntegrationValue() { float value = 0.0f, temp = 0.0f; if(normalize) { minimum = java.lang.Float.MAX_VALUE; maximum = 0.0f; } if(itsType == NodeTemplate.SUM) { for(int i = 0; i < myNodeCount; i++) { temp = myNodes[i].getIntegrationValue(); value += temp; if(normalize) { if(temp > maximum) maximum = temp; if(temp < minimum) minimum = temp; } } if(myPars != null) value *= makeRatio(); if(normalize) { if(minOrMax == MIN) value /= minimum; else value /= maximum; } } else if(itsType == NodeTemplate.RATIO) { value = myNodes[1].getIntegrationValue(); value /= myNodes[2].getIntegrationValue(); if(myPars != null) value *= makeRatio(); } else if(itsType == NodeTemplate.WEIGHTEDSUM) { int j = myNodeCount; for(int i = 0; i < myNodeCount; i++,j++) { temp = 1.0f; if(i < myParCount) temp *= Affector.getParamValue(myPars[i]); if(j < myParCount) temp /= Affector.getParamValue(myPars[j]); value += temp * myNodes[i].getIntegrationValue(); if(normalize) { if(temp > maximum) maximum = temp; if(temp < minimum) minimum = temp; } } if(normalize) { if(minOrMax == MIN) value /= minimum; else value /= maximum; } } return value; } public float getValue(int side) { float value = 0.0f, temp = 0.0f; if(normalize) { minimum = java.lang.Float.MAX_VALUE; maximum = 0.0f; } if(numSides == 1) { // if the Calculator is not sided but handles sided nodes, we want sums over the whole cell... value = this.getSum(); } else { // ...but if the Calculator is sided, then we want side-specific values; unsided Nodes will return as if Node.getValue(int) were passed 0 if(itsType == NodeTemplate.SUM) { for(int i = 0; i < myNodeCount; i++) { temp = myNodes[i].getValue(side); value += temp; if(normalize) { if(temp > maximum) maximum = temp; if(temp < minimum) minimum = temp; } } if(myPars != null) value *= makeRatio(); if(normalize) { if(minOrMax == MIN) value /= minimum; else value /= maximum; } } else if(itsType == NodeTemplate.RATIO) { value = myNodes[1].getValue(side); value /= myNodes[2].getValue(side); if(myPars != null) value *= makeRatio(); } else if(itsType == NodeTemplate.WEIGHTEDSUM) { int j = myNodeCount; for(int i = 0; i < myNodeCount; i++,j++) { temp = 1.0f; if(i < myPars.length) temp *= Affector.getParamValue(myPars[i]); if(j < myParCount) temp /= Affector.getParamValue(myPars[j]); value += temp * myNodes[i].getValue(side); if(normalize) { if(temp > maximum) maximum = temp; if(temp < minimum) minimum = temp; } } if(normalize) { if(minOrMax == MIN) value /= minimum; else value /= maximum; } } } return value; } public float getSum() { // called (indirectly) by StopConditions float value = 0.0f, temp = 0.0f; if(normalize) { minimum = java.lang.Float.MAX_VALUE; maximum = 0.0f; } if(itsType == NodeTemplate.SUM) { for(int i = 0; i < myNodeCount; i++) { temp = myNodes[i].getSum(); value += temp; if(normalize) { if(temp > maximum) maximum = temp; if(temp < minimum) minimum = temp; } } if(myPars != null) value *= makeRatio(); if(normalize) { if(minOrMax == MIN) value /= minimum; else value /= maximum; } } else if(itsType == NodeTemplate.RATIO) { value = myNodes[1].getSum(); value /= myNodes[2].getSum(); if(myPars != null) value *= makeRatio(); } else if(itsType == NodeTemplate.WEIGHTEDSUM) { int j = myNodeCount; for(int i = 0; i < myNodeCount; i++,j++) { temp = 1.0f; if(i < myPars.length) temp *= Affector.getParamValue(myPars[i]); if(j < myParCount) temp /= Affector.getParamValue(myPars[j]); value += temp * myNodes[i].getSum(); if(normalize) { if(temp > maximum) maximum = temp; if(temp < minimum) minimum = temp; } } if(normalize) { if(minOrMax == MIN) value /= minimum; else value /= maximum; } } return value; } public void setValue(float val) { } // Anything else make sense here? private float makeRatio() { // this is used to make a ratio out of the whole list of parameters, alternating num,denom,num,denom,... through the list int j = 0; float ratio = 1.0f; while(j < (myParCount / 2)) { // if there are two or more, iterate through this... ratio *= Affector.getParamValue(myPars[j++]); ratio /= Affector.getParamValue(myPars[j++]); } ratio *= Affector.getParamValue(myPars[j++]); // ... but do this once if there's a single parameter to be multiplied, or to get the last on an odd-sized list return ratio; } protected boolean loadParameter(String info, BetterTokenizer tokenizer) throws Exception { if(info.equals("Node")) { if(myNodeCount >= MAXNODES) throw new Exception("CalculatorNode can't handle more than ten nodes"); else { GeneralInput.nextToken(tokenizer); myNodeNames[myNodeCount++] = tokenizer.sval; } } else if(info.equals("Param")) { if(myParCount >= MAXPARS) throw new Exception("CalculatorNode can't handle more than twenty parameters"); else { GeneralInput.nextToken(tokenizer); myParNames[myParCount] = tokenizer.sval; if(myPars == null) myPars = new int[MAXPARS]; myPars[myParCount++] = Affector.findOrCreateParam(tokenizer.sval); // set it now so we stake out any unique parameter in the Affector's static parameter lists } } else if(info.equals("Normalize")) { normalize = true; GeneralInput.nextToken(tokenizer); info = tokenizer.sval; if(info.equals("min")) minOrMax = MIN; else minOrMax = MAX; } else return false; return true; } public Node copy() { CalculatorNode c = null; try { c = (CalculatorNode)this.clone(); } catch(CloneNotSupportedException e) {} if(myNodes != null) c.myNodes = new Node[myNodeCount]; for(int i = 0; i < myNodeCount; i++) { c.myNodeNames[i] = new String(myNodeNames[i]); if(myNodes != null) c.myNodes[i] = myNodes[i]; } if(myPars != null) c.myPars = new int[myParCount]; for(int i = 0; i < myParCount; i++) { c.myParNames[i] = new String(myParNames[i]); if(myPars != null) c.myPars[i] = myPars[i]; } c.this_debug_num = debug_num++; return(c); } }