/** * \brief Pulling : The movement of agents by a shrinking biofilm. Move calculated and added to * the agents movement vector. * * <p>The movement of agents by a shrinking biofilm. Move calculated and added to the agents * movement vector. * * @param aNeighbor Reference to the potentially shoving neighbour * @param isMutual Whether movement is shared between two agents or applied only to this one * @param gain Double noting change in position * @return Boolean stating whether pulling is detected (true) or not (false) */ public boolean addSpringMovement(LocatedAgent aNeighbor, boolean isMutual, double gain) { double d, distance, delta; if (aNeighbor == this) return false; // Build the escape vector and find the distance between you and your // neighbourhood d = computeDifferenceVector(_location, aNeighbor._location); _diff.normalizeVector(); distance = getShoveRadius() + aNeighbor.getShoveRadius(); distance += getSpeciesParam().shoveLimit; delta = d - distance; double lMax = _totalRadius; if (delta > 0) gain *= Math.exp(-delta * 5 / (lMax)); if (delta > lMax) gain = 0; /* Apply shoving _________________________________________________ */ if (isMutual) { _diff.times(-0.5 * delta * gain); this._movement.add(_diff); aNeighbor._movement.subtract(_diff); } else { _diff.times(-delta * gain); this._movement.add(_diff); } return (_movement.norm() > _radius * gain); }
/** * \brief Select random coordinates for the new agent within a restricted birth area * * <p>Select random coordinates for the new agent within a restricted birth area. This restricted * area is set within the protocol file, and defined as a ContinuousVector by the method * defineSquareArea * * @param cc ContinuousVector that will hold the coordinates of this agent * @param area Area within which these coordinates should be restricted */ public void shuffleCoordinates(ContinuousVector cc, ContinuousVector[] area) { do { cc.x = ExtraMath.getUniRandDbl(area[0].x, area[1].x); cc.y = ExtraMath.getUniRandDbl(area[0].y, area[1].y); cc.z = ExtraMath.getUniRandDbl(area[0].z, area[1].z); } while (domain.testCrossedBoundary(cc) != null); }
/** * \brief Mutual shoving : The movement by shoving of an agent is calculated based on the cell * overlap and added to the agents movement vector. * * <p>Mutual shoving : The movement by shoving of an agent is calculated based on the cell overlap * and added to the agents movement vector. Both agents are moved of half the overlapping distance * in opposite directions. * * @param aNeighbour Reference to the potentially shoving neighbour * @param isMutual Whether movement is shared between two agents or applied only to this one * @param gain Double noting change in position * @return Boolean stating whether shoving is detected (true) or not (false) */ public boolean addPushMovement(LocatedAgent aNeighbour, boolean isMutual, double gain) { double d, distance; if (aNeighbour == this) return false; // Build the escape vector and find the distance between you and your // neighbourhood d = computeDifferenceVector(_location, aNeighbour._location); _diff.normalizeVector(); // Compute effective cell-cell distance distance = getShoveRadius() + aNeighbour.getShoveRadius(); distance += getSpeciesParam().shoveLimit; distance = d - distance; /* Apply shoving _________________________________________________ */ // Compute shoving distance for the current agent if (distance <= 0) { if (isMutual) { _diff.times(gain * 0.5 * Math.abs(distance)); this._movement.add(_diff); aNeighbour._movement.subtract(_diff); } else { _diff.times(Math.abs(gain * distance)); this._movement.add(_diff); } return true; } else { return false; } }
/** * \brief Set the movement vector that states where to put a newly-created particle * * <p>Set the movement vector that states where to put a newly-created particle * * @param distance Distance between the this agent and the new agent */ public void setDivisionDirection(double distance) { double phi, theta; phi = 2 * Math.PI * ExtraMath.getUniRandDbl(); theta = 2 * Math.PI * ExtraMath.getUniRandDbl(); _divisionDirection.x = distance * Math.sin(phi) * Math.cos(theta); _divisionDirection.y = distance * Math.sin(phi) * Math.sin(theta); _divisionDirection.z = (_agentGrid.is3D ? distance * Math.cos(phi) : 0); }
/** * \brief For self-attachment scenarios, calculates the new position of the agent based on XY and * XZ angles and a distance to move * * <p>For self-attachment scenarios, calculates the new position of the agent based on XY and XZ * angles and a distance to move. No return value as the global swimmingAgentPosition is altered. * The angles XY and XZ are also global parameters as these can also be altered by other methods * in the swimming agent position checks * * @param distanceAgentMoves Distance that the agent is to move */ public void calculateNewAgentPosition(Double distanceAgentMoves) { // Now calculate where this angle would place the agent. swimMovement.set( Math.cos(angleOfMovingAgentXY) * Math.cos(angleOfMovingAgentXZ), Math.sin(angleOfMovingAgentXY), Math.sin(angleOfMovingAgentXZ)); swimMovement.times(distanceAgentMoves); swimmingAgentPosition.add(swimMovement); }
/** * \brief Computes the shortest distance between this agent and another, stored as * ContinuousVectors. This may be around the cyclic boundary * * <p>Computes the distance between this agent and another, stored as ContinuousVectors. This may * be around the cyclic boundary * * @param me ContinuousVector stating first agent location * @param him ContinuousVector stating other agent location * @return the shortest movement vector to go from a to b, take into account the cyclic boundary * @see addOverlapMovement * @see addPullMovement works in 2 and 3D */ public double computeDifferenceVector(ContinuousVector me, ContinuousVector him) { double gridLength; _diff.x = me.x - him.x; // check periodicity in X gridLength = _species.domain.length_X; if (Math.abs(_diff.x) > .5 * gridLength) _diff.x -= Math.signum(_diff.x) * gridLength; _diff.y = me.y - him.y; // check periodicity in Y gridLength = _species.domain.length_Y; if (Math.abs(_diff.y) > .5 * gridLength) _diff.y -= Math.signum(_diff.y) * gridLength; if (_agentGrid.is3D) { _diff.z = me.z - him.z; // check periodicity in Z gridLength = _species.domain.length_Z; if (Math.abs(_diff.z) > .5 * gridLength) _diff.z -= Math.signum(_diff.z) * gridLength; } else { _diff.z = 0; } double d = Math.sqrt(_diff.x * _diff.x + _diff.y * _diff.y + _diff.z * _diff.z); if (d == 0) { d = 1e-2 * _radius; _diff.alea(_agentGrid.is3D); } return d; }
/** * \brief With the agent move calculated, apply this movement, taking care to respect boundary * conditions * * <p>With the agent move calculated, apply this movement, taking care to respect boundary * conditions */ public double move() { if (!_movement.isValid()) { LogFile.writeLog("Incorrect movement coordinates"); _movement.reset(); } if (!_agentGrid.is3D && _movement.z != 0) { _movement.z = 0; _movement.reset(); LogFile.writeLog("Try to move in z direction !"); } // No movement planned, finish here if (_movement.isZero()) return 0; // Test the boundaries checkBoundaries(); // Now apply the movement _location.set(_newLoc); _agentGrid.registerMove(this); double delta = _movement.norm(); _movement.reset(); return delta / _totalRadius; }
/** * examines solute grid to determine where to move based on attraction/repulsion to/from a solute */ private void chemotax() { // 1) get the gradient--this will be the direction the particle // should move in or away from ContinuousVector direction = chemotaxSolute.getGradient2DNoZ(_location); if (repellent) { direction.turnAround(); } // 2) scale the gradient set the scaled gradient as _movement _movement = this.getScaledMove(_location, direction, distEachRun); }
/** * \brief For self-attach scenarios, corrects 2D coordinates that have crossed the left of right * boundary (x0z and xNz) * * <p>For self-attach scenarios, corrects 2D coordinates that have crossed the left of right * boundary (x0z and xNz). The coordinate is either placed on the opposite side for cyclic * boundaries, or bounces off the boundary at the required angle * * @param boundaryCrossed The boundary that has been detected to have been crossed * @param distanceMoving The distance that the cell is moving in this step of its repositioning */ public void correctCrossedLeftRightBoundaries(AllBC boundaryCrossed) { // Cell has passed through the boundary on the left or right hand side // If is cyclic, the point is deemed to reappear at the opposite side if (boundaryCrossed instanceof BoundaryCyclic) { // This will be inside the grid at the amount that the move would // have taken the point outside of the grid. swimmingAgentPosition.y = swimmingAgentPosition.y % domain.length_Y; } else { // Can simply take the correct value of the new Y coordinate and // reflect it the correct side of the boundary. swimmingAgentPosition.y = -swimmingAgentPosition.y % domain.length_Y; // Now we need to change the angle to note the change of direction. angleOfMovingAgentXY = 2 * Math.PI - angleOfMovingAgentXY; } }
/** * \brief With it determined that cell division will occur, create a new agent from the existing * one * * <p>With it determined that cell division will occur, create a new agent from the existing one * * @throws CloneNotSupportedException Thrown if the agent cannot be cloned */ public void makeKid() throws CloneNotSupportedException { // Create the new instance LocatedAgent baby = (LocatedAgent) sendNewAgent(); // Note that mutateAgent() does nothing yet baby.mutateAgent(); this._myDivRadius = getDivRadius(); baby._myDivRadius = getDivRadius(); baby._myDeathRadius = getDeathRadius(); // Update the lineage recordGenealogy(baby); // Share mass of all compounds between two daughter cells and compute // new size divideCompounds(baby, getBabyMassFrac()); // sonia:chemostat if (Simulator.isChemostat) { // upon division the daughter cells remain with the coordinates of their progenitor } else { // Compute movement to apply to both cells setDivisionDirection(getInteractDistance(baby) / 2); // move both daughter cells baby._movement.subtract(_divisionDirection); _movement.add(_divisionDirection); } // Now register the agent inside the guilds and the agent grid baby.registerBirth(); baby._netVolumeRate = 0; }
/** * \brief Create a new agent in a specified position * * <p>Create a new agent in a specified position * * @param position Vector stating where this agent should be located */ public void createNewAgent(ContinuousVector position) { try { // Get a clone of the progenitor LocatedAgent baby = (LocatedAgent) sendNewAgent(); baby.giveName(); // randomize its mass baby.mutatePop(); baby.updateSize(); this._myDivRadius = getDivRadius(); baby._myDivRadius = getDivRadius(); baby._myDeathRadius = getDeathRadius(); // Just to avoid to be in the carrier position.x += this._totalRadius; baby.setLocation(position); baby.registerBirth(); } catch (CloneNotSupportedException e) { utils.LogFile.writeLog("Error met in LocAgent:createNewAgent()"); } }
/** * \brief Used by the move method to determine if an agents move crosses any of the domain's * boundaries * * <p>Used by the move method to determine if an agents move crosses any of the domain's * boundaries */ public void checkBoundaries() { // Search a boundary which will be crossed _newLoc.set(_location); _newLoc.add(_movement); AllBC aBoundary = getDomain().testCrossedBoundary(_newLoc); int nDim = (_agentGrid.is3D ? 3 : 2); boolean test = (aBoundary != null); int counter = 0; // Test all boundaries and apply corrections according to crossed // boundaries while (test) { counter++; aBoundary.applyBoundary(this, _newLoc); aBoundary = getDomain().testCrossedBoundary(_newLoc); test = (aBoundary != null) | (counter > nDim); if (counter > nDim) System.out.println("LocatedAgent.move() : problem!"); } }
/** * \brief Create an agent using information in a previous state or initialisation file * * <p>Create an agent using information in a previous state or initialisation file * * @param aSim The simulation object used to simulate the conditions specified in the protocol * file * @param singleAgentData Data from the result or initialisation file that is used to recreate * this agent */ public void initFromResultFile(Simulator aSim, String[] singleAgentData) { // this routine will read data from the end of the singleAgentData array // and then pass the remaining values onto the super class // Chemostat "if" added by Sonia 27.10.09 // Rearranged by Rob 10.01.11 // find the position to start at by using length and number of values read int nValsRead = 5; int iDataStart = singleAgentData.length - nValsRead; if (Simulator.isChemostat) { // Rob: this is necessary for the case when biofilm agents in one simulation // are transferred into a chemostat for the next. _location.set(0, 0, 0); } else { double newAgentX, newAgentY, newAgentZ; newAgentX = Double.parseDouble(singleAgentData[iDataStart]); newAgentY = Double.parseDouble(singleAgentData[iDataStart + 1]); newAgentZ = Double.parseDouble(singleAgentData[iDataStart + 2]); _location.set(newAgentX, newAgentY, newAgentZ); } // agent size _radius = Double.parseDouble(singleAgentData[iDataStart + 3]); _totalRadius = Double.parseDouble(singleAgentData[iDataStart + 4]); _myDivRadius = getDivRadius(); _myDeathRadius = getDeathRadius(); // now go up the hierarchy with the rest of the data String[] remainingSingleAgentData = new String[iDataStart]; for (int i = 0; i < iDataStart; i++) remainingSingleAgentData[i] = singleAgentData[i]; super.initFromResultFile(aSim, remainingSingleAgentData); }
/** * \brief Determine if a swimming agent has left the boundary layer and returned to the bulk * * <p>For self attachment, an agent starts at the top of the boundary layer and swims in a random * direction until it attaches somewhere. If however it returns to the bulk, we assume this cell * does not return. This method checks the move to determine if the cell is moving in a trajectory * that has returned it to the bulk * * @return Boolean stating whether the cell has returned to the bulk */ public boolean isNewCoordAboveBoundaryLayer() { // Get the top of the bulk - note we may need to correct here. The // method that calls this calculates new positions. However this is run // before it is determined whether any boundaries have been crossed (as // it would be silly to check all boundaries when there is a high // chance, especially at the start, that the cell may return into the // bulk. Thus, a high Y or Z value may be sent in here, which when // referenced to the boundary layer array, will cause an error. So if // the Y or Z values are higher than the size of the grid, we will use // the size of the grid as the array reference if (swimmingAgentPosition.y > domain.length_Y) swimmingAgentPosition.y = domain.length_Y - 1; if (swimmingAgentPosition.z > domain.length_Z) swimmingAgentPosition.z = domain.length_Z - 1; // System.out.println("Checking if "+swimmingAgentPosition.toString()+" above boundary layer"); // Now calculate the top of the boundary layer at the y, z coordinate. int j = (int) (swimmingAgentPosition.y / domain._resolution); int k = (int) (swimmingAgentPosition.z / domain._resolution); double boundaryAtThisPoint = domain._topOfBoundaryLayer[j + 1][k + 1]; boundaryAtThisPoint *= domain._resolution; // System.out.println("..."+(swimmingAgentPosition.x > boundaryAtThisPoint)); // Check if we are above it return (swimmingAgentPosition.x > boundaryAtThisPoint); }
/** * \brief Set the location of this agent to the supplied continuous vector * * <p>Set the location of this agent to the supplied continuous vector * * @param cc Location which this agent should be assigned to */ public void setLocation(ContinuousVector cc) { // sonia:chemostat // set the location of the newborns to zero if (Simulator.isChemostat) { cc.set(0, 0, 0); _location.x = cc.x; _location.y = cc.y; _location.z = cc.z; } else { _location.x = cc.x; _location.y = cc.y; _location.z = cc.z; } }
/** * \brief For self-attachment scenarios, initialises agents on the boundary layer rather than * substrarum, and models their swim to the surface or biofilm * * <p>For self-attachment scenarios, the agents are initialised at the top of the boundary layer * rather than on the substratum. These agents then perform a 'run and tumble' motion until they * either attach to the substratum or forming biofilm. This method captures this behaviour for * cells that are created for a time step. Once this swimming action has been performed, the agent * is created at its final position. Note that input of agents onto the boundary layer is decided * by a parameter set in the protocol file, cellAttachmentFrequency, measured in hours. The number * of cells is adjusted to suit the global time step that is being used. Also note that this * injection of cells can be for a set period (specified in the protocol file as parameter * cellInjectionPeriod), or can be stopped and started (modelling a 'settling' period) using * parameters cellInjectionOffPeriod and cellInjectionStopHour. This is explained in detail in the * tutorial for version 1.2 of iDynoMiCS. * * @param spRoot The Species markup from the protocol file for one particular species being * initialised * @param numberAttachedInjectedAgents The number of agents of this type that need to be created * in this global timestep */ public void createBoundaryLayerPop(XMLParser spRoot, int numberAttachedInjectedAgents) { LogFile.writeLog( "\t\tAttempting to create " + numberAttachedInjectedAgents + " agents of " + speciesName + " in the boundary layer"); // Create all the required agents // Note that this continues UNTIL THE DESIRED NUMBER OF CELLS HAVE ATTACHED SOMEWHERE // Just out of interest, I've decided to keep a count of how many cells are required for this to // happen int totalNumberOfInjectedAgents = 0; int agentsReturnedToBulk = 0; int requiredNumAttachedAgents = numberAttachedInjectedAgents; // Temporary DiscreteVector to make finding the boundary layer tidier. DiscreteVector dV = new DiscreteVector(); while (numberAttachedInjectedAgents > 0) { totalNumberOfInjectedAgents++; if (_progenitor instanceof LocatedAgent) { swimmingAgentPosition.reset(); // Now to choose coordinates for this particular agent while (true) { // This cell needs to take a random location in the Z and Y // directions. The X will come from the position of the // boundary layer on those axes. Generate these randomly. swimmingAgentPosition.y = ExtraMath.getUniRandDbl() * domain.length_Y; if (domain.is3D) { swimmingAgentPosition.z = ExtraMath.getUniRandDbl() * domain.length_Z; } // Now to work out the X coordinate. This is based on where // the top of the boundary layer is when this agent is // created. The top of the boundary layer is calculated in // Domain at each step. Now the resolution differs (this is // in nI x nJ x nK rather than microns - so this will need // to be converted accordingly. Method to calculate this: // - get the value from the top of the boundary layer // - reduce by 1 (such that the micron value will be the // top of the layer) // - multiply by resolution of this domain. dV.set(swimmingAgentPosition, domain._resolution); if (!domain.is3D) dV.k = 1; swimmingAgentPosition.x = domain._topOfBoundaryLayer[dV.j][dV.k] - 1.0; swimmingAgentPosition.x *= domain._resolution; // Check this is ok. // System.out.println("Trying starting position "+dV.toString()+" => // "+this.swimmingAgentPosition.toString()); if (domain.testCrossedBoundary(swimmingAgentPosition) == null) break; } // Now we can do the run and tumble motion of these cells int cellRunResult = performRunAndTumble(spRoot); // If increment the relevant counters, as these may be useful switch (cellRunResult) { case 1: // Successfully Attached numberAttachedInjectedAgents--; // Create the agent at these coordinates ((LocatedAgent) _progenitor).createNewAgent(this.swimmingAgentPosition); // System.out.println("Cell "+swimmingAgentPosition.toString()+" attached"); break; case 2: // System.out.println("Cell at "+swimmingAgentPosition.toString()+" returned to bulk"); agentsReturnedToBulk++; break; } } else { // If this isn't a located species, just create a new agent. _progenitor.createNewAgent(); } } // Write the stats to the log file incase of interest LogFile.writeLog( requiredNumAttachedAgents + " agents of species " + speciesName + " for self-attachment successfully created"); LogFile.writeLog( totalNumberOfInjectedAgents + " agents of species " + speciesName + " attempted to attach"); LogFile.writeLog( agentsReturnedToBulk + " agents of species " + speciesName + " returned to the bulk"); }
/** * \brief For self-attachment scenarios, determines whether a swimming agents move has taken it * across a boundary, correcting the coordinate accordingly * * <p>For self-attachment scenarios, the agents are swimming through the domain, and we need to * ensure that they perform the correct behaviour when the boundary is met. This method checks * whether an agents move has taken it over the boundary and applies the relevant correction * (either a bounce off the boundary or a reappearance on the other side). If the cell has hit the * surface, the cell is deemed to have adhered to that surface and a relevant x coordinate * generated to note that this is the case. The top of the domain is dealt with differently, as * this is checked by the call to isNewCoordAboveBoundaryLayer, which determines if the agent has * moved out of the boundary layer. If this is the case we assume this cell to have returned to * the bulk and do no further action with that cell. An integer is returned noting the fate of * this move - a 0 if the move is ok (after adjustment if required), a 1 if the agent has met the * substratum and attached, and a 2 if the cell has returned to the bulk * * @param distanceMoving Distance the agent is moving (in microns) in this move * @return Integer noting the fate of this move (0 move ok (after adjustment if required), 1 if * attached to surface, 2 if returned to bulk */ public int agentMoveBorderCheck() { // Simplest test to do first is to check if any boundaries have been crossed AllBC boundaryCrossed = domain.testCrossedBoundary(swimmingAgentPosition); // First is a simple test - has the cell returned to the bulk // If this is the case, we can just forget this and have another go // The cell will only have the capability to return to the bulk if the angle has it moving // upwards or directly across // (i.e 0-90 and 270-360 degrees) if (isNewCoordAboveBoundaryLayer()) { // The cell has returned into the bulk, and thus this try is over. // Return 2 so the process starts with a new cell. return 2; } else { // Now to see if the move takes the point outside any of the boundaries if (boundaryCrossed == null) { // No borders crossed, not back in the bulk. return 0; } else { String boundaryCrossedName = boundaryCrossed.getSideName(); if (boundaryCrossedName.equals("y0z")) { // Detected that the move has crossed the substratum, thus // the cell has hit the biofilm. A return of 1 indicates // that this is the case. // Hit the biofilm, so set the species coordinates as required. // We may have hit the biofilm but the Y and Z coordinates // in this generated move may still be negative (as they // may have gone over another boundary). So before we set // the final x, we should check Y and Z. // So firstly, set the X position onto the surface swimmingAgentPosition.x = ExtraMath.getUniRandDbl(); // Now set the final X position. // Do a new check on the boundary crossed. boundaryCrossed = domain.testCrossedBoundarySelfAttach(swimmingAgentPosition); if (boundaryCrossed != null) { boundaryCrossedName = boundaryCrossed.getSideName(); if (boundaryCrossedName.equals("xNz") || boundaryCrossedName.equals("x0z")) correctCrossedLeftRightBoundaries(boundaryCrossed); else correctCrossedFrontBackBoundaries(boundaryCrossed); } return 1; } else if (boundaryCrossedName.equals("xNz") || boundaryCrossedName.equals("x0z")) { correctCrossedLeftRightBoundaries(boundaryCrossed); return 0; } // Deal with 3D boundary too else if (boundaryCrossedName.equals("x0y") || boundaryCrossedName.equals("xNy")) { correctCrossedFrontBackBoundaries(boundaryCrossed); return 0; } else { // This needs to be here so the function returns something. // However this deals with the top boundary (yNz) and this // has already been dealt with by the crossed bulk method. // So it is highly doubtful we will ever end up here. return 0; } } } }
/** * \brief Determine if an agent has a move to perform * * <p>Determine if an agent has a move to perform * * @return Boolean noting whether the agent has a move to perform */ public boolean isMoving() { return (_movement.norm() > _totalRadius / 10); }