package integrators; import main.*; /** Base class for objects that implement numerical integration routines. Does nothing but provide generic methods for all such classes. Subclass and override at least the methods Integrator.init() and Integrator.IntegrateOneStep(), and probably Integrator.reset() as well. */ public class Integrator extends Object { final String type = ""; Model model = null; int num_nodes = 0; int num_cells = 0; float timestep = 0.01f; float defaultStepsize = 0.01f; // this exists because many integrators change the timestep from one step to the next... use this to reset after a whole integration float errorTolerance = 0.0001f; float eEst; // placeholder for estimate of per-step error public Integrator() {} /** Any object planning to use an Integrator of any kind MUST call its init(Model) method, which (among other things) is generally used to allocate storage arrays for the intermediates in an integration method. This method does NOT do any numerics so does not depend on the state of the model, but it DOES need to be called after the model has been properly populated with Cells and Nodes, and needs to be called again if these change. */ public void init(Model model) { this.model = model; num_cells = model.numCells; num_nodes = model.cells[0].nodes.length; // Derived classes do stuff like this in contructors to make arrays of NodeValues for // storing values and derivatives of each node. /*nodeValues = new NodeValues[num_cells * num_nodes]; for(cell = 0; cell < num_cells; cell++) { for(node = 0; node < num_nodes; node++) { nodeValues[cell * num_nodes + node] = new NodeValues(); nodeValues[cell * num_nodes + node].numSides = model.cells[0].nodes[node].numSides; } }*/ } /** Should be called after the Integrator has crossed an entire interval. The base class does nothing; typical subclasses will override Integrator.reset() to reset the time step size to the default value. This method is NOT used to reallocate storage; that requires a call to init(). */ public void reset() { } /** Integrates across a single time step. Return value is the size of the timestep, which may differ from that specified by the calling method if the Integrator subclass adapts the step size to match error requirements. */ public float IntegrateOneStep() { return timestep; // fill in the blank! Return value should be timestep } protected void getCurrentValues(NodeValues [] node_values) { for(int i = 0; i < num_cells; i++) { for(int j = 0; j < num_nodes; j++) { model.cells[i].nodes[j].getValue(node_values[(i*num_nodes) + j].values); } } } protected void getDerivatives(NodeValues [] deriv_values) { for(int i = 0; i < num_cells; i++) { for(int j = 0; j < num_nodes; j++) { model.cells[i].nodes[j].getAffectorsValue(deriv_values[(i*num_nodes) + j].values); } } } protected void setIntegrationValues(NodeValues [] node_values) { for(int i = 0; i < num_cells; i++) { for(int j = 0; j < num_nodes; j++) { model.cells[i].nodes[j].setIntegrationValues(node_values[(i*num_nodes) + j].values); } } } protected void setFinalValues(NodeValues [] node_values) { for(int i = 0; i < num_cells; i++) { for(int j = 0; j < num_nodes; j++) { model.cells[i].nodes[j].setFinalValues(node_values[(i*num_nodes) + j].values); } } } /** Set the size of the timestep used by the integrator for the next (and subsequent) calls to IntegrateOneStep. Keep in mind that the stepsize means different things to different methods, and also that this value may change as a consequence of attempts by the Integrator to make a step sufficiently accurate. Thus, IntegrateOneStep() returns the timestep actually taken. */ public void setStepSize(float ts) { timestep = ts; } /** Set the default step size; this is usually only used by Integrator.reset() to return the Integrator to it's defualt state after an entire integration.*/ public void setDefaultStepSize(float ts) { defaultStepsize = ts; } /** Set the per-step error tolerance; different methods make different use of this value, so see documentation of each Integrator subclass to determine how best to set this, or whether the default value is appropriate. */ public void setErrorTolerance(float err_tol) { errorTolerance = err_tol; } /** Returns an estimate of the error introduced per timestep. Although this is often a conservative estimate, it is the duty of the derived Integrator to determine the error according to whatever algorithm it implements; see the documentation for each Integrator to see how the error is estimated. */ public float getErrorEstimate() { return eEst; } public boolean isType(String t) { if(t.equals(type)) return true; else return false; } public void copyNVarray(NodeValues [] from, NodeValues [] to) { for(int i = 0; i < Math.min(from.length, to.length); i++) { from[i].copyInto(to[i]); } } /** This utility class may be more convenient for some purposes than an array of NodeValues objects would be. */ public class NVvector extends Object { float [] values; int [] numSides; int num_nodes; int num_cells; int dim; /** Constructor needs a Model as an argument to get information about the number of cells, nodes, and sides per node. */ NVvector(Model model) { this.num_nodes = model.cells[0].nodes.length; this.num_cells = model.numCells; this.numSides = new int[this.num_nodes * this.num_cells]; this.dim = this.num_nodes * this.num_cells * Globals.cellNumSides; this.values = new float[this.dim]; for(int i = 0; i < this.num_cells; i++) { for(int j = 0; j < this.num_nodes; j++) { this.numSides[(i * this.num_nodes) + j] = model.cells[0].nodes[j].numSides; } } } /** collapse() returns an array of floats without the unused spaces associated with unsided Nodes; the purpose of this is to facilitate the use of canned matrix operation routines (should we need to), which won't know anything about sided-ness and so on. */ public float [] collapse() { int i,j,k,l,n,nv = 0; for(i = 0; i < this.num_nodes; i++) nv += this.numSides[i]; nv *= this.num_cells; float [] v = new float[nv]; n = 0; for(i = 0; i < num_cells; i++) { for(j = 0; j < num_nodes; j++) { k = (i*num_cells)+j; for(l = 0; l < this.numSides[k]; l++) v[n++] = this.values[(k*Globals.cellNumSides)+l]; } } return v; } /** Use this to copy an array of NodeValues objects into a pre-made NVvector. */ public void convertToNVvector(NodeValues [] nv) throws Exception { if(nv.length != this.num_cells * this.num_nodes) throw new Exception("NVvector.convertToNVvector(NodeValues[]): size mismatch!"); else { int i,j,k,l; for(i = 0; i < num_cells; i++) { for(j = 0; j < num_nodes; j++) { k = (i*num_cells)+j; for(l = 0; l < this.numSides[k]; l++) this.values[(k*Globals.cellNumSides)+l] = nv[k].values[l]; } } } } /** Use this if you want a copy of this vector in the form of an array of NodeValues objects. */ public NodeValues [] convertToNodeValues(NVvector vec) { int i,j,k,l; NodeValues [] nvarray = new NodeValues[num_nodes * num_cells]; for(i = 0; i < num_cells; i++) { for(j = 0; j < num_nodes; j++) { k = (i*num_cells)+j; nvarray[k] = new NodeValues(); nvarray[k].numSides = this.numSides[k]; for(l = 0; l < this.numSides[k]; l++) nvarray[k].values[l] = this.values[(k*Globals.cellNumSides)+l]; } } return nvarray; } /** Use this if you want a freshly-made copy of the values vector in the form of an array of floats */ public float [] getCopyOfVector() { float [] cp = new float[this.dim]; for(int i = 0; i < this.dim; i++) cp[i] = this.values[i]; return cp; } /** Use this one if you want the values vector copied into a pre-allocated array of floats */ public void getCopyOfVector(float [] cp) throws Exception { if(cp.length < this.values.length) throw new Exception("NVvector.getCopyOfVector(float []): array argument too short!"); else for(int i = 0; i < this.dim; i++) cp[i] = this.values[i]; } /** Use this one if you just want a handle to the values vector */ public float [] getValueVector() { return this.values; } } /** This class was intended for storing a Jacobian matrix, or something like that. */ protected class NVmatrix extends Object { float [][] values; int [] numSides; int num_nodes; int num_cells; int dim; /** Constructor needs a Model as an argument to get information about the number of cells, nodes, and sides per node. */ NVmatrix(Model model) { this.num_nodes = model.cells[0].nodes.length; this.num_cells = model.numCells; this.numSides = new int[this.num_nodes * this.num_cells]; this.dim = this.num_nodes * this.num_cells * Globals.cellNumSides; this.values = new float[this.dim][this.dim]; for(int i = 0; i < this.num_cells; i++) { for(int j = 0; j < this.num_nodes; j++) { this.numSides[(i * this.num_nodes) + j] = model.cells[0].nodes[j].numSides; } } } /** I haven't yet had the heart to test this messy thing... I haven't written any code that needs it yet. What is is supposed to do is return a 2D array of floats that leave out the empty spaces where there are zeros for unsided Nodes, analogous to what NVvector.collapse() does. This will come in handy if one needs to do canned matrix operations on these arrays. Of course, sooner or later we'll need code to go the other direction as well. */ public float [][] collapse() { int i,j,k,l,m,n,o,p,q,r,nv = 0; for(i = 0; i < this.num_nodes; i++) nv += this.numSides[i]; nv *= this.num_cells; float [][] v = new float[nv][nv]; q = r = 0; for(i = 0; i < num_cells; i++) { for(j = 0; j < num_nodes; j++) { k = (i*num_cells)+j; for(l = 0; l < this.numSides[k]; l++) { for(m = 0; m < num_cells; m++) { for(n = 0 ; n < num_nodes; n++) { o = (m*num_cells)+n; for(p = 0; p < this.numSides[o]; o++) v[q][r++] = this.values[(k*Globals.cellNumSides)+l][(o*Globals.cellNumSides)+p]; } } q++; } } } return v; } /** Use this if you want a freshly-made copy of the values matrix in the form of a 2D array of floats */ public float [][] getCopyOfMatrix() { float [][] cp = new float[this.dim][this.dim]; for(int i = 0; i < this.dim; i++) { for(int j = 0; j < this.dim; j++) { cp[i][j] = this.values[i][j]; } } return cp; } /** Use this one if you want the values matrix copied into a pre-allocated 2D array of floats */ public void getCopyOfMatrix(float [][] cp) throws Exception { if(cp.length < dim || cp[0].length < dim) throw new Exception("NVmatrix.getCopyOfMatrix(float []): matrix argument too small!"); else { for(int i = 0; i < this.dim; i++) { for(int j = 0; j < this.dim; j++) { cp[i][j] = this.values[i][j]; } } } } /** Use this one if you just want a handle to the values vector */ public float [][] getValueMatrix() { return this.values; } } }