Courses/531-05F/Src/Assignment-5*/Readme.txt ---------------------------------------- In /users/rlr/Courses/531/assignment-5.txt I outlined a possible sequence of steps toward the final (big) task: create a "model" in which the Ant evolve to have a larger vision length. In particular i suggested: - AntEaters see ants (one of the assignments above) - AntEater selects one to eat - AntEater actually "eats" one Hint-> what needs to be done for an Ant to "die" ie be removed from the model? - Ant have to be created with variable visionLengths - You need to calculate and average the visionLength each step - Ants need to "see" all the anteaters in their vision range. - Ants have to decide which way to move - Ants have to be born when they die I will now try to follow my own advice and see how it all comes out. In this file i will try to record all the steps i take, how i tested things, etc. =================================================================== - AntEaters see ants Animals have a (double) visionLength. Animal default is 4.0, and AntEaters use that. So i am going to write a method in AntEater ArrayList getListOfAntsSeen( ) which will return an ArrayList with pointers to whatever Ants the AntEater can "see" given its visionLength. Now, to write that method, we have to decide how to operationalize visionLength. The world is a 2D discrete (cellular) grid. So we could decide that AntEaters can see things within visionLength cells of their current location, or we could decide that they can see things in cells that have the cell center within visionLength of the center of the AntEater's cell, calculated using euclidean geometry. For simplicity, i'm going to just use the cell count as distance. Note we can re-visit this later, after the rest of the program is done, and just change how this one method works to change what they see, and not have to change anything else. I'm going to use the Repast Object2DGrid method to implement the search in the neighborhood of the AntEater. Note it returns a Vector. So i guess for convenience i'll change my method: Vector getAntsSeen( ) How to test this? I'll have the AntEater call this method each step, and then have it print out the list of Ants it sees, and their locations. Notes: - i added some getters to Model.java so the AntEaters, etc. could get access to the world, antList, etc. - I had to import the repast sim.space . Time: about 20min =================================================================== - AntEater selects one to eat For this, we could have them select the closest one see, with ties broken at random. Again the issue of defining "closest" comes up--cell wise, euclidean wise, or what? Since i chose above to go cell-step-wise for vision, i guess i'll stay consistent here. Ant pickClosestAnt( Collection c ) This will just look at them all. distance = sum of x,y differences. keep list of all with minimal distance seen. at the end, pick one. Note i use the generic superclass Collection, which could be an ArrayList, a Vector, etc. Then i need to use an "interator" to loop over the elements. Testing: Pick and print the one chosen, and its distance. Save it here: export JARDATE=`date +%y%m%d`; export JARTIME=`date +%H%M` jar -cvf ants2-${JARDATE}-${JARTIME}.jar ./*.java ./*.txt --> 8422 Dec 1 14:58 ants2-051201-1458.jar Time: 25min (with interruptions...) =================================================================== - AntEater actually "eats" one Hint-> what needs to be done for an Ant to "die" ie be removed from the model? For an Ant to die, we have to remove it from all the lists it is one (antList, allList) and from the world. What class should be responsible for all this? To be most correct, perhaps we - send a message to the Ant tell it it die just in case it has any cleaning up to do (eg in some models it might have relations with particular other objects). - it then sends a msg telling the world it is dying So i'll write these methods: Ant-eaten() Model-removeDeadAnt( Ant a ) I guess we'll allow the AntEater to acquire the weight of the eaten ant, too. Testing: We'll have the AntEater eat the closest ant it finds. But something strange happens: Recall we create 8 ants, then 2 anteaters, and in this version they are active in that order. So here is a fragment of the output when the first anteater is active: - Ant 7 (5,3) step: - try to move dx,dy = 1,1 -> AntEater 8 (8,3) step: Sees Ant at 6,0. Sees Ant at 4,1. Sees Ant at 6,4. Sees Ant at 6,7. - pickClosestAnt found 1 closest... - picked ant at 6,4. - Ant 7 (6,4) has been eaten! - antList now: ...prints 7 ants... ...prints the locations of objects in the world... Note it eats the closest ant, just fine. And from the output i see <- 9 objects in the world. But the second anteater didn't get to take its step and eat!! Why?? Note that in model step we have this loop for activity: for ( int i = 0; i < allList.size(); ++i ) { Object obj = allList.get(i); if ( obj.getClass() == Ant.class ) ((Ant) obj).step(); else ((AntEater) obj).step(); } So when the first anteater its, i=8. it eats an ant, removes it from the list, so allList.size() is 9. Then we increment i to 9! So we don't give that last anteater a chance to act!! So...the basic problem is that we removed an object from a list (allList) in the middle of working our way down that list processing items on it, but the for-loop above doesn't know what we did. There are different ways people deal with this kind of problem. For us, we know the problem is just with the allList, because we are using that to control the selection of what agents get activated each step. So here is one kludgy solution: - Add an instance variable to Animals that is Boolean dead, default false. - If an animal (eg ant) gets killed (eg eaten), it marks its dead = true; - we then change the Model-step() to be sure it doesn't activate dead animals. - Then after going thru the allList to activate all animals, we go over it again with a "grim reaper" to remove any marked dead == true. Note will use an Iterator here, since its remove() method keeps the list "intact". NOTE: this is not totally satisfactory...it means that we have to be sure that the only refs to dead ants are through the allList, and then be sure that the one place that list is used when it might have dead ants on it is in the model-step() method, the place that checks for them being dead. save as: ants2-051201-1555.jar Time: 30 minutes =================================================================== - Ant have to be created with variable visionLengths - You need to calculate and average the visionLength each step In Model.java, when we create Ants, assign visionLengths from a draw from a Normal variate, as determeind by the new Model instance variable antVLMean (=3 for now). Now lets calc some simple statistics each step: avgAntVisionLength. And print it at the end of each step. Just to show how, lets add a new 3rd party library and use it. We'll use this one -- Commons Math library: http://jakarta.apache.org/commons/math/ http://jakarta.apache.org/commons/math/apidocs/index.html -- The API The jar file is located here on the CSCS system: /appl/java/commons-math-1.0/commons-math-1.0.jar For the commons math lib, the top level interface is org.apache.commons.math.stat.* where stat can be various more specific classes and packages, eg univariate.UnivariateStatistics So in Model.java we will import this: import org.apache.commons.math.stat.descriptive.DescriptiveStatistics; We then need to create an IV for the object: private DescriptiveStatistics avgAVLStats; // stats on ant's visionLength Create the stats object: avgAVLStats = DescriptiveStatistics.newInstance(); The we can have it calc things for us with avgAVLStats.clear(); for ( Ant ant : antList ) avgAVLStats.addValue( ant.getVisionLength() ); The print these: avgAVLStats.getMin(); avgAVLStats.getMean(); avgAVLStats.getMax(); avgAVLStats.getStandardDeviation(); Actually, we store these in some instance variables using a new method calcStats(), then print from those. There are lots of other things that class can do...see the API. Lets make the world a bit bigger (12x12) and with 16 ants, just to give us some more to test. Change the Ant display to display its visionLength. Test: run it, look to see the printed ant visionLengths. Discovered a bug in Animal.java, setVisionLength(v) -- it was setting hungerLevel !! (a typical copy/paste error!!) OK, after fixing that, we see the min/mean/max ant visionLength changes as the ants get eaten. (of course now its just random which ants get eaten, since they can't see yet!) Time: 40 min (had to look up the use of the common library) Save: ants2-051202-0847.jar =================================================================== - Ants need to "see" all the anteaters in their vision range. So lets add a method for Ants that is similar to what we wrote for the AntEaters. Actually, lets see if we can write a general method in Animal: public Vector getObjectSeenOfClass ( Class objectClass ) Had to add import uchicago.src.sim.space.*; to Animal.java . Then test it by using it in AntEater, first: Ok, that works. Now lets take the specialized method getAntsSeen() out of AntEater, since we don't need it. And now lets add to the Ant-step() method so that the ants get and report on the AntEaters that they see. Lets generalize this: public Ant pickClosestAnt( Collection c ) to public Object pickClosestObjectOfClass ( class objClass, Collection c ) OK, that works, so lets use it in AntEater, too. Change Animal printSelf to print x,y location. Test: run and view printf's of what the ant's see, and what the anteaters see and eat (since we changed that to using the generic method above). Time: 30 min (lots of generalization done). ===================================================================== - Ants have to be born when they die This could be done in a lot of ways. I think what i'll do is: define a Model method: Ant createAnt() which creates a new ant, and addAntToModel( Ant a ) which adds it to the various lists/ Then we can use that in both the initialization, and in adding new ants during a run. That i will do at the end of the Model-step() method, after the grim reaper had removed dead ants, adding enough ants to fill the list to its original size again. Time: 10 min ===================================================================== - Ants have to decide which way to move Now we want the ants to move away from the closest AntEater it sees. Lets just do something simple: move away 1 step in x,y direction. (check for walls, tho.) Test: it does seem to work, in that there seems to generally be a empty bubble around the antEaters, and the ants mean visionLength does go up, cycling between 3.5 and 4.0 mostly. Of course Ants are being born with visionLength drawn from the original distribution, so we keep getting guys with low vision. But one thing is a little puzzling: AntEaters have a VL of 4. Once an Ant gets a vL > 5, it should never get into the vision/range of an AntEater, yes? But i see that the max vL is 5.95. so maybe they just don't get a chance to get that high. Note also that are now being added/activated after antEaters. So if an Ant is 5 away, it might move randomly, right into range of an antEater! It would be nice to have an age variable in these guys, and track average age, max age, etc. Time: 10min. Save: 13641 Dec 2 13:04 ants2-051202-1304.jar ===================================================================== - Added step: - Add age, increment each step. Print stats for it for Ants at the end of each step. Note here we just copy/paste/change the AVL stats stuff. - Add inheritance! So we want the newly born to be offspring of some existing ants. How to select the parents? How about selecting by age! Pick any one with age > average. Before we had: // now lets allow Ants to be born to fill list. for ( int i = antList.size(); i < numberOfAnts; ++i ) { Ant anAnt = createAnt(); addAntToModel( anAnt ); System.out.printf( " - New Ant born ID=%d at %d,%d.\n", anAnt.getID(), anAnt.getX(), anAnt.getY() ); } Lets change it to this use a new method to create an ant: Ant anAnt = getOffspringAnt(); and have that call some other new methods (eg selectParent()). Lets clean up some names on the stats variables, i.e., no need to call it avgAge* and avgAVL*. Testing: It works! now avgVL goes up pretty monotonically, and even after 56 steps is about 4.70. Ah, but here is a bug i just introduced. Note the getOffspringAnt() just *copies* visionLength. So eventually it converges to all one VL, and then the getParent() fails. I need to add mutation back in. All right, they are definitely doing well now. After 100 steps, avgVL=5.64 and climbing! But climbing slower, as its no great advantage to be a lot higher. 200 steps: avgVl = 6.06 300 steps: = 6.06 might be stuck now, because there is no death by old age. the occasional death is now from births in the vision range of the AntEaters, i guess. But since there are no deaths, no births, so its all over!! Note the disadvantage of no death by old age---evolution has stopped, Time: stats added -- 10 min. fix names: 10min add selectin parent: 30min Save: 15007 Dec 2 15:35 ants2-051202-1535.jar ===> Total a little less than 4 hours. (including typing all this!) =====================================================================