package iamrescue.agent.ambulanceteam.ambulancetools;
import iamrescue.agent.AbstractIAMAgent;
import iamrescue.agent.ISimulationTimer;
import iamrescue.agent.ambulanceteam.IAMAmbulanceTeam;
import iamrescue.agent.firebrigade.FastFireSite;
import iamrescue.belief.IAMWorldModel;
import iamrescue.execution.command.IPath;
import iamrescue.routing.IRoutingModule;
import iamrescue.routing.util.ISpeedInfo;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.HashMap;
import java.util.Random;
import java.lang.Integer;
import java.lang.Math;
import org.apache.log4j.Logger;
import rescuecore2.standard.entities.AmbulanceTeam;
import rescuecore2.standard.entities.Building;
import rescuecore2.standard.entities.Human;
import rescuecore2.standard.entities.Refuge;
import rescuecore2.standard.entities.StandardEntity;
import rescuecore2.worldmodel.EntityID;
public class RScheduler
{
/**
* Variable to print out comments
*/
private static final Logger LOGGER = Logger.getLogger(IAMAmbulanceTeam.class);
/**
* The current time step
*/
private int timeStep;
/**
* The memory of the agent using this scheduler
*/
private IAMWorldModel memory;
/**
* The list of ids in my team
*/
private AmbulanceTeam[] team;
/**2
* My index in the SORTED version of the team!!
*/
private int myIndex;
/**
* List of all buildings
*/
private StandardEntity[] allBuildings;
/**
* The avoid blockage path planner
*/
private IRoutingModule pathPlanner;//no used
/**
* The victims from the previous time step
*/
//private ArrayList<RescueTask> oldVictims;
/**
* The new Strategy: tasks to execute in the next time window
*/
public ArrayList<RescueTask> nextTasks;
/**
* all well-know tasks
*/
private HashMap<EntityID, RescueTask> allTasks;
/**
* Matrix of agent's ids: for each ambulance(row) is reported its strategy for the next time window
*/
public int[][] nextAllocations ;
/**
* Number of all found civilians
*/
private int allVictimsSize;
/**
* dimension of the decision window
*/
private int window=AladdinInterAgentConstants.ALLOCATION_WINDOW;
/**
* Determines how frequently strategy should be recomputed
*/
private double ReAllocProb = 0.9;
/**
* to obtain random number in [0,1] interval
*/
private Random generator;
/*
* deadline function
*/
DeadlineFunction deadline = new DeadlineFunction();
private ISpeedInfo speed;
private FireTracker fires;
private ISimulationTimer timer;
/**
* Constructor for the RScheduler
* @param _oldVics - the previous set of victims - empty to start with
* @param mem - the memory of the agent
* @param planner the avoid blockage path planner
* @param _ignorePlanner the ignore blockage path planner
* @param _team list of ids in my team
* @param index my index in the SORTED version of the team ids
* @param time the current time step
* @param iSpeedInfo
* @param iamAmbulanceTeam
*/
public RScheduler(ArrayList<RescueTask> _oldVics, IAMWorldModel mem,StandardEntity[]_allBuildings, IRoutingModule planner, int time, ISpeedInfo iSpeedInfo, IAMAmbulanceTeam me, ISimulationTimer timer){
//oldVictims = _oldVics;
memory = mem;
pathPlanner = planner;
timeStep = time;
allBuildings = _allBuildings;
speed = iSpeedInfo;
this.timer=timer;
nextTasks=new ArrayList<RescueTask>();
allTasks = new HashMap<EntityID, RescueTask>() ;
allVictimsSize = 0;
generator = new Random();
fires = new FireTracker(timer, mem);
}
/**
* @return myIndex: the index of this agent in the "team" vector
*/
public int myIndex(){return myIndex;}
/**
* This method returns the estimated deadline of a given victim with the assigned ambulances
* @param victim - the humanoid
* @return - the estimated deadline
*/
public double getVictimDeadline(Human victim)
{
return deadline.getDeadline(victim.getHP());
}
/**
* This method chooses the next victim to rescue.
* @return the next victim to rescue
*/
public Human chooseNextVictim()
{
Human nextVictim=null;
if(nextTasks.size()>0)
{
nextVictim=nextTasks.get(0).getVictim();
nextTasks.remove(0);//remove the next victim from the nextTasks vector
}
return nextVictim;
}
/**
* This method chooses what the agent's strategy for the next 'window' time steps.
* where 'window' is ALLOCATION_WINDOW constant.
* @param allVictims - all found civilians
* @param time - current time
* @return true if there are some available task, false if nextTasks is empty
*/
public boolean chooseStrategy(ArrayList<Task> allVictims, int time)
{
timeStep = time;
allVictimsSize = allVictims.size();
if(allVictimsSize>0)//at least one task available
{
// update nextTasks vector
if(nextTasks.size()>0 )
{
RescueTask thisTask = nextTasks.get(0);
if(thisTask.victim.getBuriedness()==0)
while(nextTasks.size()>0 && nextTasks.get(0) == thisTask)
nextTasks.remove(0);
}
StandardEntity pos = memory.getEntity(team[myIndex].getID());
for(Iterator<RescueTask> it=nextTasks.iterator(); it.hasNext();){
AbstractIAMAgent.stopIfInterrupted();
RescueTask task = it.next();
if(!task.getVictim().isPositionDefined() || !task.getVictim().isXDefined() || !task.getVictim().isYDefined()){
it.remove();
} else if(memory.getEntity(task.getVictim().getPosition()) instanceof AmbulanceTeam && task.getVictim().getPosition().getValue() != team[myIndex].getID().getValue()){
it.remove();
} else if(task.getVictim().getHP()==0){
it.remove();
//} else if(!task.pathStillValid(team[myIndex].getID())){
} else if(!task.pathStillValid(allVictims)) {
it.remove();
}
}
//recalculate the nextTasks vector with ReAllocProbability or if the agent is in a refuge or if nextTask vector is quite empty
if(generator.nextDouble() < ReAllocProb || (pos instanceof Refuge) || nextTasks.size() < window*2.0/3)
{
if (LOGGER.isTraceEnabled())
LOGGER.trace(" - calculate strategy victimsize:"+allVictimsSize);
nextTasks.clear();//delete the old vector
RescueTask task;
Task t;
Human victim;
ArrayList<RescueTask> sortedAllTasks=new ArrayList<RescueTask>();
/* This iterator traverses the list of victims sorted according to shortest deadline first.
It creates a RescueTask for each victim and calculates the utility for each task.*/
for (Iterator<Task> it = allVictims.iterator();it.hasNext();)
{
AbstractIAMAgent.stopIfInterrupted();
t= it.next();
victim = t.civilian;
if(allTasks.containsKey(victim.getID()))
{
task = (RescueTask)allTasks.get(victim.getID());
task.path=t.path;
}
else
task=new RescueTask(victim, t.path);
task.setMarginalUtility(time);
allTasks.put(victim.getID(), task);
sortedAllTasks.add(task);
}
//tasks sorted according to bigger utility first
Collections.sort(sortedAllTasks, new TaskComparator());
//decides what it will do in the next time window
int k=time+1;
//calculate the vector of tasks to executed in the next time window
for(Iterator<RescueTask> it=sortedAllTasks.iterator();it.hasNext() && k<time+1+window;)
{
AbstractIAMAgent.stopIfInterrupted();
task = it.next();
if(LOGGER.isTraceEnabled())
LOGGER.trace(" saving victim: "+task.getVictim().getID()+" with utility: "+task.marginalUtility);
int taskCompletionTime = (int)Math.ceil(task.getCompletionTime(time,true));
while(k<=taskCompletionTime && k<time+1+window)
{
nextTasks.add(task);
k++;
}
}
if(LOGGER.isTraceEnabled() && k<time+1+window)
LOGGER.trace(" - my stategy is shorter that window");
}
else //UPDATE the nextTasks vector: add a task, which is choosen in random way
{
RescueTask randomTask = null;
for(int tries=0;randomTask == null && tries < 5;tries++)
randomTask = (RescueTask)allTasks.get((allVictims.get(generator.nextInt(allVictims.size()))).civilian.getID());
while(nextTasks.size()<window && randomTask != null)
nextTasks.add(randomTask);
if (LOGGER.isTraceEnabled())
LOGGER.trace(" - update strategy victimsize:"+allVictimsSize);
}
updateNextAllocations();
if(LOGGER.isTraceEnabled())
printMyAllocation();
return true;
}
else
{//no tasks available
if (LOGGER.isTraceEnabled())
LOGGER.trace(" - in chooseStrategy victimsize:"+allVictimsSize);
return false;
}
}
/**
* Add the travel time to the strategy of this ambulance
* @return strategy - strategy updating with travel time too
*/
private ArrayList<Integer> getStrategyWithTravelTime()
{
ArrayList<Integer> strategy=new ArrayList<Integer>();
RescueTask previousTask = null;
RescueTask thisTask = null;
int thisTaskID;
int travelling = 0;
for(Iterator<RescueTask> it = nextTasks.iterator(); it.hasNext();)
{
thisTask = it.next();
if(thisTask == null)
continue;
if(thisTask != previousTask)
{
previousTask = thisTask;
travelling = (int)Math.ceil(thisTask.travelTime());
}
thisTaskID = thisTask.getVictim().getID().getValue();
if(travelling==0)
strategy.add(thisTaskID);
else
{
strategy.add(-1);
travelling--;
}
}
// if enough tasks are not there, then add -2 until window which means i'm travelling because I haven't anything to do
while(strategy.size()<window)
strategy.add(-2);
return strategy;
}
/**
* Add this ambulance'id to the strategy of this ambulance, in order to send the strategy to othe ambulances
* @return strategy - ambulance'id + strategy updating with travel time
*/
public int[] getStrategyToSend()
{
ArrayList<Integer> strategyWithTravelTime=new ArrayList<Integer>();
strategyWithTravelTime.addAll(getStrategyWithTravelTime());
Object[] ar = strategyWithTravelTime.toArray();
int[] ret = new int[ar.length];
for(int i=0; i<ret.length; i++){
ret[i] = (Integer) ar[i];
}
return ret;
}
public int[] encodeStrategyToSend(){
int[] strategy = getStrategyToSend();
//encode this as an alternating array of tasks and durations
//-1 -1 -1 123 123 123 123 -1 -1 456 456 456 -2 -2 -2 -2 would become
//-1 3 123 4 -1 2 456 3 -2 4
ArrayList<Integer> encoded = new ArrayList<Integer>();
int current = strategy[0];
int time = 1;
for(int i=1; i<strategy.length; i++){
if(strategy[i]==current){
time++;
} else {
encoded.add(current);
encoded.add(time);
current=strategy[i];
time=1;
}
}
encoded.add(current);
encoded.add(time);
int[] ret = new int[encoded.size()];
for(int i=0; i<ret.length; i++){
ret[i]=encoded.get(i);
}
return ret;
}
public int getMyTeamIndex(int agent){
for(int i=0; i<team.length; i++){
if(agent == team[i].getID().getValue()){
return i;
}
}
return Integer.MAX_VALUE;
}
public int[] decodeStrategy(int[] encoded){
ArrayList<Integer> decoded = new ArrayList<Integer>();
for(int i=0; i<encoded.length;i++){
for(int j=0; j<encoded[i+1]; j++){
decoded.add(encoded[i]);
}
i++;
}
int[] ret = new int[decoded.size()];
for(int i=0; i<ret.length; i++){
ret[i]=decoded.get(i);
}
return ret;
}
/**
* Update the nextAllocations matrix.
* It writes the ids of next victims to rescue or -1(for travel time) in the row (myIndex) .
*/
private void updateNextAllocations()
{
ArrayList<Integer> strategyWithTravelTime=getStrategyWithTravelTime();
int nextTime = 0;
for(Iterator<Integer> it = strategyWithTravelTime.iterator();it.hasNext() && nextTime<window;)
{
nextAllocations[myIndex][nextTime] = it.next();
nextTime++;
}
}
/**
* This method the next Victims to rescue
*
*/
private void printMyAllocation()
{
String output = " nextStrategy: ";
for(int i=0; i<nextTasks.size() ;i++)
output = output + nextTasks.get(i).getVictim().getID()+" ";
output = output+ "\nnextAllocations: ";
for(int i=0;i<nextAllocations[myIndex].length;i++)
output = output + nextAllocations[myIndex][i]+" ";
LOGGER.trace(output);
}
/**
* Compute distance between victim and ambulance based on direct distance
* @param victim
* @param ambulance
* @return distance in mm
*/
private double computePathDistance(RescueTask victim, StandardEntity ambulance)
{
if(LOGGER.isTraceEnabled()){
LOGGER.trace(" in path distance " + ambulance.getID().getValue() + " " + victim.getVictim().getID().getValue());
LOGGER.trace(" locations " + ((Human) ambulance).getPosition().getValue()+ " " + victim.getVictim().getPosition().getValue());
}
/*IPath oldPath = victim.path;
IPath path;
if(victim.getVictim().getPosition().getValue()==oldPath.getLocations().get(oldPath.getLocations().size()-1).getValue() &&
((Human)ambulance).getPosition().getValue()==oldPath.getLocations().get(0).getValue()){
//path is still valid
path=oldPath;
} else {
//its not;
path= pathPlanner.findShortestPath(ambulance.getID(),victim.getVictim().getID());
this.ambulance.pathCount++;
if(LOGGER.isTraceEnabled())
LOGGER.trace(" PATH:setting new path in compute distance");
victim.path=path;
}*/
double cost = speed.getTimeToTravelPath(victim.path);
if(LOGGER.isTraceEnabled()){
LOGGER.trace(" time to travel is " + cost);
}
return cost;
}
/**
* Contains the victim and the assigned ambulances.
* This class is used by the scheduler to keep track of allocated tasks.
*/
public class RescueTask
{
/**
* the victim to rescue
*/
private Human victim;
/**
* path at start
*/
public IPath path;
/**
* d.factor to calculate utility functions
*/
private double beta=0.9;
/**
* the ambulance's utility
*/
private double marginalUtility;
/**
* The constructor
* @param h - the victim
*/
public RescueTask(Human h, IPath p)
{
victim = h;
path=p;
}
/**
* Return the deadline of this task
* @return deadline
*/
public double getDeadline() {
ArrayList<Building> buildings = getFireSites();
double min = Double.MAX_VALUE;
double fireSpeed = 2177;
for(Iterator<Building> it = buildings.iterator(); it.hasNext();){
Building b = it.next();
double time = timer.getTime() + (timeToHere(b,victim)/fireSpeed);
if(time<min){
min=time;
}
}
return Math.min(min, timer.getTime()+getVictimDeadline(victim));
}
private double timeToHere(Building b, Human victim2) {
return memory.getDistance(b.getID(), victim.getID());
}
ArrayList<Building> getFireSites(){
ArrayList<Building> fringeBuildings = new ArrayList<Building>();
for (FastFireSite fireSite : fires.getFireSites()) {
fringeBuildings.addAll(fireSite.getFringe());
}
return fringeBuildings;
}
/**
* Return the victim to rescue
* @return victim
*/
public Human getVictim() {return victim;}
/**
* Calculate how much time is necessary to reach and rescue the victim.
* @return processingTime - time that is necessary to reach and rescue the victim
*/
private double getProcessingTime()
{
double distance = computePathDistance(this,team[myIndex]);
double travelTime=distance;
return (victim.getBuriedness()+travelTime);
}
public boolean pathStillValid(ArrayList<Task> toDo){
for(Iterator<Task> it=toDo.iterator(); it.hasNext();){
Task t = it.next();
if(t.civilian.getID().getValue()==victim.getID().getValue()){
this.path=t.path;
return true;
}
}
return false;
}
public boolean pathStillValid(EntityID location){
if(path.isValid()){
if(victim.getPosition().getValue()==path.getLocations().get(path.getLocations().size()-1).getValue() &&
location.getValue()==path.getLocations().get(0).getValue()){
//path is still valid
} else {
//its not;
path= pathPlanner.findShortestPath(location,victim.getID());
}
} else {
path= pathPlanner.findShortestPath(location,victim.getID());
}
return path.isValid();
}
/**
* Calculate how much time is necessary to reach the victim.
* @return travelTime - time that is necessary to reach the victim
*/
private double travelTime()
{
double distance= computePathDistance(this,team[myIndex]);
return distance;
}
/**
* Calculate the task's utility
* @return ask's utility
*/
private double utility(double completionTime, double deadline)
{
double utility=0;
if(completionTime<=deadline)
utility=Math.pow(beta,completionTime);
if(LOGGER.isTraceEnabled())
LOGGER.trace(" : "+victim+" has Utility:"+utility+", completionTime:"+completionTime+", deadline:"+deadline);
return utility;
}
/**
* Calculate the time that is necessary to complete this task
* @param timeStep - the current time
* @param partecipation - it is true, if this agent collaborate to the task
* @return completitionTime
*/
private double getCompletionTime(int timeStep, boolean partecipation)
{
double completionTime=timeStep+1;//minimum value
if(timeStep<=3)//at beginning
{
if(allVictimsSize>0)
completionTime=timeStep+this.getProcessingTime()/(team.length/allVictimsSize);//ambulances are shared equally between the victims;
}
else//during the simulation
{
if(LOGGER.isTraceEnabled() && partecipation){
LOGGER.trace(" with me");
} else {
if(LOGGER.isTraceEnabled()){
LOGGER.trace(" without me");
}
}
int needProcessing=(int)Math.ceil(this.getProcessingTime());
double estimation=(double)needProcessing;
if(LOGGER.isTraceEnabled()){
LOGGER.trace(" " + partecipation + " initial processing time " + estimation);
}
int j=0;
int time=timeStep+1;
while(j<window && needProcessing>0)
{
for (int i=0;i<team.length;i++)
if(partecipation)
{
if((this.getVictim().getID()).getValue()==nextAllocations [i][j]){
needProcessing--;
//if(LOGGER.isTraceEnabled()){
// LOGGER.trace("reduced processing for true task");
//}
}
}
else {
if(i!=myIndex && (this.getVictim()).getID().getValue()==nextAllocations [i][j]){
needProcessing--;
//if(LOGGER.isTraceEnabled()){
// LOGGER.trace("reduced processing for false task");
//}
}
}
j++;
time++;
}
if(LOGGER.isTraceEnabled()){
LOGGER.trace(" time digging " + time + " new processing " + needProcessing);
}
completionTime = time;
if(needProcessing>0)//if more processing time is necessary
{
estimation=((double)window*estimation)/(estimation-(double)needProcessing);
if(LOGGER.isTraceEnabled()){
LOGGER.trace(" still to do " + estimation);
}
completionTime+=(int)estimation;
//completionTime+=((int)estimation-needProcessing);
}
if(LOGGER.isTraceEnabled()){
LOGGER.trace(" complete time " + completionTime);
}
}
return completionTime;
}
/**
* Set the agent's utility, which is the marginal utility
*/
private void setMarginalUtility(int time)
{
double deadline = getDeadline();//time+getVictimDeadline(victim);
double utilityDoing = utility(getCompletionTime(time, true),deadline);
double utilityNotDoing = utility(getCompletionTime(time, false),deadline);
//deadline = deadline - timer.getTime();
marginalUtility = (utilityDoing - utilityNotDoing)- (0.05*deadline) - (0.95*travelTime());//
if(LOGGER.isTraceEnabled() && utilityDoing > 0 && utilityNotDoing == 0)
LOGGER.trace(" - SUCCESS marginal utility is useful for task: "+this.getVictim().getID());
}
/**
* Compares task's utilities
* @params a - first task
* @params b - second task
* @return a negative integer, zero or a positive integer
* as the first argument is greater than, equal to o less than the second
*/
/* private int compareTo(RescueTask a)
{
if (a.marginalUtility<this.marginalUtility) return 1;
else if (a.marginalUtility>this.marginalUtility) return -1;
else return 0;
}
*/
/**
* Compares this task's victim with task a' victim
* @params task a
* @return true or false
*/
/* private boolean equals (RescueTask a)
{return a.getVictim().equals(this.getVictim());}
*/
}//end RescueTask
public class TaskComparator implements Comparator<Object>
{
/**
* Compares marginal utilities
* @params e1 - first task
* @params e2 - second task
* @return a negative integer, zero or a positive integer
* as the first argument is greater than, equal to o less than the second
*/
public int compare(Object e1, Object e2) {
if(e1 instanceof RescueTask && e2 instanceof RescueTask)
{
if( ((RescueTask)e1).marginalUtility > ((RescueTask)e2).marginalUtility)
return -1;
else if( ((RescueTask)e1).marginalUtility == ((RescueTask)e2).marginalUtility)
return 0;
else
return 1;
}
return 0;
}
}
public void setmyIndex(int myIndex2) {
// TODO Auto-generated method stub
myIndex=myIndex2;
}
public void setTeam(AmbulanceTeam[] team2) {
team = team2;
nextAllocations=new int[team.length][window];
}
}//end RScheduler