/******************************************************************************* * Copyright (c) 2016 Alex Shapiro - github.com/shpralex * This program and the accompanying materials * are made available under the terms of the The MIT License (MIT) * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. *******************************************************************************/ package com.sproutlife.model; import java.awt.Point; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import com.sproutlife.model.echosystem.Echosystem; import com.sproutlife.model.echosystem.Mutation; import com.sproutlife.model.echosystem.Organism; public class Stats { GameModel gameModel; public double smoothedPopulation=0; public double smoothedPopDensity=0; public int smoothedResetTimer = 0; public double avgAge; public double avgLifespan; public int avgMaxLifespan; public int gameTime = 0; public double boardSizeDivPopulation; double lifespanHistogram[] = new double[100]; double avgSize = 0; double avgMaxSize = 0; double avgTerritory = 0; double avgMaxTerritory = 0; int[] maxTerriroty = new int[100]; double avgChildNumber = 0; double[] avgChildAtAge = new double[10]; double[] childNumberPercent = new double[10]; double childlessPercent = 0; double avgTotalMutations = 0; double avgMutationAge = 0; //visualize all mutations on a 30x30 grid double mutationGridtXY[][] = new double[30][30]; double mutationGridtXYold[][][] = new double[20][30][30]; double mutationAgeHistogram[] = new double[50]; double mutationSpeed = 0; double mutationDiversity = 0; double mutationDiversityAge = 0; public int c1, c2, c3, c4; public int born, die1, die2, stayed; public int gridSize; public int mutationCount; public int mutationMiss; public Mutation freqMutation; public int freqMuteFreq =0; public int infectedCount =0; public int[] childEnergy = new int[100]; public int[] sproutNumber = new int[100]; public Stats(GameModel gameModel) { this.gameModel = gameModel; } public void reset() { } private Echosystem getEchosystem() { return gameModel.getEchosystem(); } private int getTime() { return gameModel.getTime(); } public String getDisplayText() { final String HEADER_ROW = "<html><body style='font-family:ariel'>"; final String START_TABLE = "<table width='100%' cellspacing='0' cellpadding='0'>"; final String END_TABLE = "</table>"; final String BLANK_ROW = "<tr style='height:5px;'></tr>"; final String FOOTER_ROW = "</body></html>"; String text = ""; text += HEADER_ROW; text += START_TABLE; text += buildDisplayRow("Game Time: ",gameTime); text += buildDisplayRow("Population (Organism #): ",getEchosystem().getOrganisms().size()); text += buildDisplayRow("Smoothed Population: ",(int) (smoothedPopulation+0.5)); //+0.5 to do average vs. floor text += buildDisplayRowBold("Smoothed Pop Density: ",String.format("%.1f", smoothedPopDensity)); //+0.5 to do average vs. floor text += BLANK_ROW; text += buildDisplayRow("Average Age: ",String.format("%.1f", avgAge)); text += buildDisplayRow("Average Lifespan: ",String.format("%.1f", avgLifespan)); text += buildDisplayRowBold("Average Self-Destruct Age: ",String.format("%.1f", avgMaxLifespan/10.0+1)); text += BLANK_ROW; text += buildDisplayRow("Average Child #: ",String.format("%.3f", avgChildNumber)); text += buildDisplayRow("Died Childless %: ",String.format("%.1f", childlessPercent)); text += buildDisplayRow("1 child %\t: ",String.format("%.1f", (childNumberPercent[0]-childNumberPercent[1]))); text += buildDisplayRow("2 chilren %\t: ",String.format("%.1f", (childNumberPercent[1]-childNumberPercent[2]))); text += buildDisplayRow("3 chilren %\t: ",String.format("%.1f", (childNumberPercent[2]-childNumberPercent[3]))); text += buildDisplayRow("4+ chilren %\t: ",String.format("%.1f", (childNumberPercent[4]))); text += buildDisplayRowBold("Average 1st childbirth age: ",String.format("%.1f", avgChildAtAge[0])); text += buildDisplayRow("Average 2nd childbirth age: ",String.format("%.1f", avgChildAtAge[1])); text += buildDisplayRow("Average 3rd childbirth age: ",String.format("%.1f", avgChildAtAge[2])); text += BLANK_ROW; text += buildDisplayRow("Average Cell #: ",String.format("%.1f", avgSize)); text += buildDisplayRow("Average Lifetime Cell#: ",String.format("%.1f", avgMaxSize)); text += buildDisplayRow("Average Territory: ",String.format("%.1f", avgTerritory)); text += buildDisplayRowBold("Average Lifetime Territory: ",String.format("%.1f", avgMaxTerritory)); text += buildDisplayRow("Board size / population: ",String.format("%.1f", boardSizeDivPopulation)); text += BLANK_ROW; text += buildDisplayRowBold("Average Mutation #: ",String.format("%.1f", avgTotalMutations)); text += buildDisplayRowBold("Mutation Diversity: ",String.format("%.1f", mutationDiversity*10)); text += buildDisplayRowBold("Evolution speed (avg genome vector delta): ",String.format("%.1f", mutationSpeed)); text += buildDisplayRow("Average Mutation Age: ",String.format("%.1f", avgMutationAge)); text += buildDisplayRow("Mutation Diversity Age: ",String.format("%.1f", mutationDiversityAge)); text += buildDisplayRow("Mutation age histogram: "); text += buildMutationAgeHistogram(); text += buildDisplayRow("Avg Genome, mutat prevalence: "); text += buildDisplayRow("2=20%, $=95%, #=99% "); text += buildGenomeGrid(); text += END_TABLE; text += FOOTER_ROW; return text; } public void update() { gameTime = getTime(); updateSizeStats(); updateLifespanStats(); updateChildStats(); updateMutationStats(); } private String buildDisplayRow(String label, Object value) { return "<tr><td>"+label+"</td><td align='right'>"+value+"</td></tr>"; } private String buildDisplayRow(String label) { return "<tr><td colspan='2'>"+label+"</td></tr>"; } private String buildDisplayRowBold(String label, Object value) { return buildDisplayRow(label, "<b>"+value+"</b>"); } private String buildGenomeGrid() { String text = ""; //This is a temporary ascii based visualizatoin //we're switching the x,y axis because the r-pentomino //causes the organism to grow and mutate horizontally, and we //have more room vertically. for (int i=0;i<14;i++) { String mutationHist = ""; for (int j=0;j<18;j++) { if (mutationGridtXY[i][j]<1) { mutationHist+=" "+String.format("_"); } else if (mutationGridtXY[i][j]>=9.9) { mutationHist+=" "+String.format("#"); } else if (mutationGridtXY[i][j]>=9.5) { mutationHist+=" "+String.format("$"); } else { //if (Math.abs(mutationGridtXYold[12][i][j]-mutationGridtXY[i][j])>2) { // mutationHist+=" <b>"+String.format("%.0f",mutationGridtXY[i][j])+"</b>"; //} //else { mutationHist+=" "+String.format("%.0f",mutationGridtXY[i][j]); //} } } text += buildDisplayRow(mutationHist); } return text; } private String buildMutationAgeHistogram() { String text = ""; for (int r=0;r<4;r++) { String mutationHist = ""; for (int t=0;t<10;t++) { if (mutationAgeHistogram[r*10+t]<0.1) { mutationHist+=" "+"__"; } else { mutationHist+=" "; if (mutationAgeHistogram[r*10+t]+0.05<1) { mutationHist+="_"; } mutationHist+=String.format("%.0f",mutationAgeHistogram[r*10+t]*10); } } text += buildDisplayRow(mutationHist); } return text; } public void updateSmoothedPopulation() { int population = getEchosystem().getOrganisms().size(); smoothedPopulation = (smoothedPopulation*999+population)/1000; //We expect the actual population to once in a while be within 10% of the smoothed population //If it's not, something changed, so reset the smoothed population to the actual population if (population>0 && Math.abs(smoothedPopulation/ population - 1)>0.1) { this.smoothedResetTimer++; } else { smoothedResetTimer=0; } if (smoothedResetTimer>100) { smoothedPopulation = (smoothedPopulation*9+population)/10; //smoothedPopulation = population; //smoothedResetTimer = 0; } smoothedPopDensity = smoothedPopulation*10000/gameModel.getBoard().getHeight()/gameModel.getBoard().getWidth(); } public void printHistogram() { int cellCount = 0; for (Organism o : getEchosystem().getOrganisms()) { int oc = o.size(); cellCount += oc; } int[] countAtAge = new int[100]; int[] countAtSize = new int[100]; int[] countAtLifespan = new int[200]; int[] maxSizeAtLifespan = new int[200]; int[] maxAgeAtLifespan = new int[200]; int[] ageHalfLifespan = new int[200]; int[] sizeAtAge = new int[100]; int[] mutationCount = new int[100]; for (Organism o : getEchosystem().getOrganisms()) { int oc = o.size(); cellCount += oc; countAtSize[o.size()/5]+=1; countAtAge[o.getAge()/5]+=1; countAtLifespan[o.lifespan/2]+=1; //int mc = o.getGenome().getRecentMutations(0,getEchosystem().getTime(),o.lifespan).size(); //mutationCount[mc/3]++; if (oc>maxSizeAtLifespan[o.lifespan/2]) { maxSizeAtLifespan[o.lifespan/2]=oc; } if (o.getAge()>maxAgeAtLifespan[o.lifespan/2]) { maxAgeAtLifespan[o.lifespan/2]=oc; } if (o.getAge()>=o.lifespan/2) { ageHalfLifespan[o.lifespan/2]++;; } sizeAtAge[o.getAge()/5]+=o.size(); } System.out.print(getTime() + " Org count "+getEchosystem().getOrganisms().size()); System.out.print(" Cell count " + cellCount); System.out.print(" Avg cells " + cellCount*10/getEchosystem().getOrganisms().size()); /* System.out.print(" Count at age: "); for (int i=0;i<10;i++) { System.out.print(countAtAge[i]+" "); } */ /* System.out.print(" Count at size: "); for (int i=0;i<10;i++) { System.out.print(countAtSize[i]+" "); } */ System.out.print(" Count at lifespan: "); for (int i=6;i<50;i++) { System.out.print(countAtLifespan[i]+" "); } System.out.print(" AMC: "+(int) avgMaxTerritory); /* System.out.print(" Max cells: "); for (int i=0;i<50;i++) { System.out.print(maxCells[i]+" "); } */ /* System.out.print(" MC: "); for (int i=0;i<50;i++) { System.out.print(mutationCount[i]+" "); } */ /* System.out.print(" Size at age: "); for (int i=0;i<10;i++) { if (countAtAge[i]>0) { System.out.print(sizeAtAge[i]*10/countAtAge[i]+" "); } else { System.out.print("0 "); } } */ } public void printChildEnergy() { printHistogram(); System.out.print(" Avg Life " + avgMaxLifespan); int allEnergy = 0; int childSum = 0; for (int i=0;i<5;i++) { childSum +=sproutNumber[i]; } if (childSum>0) { System.out.print(" AVC: "+avgMaxLifespan*getEchosystem().getOrganisms().size()/childSum); } System.out.print(" RM count: "+getRecentMutationCount(getEchosystem().getTime(),5000)); System.out.print(" CE:"); for (int i=0;i<4;i++) { if (sproutNumber[i]== 0 ) { System.out.print(" 0"); continue; } int e = childEnergy[i]*10/sproutNumber[i]; if (i>0 && sproutNumber[i-1]>0 ) { e-=(childEnergy[i-1]*10/sproutNumber[i-1]); } if(i==0) { allEnergy+=e; } else { if (sproutNumber[0]>0) { allEnergy+=e*sproutNumber[i]/sproutNumber[0]; } } //childEnergy[0]*10/sproutNumber[0]; //childEnergy[1]*10/sproutNumber[0] //childEnergy[0]*10*sproutNumber[1]/sproutNumber[0]/sproutNumber[0]; //childEnergy[0]*10; //childEnergy[1]*10] //childEnergy[0]*10*sproutNumber[1]/sproutNumber[0]; System.out.print(" "+e); } System.out.print(" All Energy2: "+allEnergy); System.out.print(" Ratios: "); if (sproutNumber[0]>0) { System.out.print(" "+sproutNumber[1]*1000/sproutNumber[0]); System.out.print(" "+sproutNumber[2]*1000/sproutNumber[0]); System.out.print(" "+sproutNumber[3]*1000/sproutNumber[0]); } System.out.println(); childEnergy = new int[20]; sproutNumber = new int[20]; } public void printMutations() { System.out.print(getTime() + " Org count "+getEchosystem().getOrganisms().size()); System.out.print(" Avg Life " + avgMaxLifespan); System.out.print(" RM count: "+getRecentMutationCount(10000,1000)); System.out.print(" Mutations: " + mutationCount +" Hit: "+(mutationCount-mutationMiss) + " Percent "+(int) (mutationMiss*100/(mutationCount+0.1))); if (freqMutation!=null) { System.out.print(" MaxFreq "+freqMuteFreq+" x "+freqMutation.getLocation().x+" y "+freqMutation.getLocation().y+" time "+freqMutation.getOrganismAge()); } System.out.println(); //else { //} mutationCount=0; mutationMiss=0; freqMutation = null; freqMuteFreq = 0; } private int getRecentMutationCount(int fromAge, int toAge) { HashSet<Mutation> recentMutations = new HashSet<Mutation>(); HashMap<Mutation,Integer> totalRM = new HashMap<Mutation,Integer>(); for (Organism o: getEchosystem().getOrganisms()) { int fromTime = getEchosystem().getTime()-fromAge; int toTime = getEchosystem().getTime()-toAge; for(Mutation m: o.getGenome().getRecentMutations(fromTime, toTime, o.lifespan)) { recentMutations.add(m); Integer mCount = totalRM.get(m); if (mCount==null) { mCount = 0; } mCount++; totalRM.put(m, mCount); } } int totalCount = 0; for (Integer mc : totalRM.values()) { totalCount +=mc; } return totalCount;//recentMutations.size(); } public void printBDS() { System.out.print(getTime() + " Org count "+getEchosystem().getOrganisms().size()); System.out.println(" Grid: "+gridSize+" Born: "+born+" Die1: "+die1+" Die2: "+die2+" Stayed: "+stayed); } public int getOldestCommonAncestorAge() { HashSet<Organism> generation = new HashSet(getEchosystem().getOrganisms()); HashMap<Organism,Integer> descendantMap = new HashMap<Organism,Integer>(); //System.out.print(getTime() + " Org count "+getEchosystem().getOrganisms().size()); for (int i=1;i<200;i++) { HashSet<Organism> parents = new HashSet<Organism>(); HashSet<Organism> remove = new HashSet<Organism>(); for (Organism o: generation) { if(o.getAge() < i*115 ) { remove.add(o); Organism p = o.getParent(); while (p!=null && (p.getTimeSinceBorn() < i*115)) { Integer dCount = descendantMap.get(p); if (dCount==null) { dCount =0; } descendantMap.remove(p); p=p.getParent(); if(p!=null){ Integer pdCount = descendantMap.get(p); if (pdCount==null) { pdCount =0; } descendantMap.put(p, dCount+pdCount); } } if (p!=null) { Integer dCount = descendantMap.get(p); if (dCount==null) { dCount =0; } Integer odCount = descendantMap.get(o); if (odCount==null) { odCount =1; } descendantMap.put(p, dCount+odCount); parents.add(p); } } } generation.removeAll(remove); generation.addAll(parents); for (Organism r:remove) { descendantMap.remove(r); } if (descendantMap.size()>10) { int x=5; } for (Organism ancestor:generation) { if (descendantMap.get(ancestor)>getEchosystem().getOrganisms().size()/3) { return ancestor.getTimeSinceBorn(); } } //if (remove.size()==1) { // return remove.iterator().next().getTimeSinceBorn(); //} //System.out.print(" "+remove.size()); } return 10000; //System.out.println(); } private void updateSizeStats() { int maxSizeSum = 0; int sizeSum = 0; int territorySum = 0; int sumMaxTerritory = 0; for (Organism o : getEchosystem().getOrganisms()) { sizeSum += o.size(); territorySum +=o.getAttributes().getTerritorySize(); if(o.getParent()!=null) { int ms = o.getParent().getAttributes().maxCells; if (o.getParent().getParent()!=null) { ms = Math.max(ms, o.getParent().getAttributes().maxCells); } maxSizeSum+=ms; } if(o.getParent()!=null) { int ts = o.getParent().getAttributes().getTerritorySize(); if (o.getParent().getAttributes().getTerritorySize()/3<100 && ts/10<100) { maxTerriroty[ts/10]++; } sumMaxTerritory+=ts; } } double population = (double) getEchosystem().getOrganisms().size(); if (population>0) { this.avgSize = sizeSum / population; this.avgTerritory = territorySum / population; this.avgMaxSize = maxSizeSum/ population; this.avgMaxTerritory = sumMaxTerritory/ population; this.boardSizeDivPopulation = gameModel.getBoard().getHeight()*gameModel.getBoard().getWidth()/smoothedPopulation; } } private int getLifespanCountOfAtLeastAge(int age) { int sumLifespan = 0; for (int a=lifespanHistogram.length-1;a>=age;a--) { sumLifespan+=lifespanHistogram[a]; } return sumLifespan; } private void updateLifespanStats() { int ageSum = 0; for (int a=0;a<lifespanHistogram.length;a++) { //lifespanHistogram[a]=0; } for (Organism o : getEchosystem().getOrganisms()) { ageSum +=o.getAge(); //lifespanHistogram[o.getLifespan()]++; } if (getEchosystem().getOrganisms().size()>0) { this.avgAge = ageSum / (double) getEchosystem().getOrganisms().size(); } else { this.avgAge = 0; } int lifespanSum = 0; for (Organism o : getEchosystem().getRetiredOrganisms()) { lifespanSum +=o.getAge(); } if (getEchosystem().getRetiredOrganisms().size()>0) { this.avgLifespan = lifespanSum / (double) getEchosystem().getRetiredOrganisms().size(); } else { this.avgLifespan = 0; } int maxLifespanSum = 0; for (Organism o : getEchosystem().getOrganisms()) { maxLifespanSum +=o.lifespan; } this.avgMaxLifespan = maxLifespanSum*10/ getEchosystem().getOrganisms().size(); } public void updateChildStats() { int sumChildNumber = 0; int sumChildless = 0; int[] childNumberHistogram = new int[5]; //keep track of number number organisms having x number of childern int[] sumChildAge = new int[5]; //keep track of combined parent age having 1st children for (Organism o : getEchosystem().getRetiredOrganisms()) { sumChildNumber += o.getChildren().size(); if (o.getChildren().size()==0) { sumChildless+=1; } for (int ci = 0; ci<o.getChildren().size(); ci++) { int parentAgeAtBirth = o.getChildren().get(ci).getAttributes().parentAgeAtBirth; if (ci<4) { childNumberHistogram[ci]+=1; sumChildAge[ci]+=parentAgeAtBirth; } else { childNumberHistogram[4]+=1; sumChildAge[4]+=parentAgeAtBirth; } //assume getChildren() sorts children in order born } } avgChildNumber = 0; avgChildAtAge = new double[5]; childNumberPercent = new double[5]; if (getEchosystem().getRetiredOrganisms().size()>0) { avgChildNumber = sumChildNumber / (double) getEchosystem().getRetiredOrganisms().size(); childlessPercent = (getEchosystem().getRetiredOrganisms().size()-childNumberHistogram[0])*100.0/getEchosystem().getRetiredOrganisms().size(); for (int ci = 0; ci<5;ci++) { if (childNumberHistogram[ci]>0) { avgChildAtAge[ci] = sumChildAge[ci]/(double) childNumberHistogram[ci]; } if (sumChildNumber>0) { childNumberPercent[ci] = childNumberHistogram[ci]*100.0 / getEchosystem().getRetiredOrganisms().size(); } } } } public void updateMutationStats() { int mutationSum=0; int mutationAgeSum=0; int mutationSumX=0; int mutationSumY=0; int mutationSumLate=0; int mutationSumLateX=0; int mutationSumLateY=0; double population = getEchosystem().getOrganisms().size(); double mutationDistSum = 0 ; mutationDiversity = 0; mutationDiversityAge = 0; HashMap<Mutation,Integer> mutationFreq = new HashMap<Mutation,Integer>(); for (int i=0;i<30;i++) { for (int j=0;j<30;j++) { mutationGridtXY[i][j]=0; } } for (int t=0;t<mutationAgeHistogram.length;t++) { mutationAgeHistogram[t]=0; } for (Organism o : getEchosystem().getOrganisms()) { int toTime = getEchosystem().getTime(); Collection<Mutation> mutations = o.getGenome().getRecentMutations(0, toTime, 100); mutationSum +=mutations.size(); for (Mutation m : mutations) { Integer freq = mutationFreq.get(m); if (freq==null) { freq = 0; } mutationFreq.put(m, freq+1); //if(m.getOrganismAge()<=27) { int countAtAge = getLifespanCountOfAtLeastAge(m.getOrganismAge()); int mx = Math.min(13,Math.max(0, m.getLocation().x+10)); int my = Math.min(20,Math.max(0,m.getLocation().y+10)); //if (countAtAge>population/10.0) { mutationGridtXY[mx][my]+=1.0/population; //} //} mutationAgeSum+=m.getOrganismAge(); if (m.getOrganismAge()<mutationAgeHistogram.length) { //if (countAtAge>population/10.0) { mutationAgeHistogram[m.getOrganismAge()]+=1.0/population; //} } mutationSumX+=m.getLocation().x; mutationSumY+=m.getLocation().y; if (m.getOrganismAge()>25) { mutationSumLateX+=m.getLocation().x; mutationSumLateY+=m.getLocation().y; mutationSumLate++; } } } for (int i=0;i<20;i++) { for (int j=0;j<20;j++) { mutationGridtXY[i][j]/=(0.1);//*(double) getEchosystem().getOrganisms().size()); if(getEchosystem().getTime()%100==0) { mutationDistSum+=(mutationGridtXY[i][j]-mutationGridtXYold[12][i][j])*(mutationGridtXY[i][j]-mutationGridtXYold[12][i][j]); for (int t=18;t>=0;t--) { mutationGridtXYold[t+1][i][j] = (mutationGridtXYold[t+1][i][j] + mutationGridtXYold[t][i][j])/2; } mutationGridtXYold[0][i][j] =(mutationGridtXYold[0][i][j]+mutationGridtXY[i][j])/2; } } } for (int t=0;t<mutationAgeHistogram.length;t++) { //mutationAgeHistogram[t]/=(double) getEchosystem().getOrganisms().size(); } avgMutationAge = mutationAgeSum/(double) mutationSum; avgTotalMutations = mutationSum / (double) getEchosystem().getOrganisms().size(); mutationSpeed = mutationDistSum; for (Mutation m: mutationFreq.keySet()) { double countAtAge = getLifespanCountOfAtLeastAge(m.getOrganismAge()); double divers = 0.5-mutationFreq.get(m)/population; if (0.5-Math.abs(divers)<0) { System.out.println("Diversity less than 0, mf, caa "+mutationFreq.get(m)+" "+population); } //if(countAtAge>0) { mutationDiversity+=(0.5-Math.abs(divers));//*countAtAge/population; //weigh by proportion to population mutationDiversityAge+=m.getOrganismAge()*(0.5-Math.abs(divers)); //} } if( mutationDiversity>0) { mutationDiversityAge/= mutationDiversity; } if(getEchosystem().getTime()%200==0) { System.out.print(getTime() + " M Speed1: "+(int)mutationSpeed); System.out.print(" MDiversity: "+(int)(mutationDiversity*10)); System.out.print(" MAge: "+(int)(avgMutationAge*10)); System.out.print(" MDAgeDiff: "+(int)((mutationDiversityAge-avgMutationAge)*10)); //System.out.print(" OCAA "+ getOldestCommonAncestorAge()); System.out.print(" Pop density "+String.format("%.1f",smoothedPopDensity)); System.out.print(" Avg Life " + (int)avgMaxLifespan); System.out.print(" Max territory " + (int) avgMaxTerritory); System.out.print(" MutationCount " + String.format("%.1f",avgTotalMutations)); if (mutationSum>0) { System.out.print(" Mutation X,Y " + (int) mutationSumX*100/mutationSum+" "+ (int) mutationSumY*100/mutationSum); } else { System.out.print(" Mutation X,Y " + 0+" "+ 0); } if (mutationSumLate>0) { System.out.print(" ML X,Y " + (int) mutationSumLateX*100/mutationSumLate+" "+ (int) mutationSumLateY*100/mutationSumLate); } else { System.out.print(" ML X,Y " + 0+" "+ 0); } System.out.println(); } } }