edu.snu.leader.hidden.SimulationState.java Source code

Java tutorial

Introduction

Here is the source code for edu.snu.leader.hidden.SimulationState.java

Source

/*
 *  The Bio-inspired Leadership Toolkit is a set of tools used to
 *  simulate the emergence of leaders in multi-agent systems.
 *  Copyright (C) 2014 Southern Nazarene University
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package edu.snu.leader.hidden;

// Imports
import edu.snu.leader.hidden.builder.IndividualBuilder;
import edu.snu.leader.hidden.event.EventTimeCalculator;
import edu.snu.leader.hidden.personality.ConstantPersonalityCalculator;
import edu.snu.leader.hidden.personality.PersonalityCalculator;
import edu.snu.leader.util.MiscUtils;
import ec.util.MersenneTwisterFast;
import org.apache.commons.lang.Validate;
import org.apache.log4j.Logger;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Queue;

/**
 * SimulationState
 *
 * TODO Class description
 *
 * @author Brent Eskridge
 * @version $Revision$ ($Author$)
 */
public class SimulationState {
    /** Our logger */
    private static final Logger _LOG = Logger.getLogger(SimulationState.class.getName());

    /** Key for the the random number seed */
    private static final String _RANDOM_SEED_KEY = "random-seed";

    /** Key for the individual count */
    private static final String _INDIVIDUAL_COUNT_KEY = "individual-count";

    /** Key for the number of nearest neighbors */
    private static final String _NEAREST_NEIGHBOR_COUNT_KEY = "nearest-neighbor-count";

    /** Key for the distance for nearest neighbors */
    private static final String _NEAREST_NEIGHBOR_DISTANCE_KEY = "nearest-neighbor-distance";

    /** Key for the flag indicating that individuals should be rebuild */
    private static final String _REBUILD_INDIVIDUALS_KEY = "rebuild-individuals";

    /** Key for the flag indicating that the nearest neighbor size should be
     * used as an initiator's group size */
    private static final String _USE_NEAREST_NEIGHBOR_GROUP_SIZE_KEY = "use-nearest-neighbor-group-size";

    /** Key for the number of times to run the simulator */
    private static final String _SIMULATION_COUNT_KEY = "simulation-count";

    /** Key for the event time calculator class */
    private static final String _EVENT_TIME_CALCULATOR_CLASS = "event-time-calculator-class";

    /** Key for the individual builder class */
    private static final String _INDIVIDUAL_BUILDER_CLASS = "individual-builder-class";

    /** Key for the personality calculator */
    private static final String _PERSONALITY_CALCULATOR_CLASS = "personality-calculator-class";

    /** Key for the flag denoting whether the total number of departed
     * individuals should be used or the number of nearest neighbors
     * that have departed. */
    private static final String _USE_ALL_DEPARTED_KEY = "use-all-departed";

    /** Current simulation index */
    private long _simIndex = 0;

    /** Unique individual ID counter */
    private long _indIDCounter = 0;

    /** Unique group ID counter */
    private long _groupIDCounter = 0;

    /** The simulation properties */
    private Properties _props = null;

    /** Random number generator */
    private MersenneTwisterFast _random = null;

    /** All the individuals in the simulation */
    private List<SpatialIndividual> _allIndividuals = new LinkedList<SpatialIndividual>();

    /** The event time calculator */
    private EventTimeCalculator _eventTimeCalc = null;

    /** The individual builder */
    private IndividualBuilder _indBuilder = null;

    /** The personality calculator */
    private PersonalityCalculator _personalityCalc = null;

    /** The total number of individuals */
    private int _individualCount = 0;

    /** The number of nearest neighbors for each individual */
    private int _nearestNeighborCount = 0;

    /** The distance within which are neighbors */
    private float _nearestNeighborDistance = 0.0f;

    /** Individuals remaining (i.e., not yet departed) */
    private Map<Object, SpatialIndividual> _remaining = new HashMap<Object, SpatialIndividual>();

    /** Individuals who have departed (i.e., either initiated or followed) */
    private Map<Object, SpatialIndividual> _departed = new HashMap<Object, SpatialIndividual>();

    /** Individuals that are eligible initiators */
    private Map<Object, SpatialIndividual> _eligibleInitiators = new HashMap<Object, SpatialIndividual>();

    /** Flag indicating that individual's should be rebuilt */
    private boolean _rebuildIndividuals = false;

    /** Flag indicating hat an initiator's group size is the number of
     * nearest neighbors */
    private boolean _useNearestNeighborGroupSize = false;

    /** The max number of individuals departed */
    private int _maxDepartedCount = 0;

    /** The maximum radius for generated locations */
    private float _maxRadius = 1.0f;

    /** The number of times to run the simulator */
    private int _simulationCount = 0;

    /** The predefined locations for individuals */
    private List<Point2D> _locations = new LinkedList<Point2D>();

    /** Flag denoting whether the total number of departed individuals should be
     * used or the number of nearest neighbors that have departed. */
    private boolean _useAllDeparted = true;

    /**
     * Initialize the simulation state
     *
     * @param props
     */
    public void initialize(Properties props) {
        _LOG.trace("Entering initialize( props )");

        // Save the properties
        _props = props;
        //        _LOG.warn( props.toString() );

        // Get the random number generator seed
        String randomSeedStr = props.getProperty(_RANDOM_SEED_KEY);
        Validate.notEmpty(randomSeedStr, "Random seed is required");
        long seed = Long.parseLong(randomSeedStr);
        _random = new MersenneTwisterFast(seed);

        // Get the number of individuals to create
        String individualCountStr = _props.getProperty(_INDIVIDUAL_COUNT_KEY);
        Validate.notEmpty(individualCountStr,
                "Individual count (key=" + _INDIVIDUAL_COUNT_KEY + ") may not be empty");
        _individualCount = Integer.parseInt(individualCountStr);

        // Get the number of nearest neighbors
        String nearestNeighborCountStr = _props.getProperty(_NEAREST_NEIGHBOR_COUNT_KEY);
        Validate.notEmpty(nearestNeighborCountStr,
                "Nearest neighbor count (key=" + _NEAREST_NEIGHBOR_COUNT_KEY + ") may not be empty");
        _nearestNeighborCount = Integer.parseInt(nearestNeighborCountStr);

        // Get the distance used to calculate of nearest neighbors
        String nearestNeighborDistanceStr = _props.getProperty(_NEAREST_NEIGHBOR_DISTANCE_KEY);
        //        Validate.notEmpty( nearestNeighborDistanceStr,
        //                "Nearest neighbor distance (key="
        //                + _NEAREST_NEIGHBOR_DISTANCE_KEY
        //                + ") may not be empty" );
        if (null != nearestNeighborDistanceStr) {
            _nearestNeighborDistance = Float.parseFloat(nearestNeighborDistanceStr);
        }

        // Get the individual rebuild flag
        String rebuildIndividualsStr = _props.getProperty(_REBUILD_INDIVIDUALS_KEY);
        Validate.notEmpty(rebuildIndividualsStr,
                "Rebuild individuals flag (key=" + _REBUILD_INDIVIDUALS_KEY + ") may not be empty");
        _rebuildIndividuals = Boolean.parseBoolean(rebuildIndividualsStr);

        // Get the flag that specifies how the group size is calculated
        String useNearestNeighborGroupSizeStr = _props.getProperty(_USE_NEAREST_NEIGHBOR_GROUP_SIZE_KEY);
        Validate.notEmpty(useNearestNeighborGroupSizeStr, "Use nearest neighbor as group size flag (key="
                + _USE_NEAREST_NEIGHBOR_GROUP_SIZE_KEY + ") may not be empty");
        _useNearestNeighborGroupSize = Boolean.parseBoolean(useNearestNeighborGroupSizeStr);

        // Get the simulation count
        String simulationCountStr = _props.getProperty(_SIMULATION_COUNT_KEY);
        Validate.notEmpty(simulationCountStr, "Simulation count is required");
        _simulationCount = Integer.parseInt(simulationCountStr);

        // Load the event time calculator class
        String eventTimeCalcStr = props.getProperty(_EVENT_TIME_CALCULATOR_CLASS);
        Validate.notEmpty(eventTimeCalcStr,
                "Event time calculator class (key=" + _EVENT_TIME_CALCULATOR_CLASS + ") may not be empty");
        _eventTimeCalc = (EventTimeCalculator) MiscUtils.loadAndInstantiate(eventTimeCalcStr,
                "Event time calculator class");
        _eventTimeCalc.initialize(this);

        // Load the individual builder
        String indBuilderStr = props.getProperty(_INDIVIDUAL_BUILDER_CLASS);
        Validate.notEmpty(indBuilderStr,
                "Individual builder class (key=" + _INDIVIDUAL_BUILDER_CLASS + ") may not be empty");
        _indBuilder = (IndividualBuilder) MiscUtils.loadAndInstantiate(indBuilderStr, "Individual builder class");
        _indBuilder.initialize(this);

        // Load the personality calculator class
        String personalityCalculatorStr = props.getProperty(_PERSONALITY_CALCULATOR_CLASS);
        if (null != personalityCalculatorStr) {
            _personalityCalc = (PersonalityCalculator) MiscUtils.loadAndInstantiate(personalityCalculatorStr,
                    "Personality calculator class");
        } else {
            _personalityCalc = new ConstantPersonalityCalculator();
        }
        _personalityCalc.initialize(this);

        /*  Get the flag denoting whether the total number of departed
         * individuals should be used or the number of nearest neighbors
         * that have departed */
        String useAllDepartedString = _props.getProperty(_USE_ALL_DEPARTED_KEY);
        if (null != useAllDepartedString) {
            _useAllDeparted = Boolean.parseBoolean(useAllDepartedString);
            _LOG.info("Using _useAllDeparted=[" + _useAllDeparted + "]");
        }

        // Create the individuals
        createIndividuals();

        _LOG.trace("Leaving initialize( props )");
    }

    /**
     * Resets this simulation state
     */
    public void reset() {
        // Clear the maps of remaining and departed individuals
        _remaining.clear();
        _eligibleInitiators.clear();
        _departed.clear();
        _maxDepartedCount = 0;

        // Do we build new individuals?
        if (_rebuildIndividuals) {
            // Yup
            _allIndividuals.clear();
            createIndividuals();
        } else {
            // Nope, just reset them all
            Iterator<SpatialIndividual> indIter = _allIndividuals.iterator();
            while (indIter.hasNext()) {
                indIter.next().reset();
            }
        }

        // Add all the individuals to the remaining and eligible initiators maps
        Iterator<SpatialIndividual> indIter = _allIndividuals.iterator();
        while (indIter.hasNext()) {
            SpatialIndividual ind = indIter.next();
            _remaining.put(ind.getID(), ind);
            _eligibleInitiators.put(ind.getID(), ind);
        }
    }

    /**
     * Updates the nearest neighbors for all the individuals
     */
    public void updateAllNearestNeighbors() {
        // Reset all the neighbor information for all individuals
        Iterator<SpatialIndividual> indIter = _allIndividuals.iterator();
        while (indIter.hasNext()) {
            indIter.next().resetNearestNeighbors();
        }

        // Have the individuals find their neighbors
        indIter = _allIndividuals.iterator();
        while (indIter.hasNext()) {
            indIter.next().findNearestNeighbors(this);
        }

        // Have the individuals find their neighbors
        indIter = _allIndividuals.iterator();
        while (indIter.hasNext()) {
            SpatialIndividual current = indIter.next();
            _LOG.debug("Ind=[" + current.getID() + "] mimics=[" + current.getMimickingNeighborCount()
                    + "] neighbors=[" + current.getNearestNeighborCount() + "]");
        }

    }

    /**
     * Return the number of individuals remaining
     *
     * @return The number of individuals remaining
     */
    public int getRemainingCount() {
        return _remaining.size();
    }

    /**
     * Returns the number of individuals already departed
     *
     * @return The number of individuals departed
     */
    public int getDepartedCount() {
        return _departed.size();
    }

    /**
     * Returns the maximum number of individuals departed at any given time
     * during the current simulation
     *
     * @return The max number of departed individuals
     */
    public int getMaxDepartedCount() {
        return _maxDepartedCount;
    }

    /**
     * Returns the total number of individuals
     *
     * @return The total number of individuals
     */
    public int getIndividualCount() {
        return _individualCount;
    }

    /**
     * Returns an iterator over the individuals remaining in the group
     *
     * @return An iterator over the remaining individuals
     */
    public Iterator<SpatialIndividual> getRemainingIterator() {
        return _remaining.values().iterator();
    }

    /**
     * Returns an iterator over the individuals departed from the group
     *
     * @return An iterator over the departed individuals
     */
    public Iterator<SpatialIndividual> getDepartedIterator() {
        return _departed.values().iterator();
    }

    /**
     * Returns an iterator of the eligible initiators from the group.  An
     * eligible initiator is an individual that has no neighbors that are
     * initiators or following an initiator.
     *
     * @return An iterator of the eligible initiators
     */
    public Iterator<SpatialIndividual> getEligibleInitiatorsIterator() {
        if (_LOG.isDebugEnabled()) {
            StringBuilder builder = new StringBuilder();
            Iterator<Object> initIter = _eligibleInitiators.keySet().iterator();
            while (initIter.hasNext()) {
                builder.append(initIter.next());
                builder.append(" ");
            }

            if (_LOG.isDebugEnabled()) {
                _LOG.debug("Eligible initiators [" + _eligibleInitiators.size() + "]: " + builder.toString());
            }
        }
        return _eligibleInitiators.values().iterator();
    }

    /**
     * Returns an iterator for the potential followers for the specified
     * initiator
     *
     * @param initiator
     * @return An iterator of the potential followers
     */
    public Iterator<SpatialIndividual> getPotentialFollowersIterator(SpatialIndividual initiator) {
        List<SpatialIndividual> potentialFollowers = findPotentialFollowers(initiator);

        return potentialFollowers.iterator();
    }

    /**
     * Returns a flag denoting whether or not the initiator has any remaining
     * potential followers
     *
     * @param initiator
     * @return
     */
    public boolean hasPotentialFollowers(SpatialIndividual initiator) {
        List<SpatialIndividual> potentialFollowers = findPotentialFollowers(initiator);
        return (potentialFollowers.size() > 0);
    }

    /**
     * Returns a flag denoting whether or not the individual has departed
     *
     * @param indID
     * @return <code>true</code> if the indivdiual has departed, otherwise,
     * <code>false</code>
     */
    public boolean hasDeparted(Object indID) {
        return _departed.containsKey(indID);
    }

    /**
     * Returns the size of the group associated with the current initiator
     *
     * @param initiator The current initiator
     * @return The size of the group
     */
    public int getInitiatorsGroupSize(SpatialIndividual initiator) {
        int groupSize = 0;

        if (_useNearestNeighborGroupSize) {
            groupSize = initiator.getNearestNeighborCount();
        } else {
            groupSize = _individualCount;
        }

        return groupSize;
    }

    /**
     * Signals that the specified individual has initiated a group movement
     *
     * @param initiator
     */
    public void initiate(SpatialIndividual initiator) {
        // Tell the individual that it is initiating action
        initiator.initiateMovement(this);

        // Process the initiator
        depart(initiator);
    }

    /**
     * Signals that the specified individual has chosen to follow the specified
     * leader
     *
     * @param initiator
     * @param follower
     */
    public void follow(SpatialIndividual leader, SpatialIndividual follower) {
        // Tell the follower that it is following the leader
        follower.follow(leader);

        // Process the follower
        depart(follower);
    }

    /**
     * Signals that the specified individual has canceled a group movement
     *
     * @param individual
     */
    public void cancelInitiation(SpatialIndividual individual) {
        if (_LOG.isDebugEnabled()) {
            _LOG.debug("Before cancel [" + individual.getID() + "]: eligibleInitiators=["
                    + _eligibleInitiators.size() + "] remaining=[" + _remaining.size() + "] totalFollowers=["
                    + individual.getTotalFollowerCount() + "]");
        }

        // Send it a signal so it can log some information
        individual.signalInitiationFailure(this);

        // We need to maintain a list of all the affected individuals
        List<SpatialIndividual> affected = new LinkedList<SpatialIndividual>();

        // Build the list starting with the initiator itself
        Queue<SpatialIndividual> indsToProcess = new LinkedList<SpatialIndividual>();
        indsToProcess.add(individual);
        while (!indsToProcess.isEmpty()) {
            // Get the first in the queue
            SpatialIndividual current = indsToProcess.remove();

            //            _LOG.debug( "Processing ["
            //                    + current.getID()
            //                    + "]" );

            // Add it to the list
            affected.add(current);

            // Add it's immediate followers to the queue for processing
            Iterator<Neighbor> followerIter = current.getFollowers().iterator();
            while (followerIter.hasNext()) {
                indsToProcess.add(followerIter.next().getIndividual());
            }
        }

        /* Iterate through all the affected individuals to change them from
         * departed to remaining and tell them to cancel */
        Iterator<SpatialIndividual> affectedIter = affected.iterator();
        while (affectedIter.hasNext()) {
            SpatialIndividual current = affectedIter.next();

            //            _LOG.debug( "Processing affected ["
            //                    + current.getID()
            //                    + "]" );

            // Remove the individual from the departed group
            _departed.remove(current.getID());

            // Add it to the remaining group
            _remaining.put(current.getID(), current);

            // Tell it to cancel
            current.cancel();

        }

        /* Iterate through the list again to see if they are eligible
         * initiators.  We couldn't do it during the last pass through since
         * we hadn't cleaned up all the groups yet. */
        //        affectedIter = affected.iterator();
        affectedIter = _remaining.values().iterator();
        while (affectedIter.hasNext()) {
            SpatialIndividual current = affectedIter.next();

            // Are any of the individual's neighbors initiators or followers?
            boolean eligible = true;
            Iterator<Neighbor> neighborIter = current.getNearestNeighbors().iterator();
            while (eligible && neighborIter.hasNext()) {
                // Can tell by looking at the group ID
                Neighbor neighbor = neighborIter.next();
                if (null != neighbor.getIndividual().getGroupID()) {
                    /* The neighbor belongs to a group, the individual is NOT
                     * eligible. */
                    eligible = false;
                }
            }

            // Is the individual eligible?
            if (eligible) {
                // Yup
                _eligibleInitiators.put(current.getID(), current);
            } else {
                // Nope, tell them who their first mover was
                // Iterate through the list of departed individuals and
                // find the first nearest neighbor
                Iterator<SpatialIndividual> departedIter = _departed.values().iterator();
                while (departedIter.hasNext()) {
                    SpatialIndividual departedInd = departedIter.next();
                    if (current.isNearestNeighbor(departedInd)) {
                        current.observeFirstMover(departedInd);
                        break;
                    }
                }
            }

            /* Check all the individuals not yet departed to see if they
             * observed this individual as a first mover.  If so, reset their
             * first mover if no other neighbors have departed or if another
             * has departed, set it to that neighbor */
            Iterator<SpatialIndividual> remainingIter = _remaining.values().iterator();
            while (remainingIter.hasNext()) {
                SpatialIndividual currentRemaining = remainingIter.next();
                Neighbor firstMover = currentRemaining.getFirstMover();
                if ((null != firstMover) && (firstMover.getIndividual().getID().equals(current.getID()))) {
                    // Reset the first mover
                    currentRemaining.resetFirstMover();

                    // See if they now have another first mover
                    Iterator<SpatialIndividual> departedIter = _departed.values().iterator();
                    while (departedIter.hasNext()) {
                        SpatialIndividual departedInd = departedIter.next();
                        if (currentRemaining.isNearestNeighbor(departedInd)) {
                            currentRemaining.observeFirstMover(departedInd);
                            break;
                        }
                    }
                }
            }
        }

        _LOG.debug("After cancel: eligibleInitiators=[" + _eligibleInitiators.size() + "] remaining=["
                + _remaining.size() + "]");
    }

    /**
     * Returns the props for this object
     *
     * @return The props
     */
    public Properties getProps() {
        return _props;
    }

    /**
     * Returns the event time calculator for this simulation
     *
     * @return The event time calculator
     */
    public EventTimeCalculator getEventTimeCalculator() {
        return _eventTimeCalc;
    }

    /**
     * Returns the personalityCalc for this object
     *
     * @return The personalityCalc
     */
    public PersonalityCalculator getPersonalityCalc() {
        return _personalityCalc;
    }

    /**
     * Returns the simIndex for this object
     *
     * @return The simIndex
     */
    public long getSimIndex() {
        return _simIndex;
    }

    /**
     * Sets the simIndex for this object.
     *
     * @param simIndex The specified simIndex
     */
    public void setSimIndex(long simIndex) {
        _simIndex = simIndex;
    }

    /**
     * Returns the simulationCount for this object
     *
     * @return The simulationCount
     */
    public int getSimulationCount() {
        return _simulationCount;
    }

    /**
     * Returns the nearestNeighborCount for this object
     *
     * @return The nearestNeighborCount
     */
    public int getNearestNeighborCount() {
        return _nearestNeighborCount;
    }

    /**
     * Returns the nearestNeighborDistance for this object
     *
     * @return The nearestNeighborDistance
     */
    public float getNearestNeighborDistance() {
        return _nearestNeighborDistance;
    }

    /**
     * Returns all the individuals in the simulation
     *
     * @return All the individuals
     */
    public List<SpatialIndividual> getAllIndividuals() {
        return new ArrayList<SpatialIndividual>(_allIndividuals);
    }

    /**
     * Returns a flag denoting whether or not all the departed individuals
     * should be used in rate calculations.
     *
     * @return <code>true</code> if all the departed individuals should be used,
     * otherwise, <code>false</code>
     */
    public boolean useAllDepartedIndividuals() {
        return _useAllDeparted;
    }

    /**
     * Returns the random number generator
     *
     * @return The random number generator
     */
    public MersenneTwisterFast getRandom() {
        return _random;
    }

    /**
     * Returns a new unique ID
     *
     * @return The unique ID
     */
    public Object generateUniqueIndividualID() {
        return "Ind" + String.format("%05d", _indIDCounter++);
    }

    /**
     * Returns a new unique ID
     *
     * @return The unique ID
     */
    public Object generateUniqueGroupID() {
        return "Group" + String.format("%05d", _groupIDCounter++);
    }

    /**
     * Create the individuals for the simulation
     */
    private void createIndividuals() {
        _LOG.trace("Entering createIndividuals()");

        // Build all the individuals
        for (int i = 0; i < _individualCount; i++) {
            // Build the individual
            SpatialIndividual ind = _indBuilder.build(i);

            // Add it to the list
            _allIndividuals.add(ind);
        }

        // Have the individuals find their neighbors
        List<SpatialIndividual> lonelyInds = new LinkedList<SpatialIndividual>();
        Iterator<SpatialIndividual> indIter = _allIndividuals.iterator();
        while (indIter.hasNext()) {
            SpatialIndividual ind = indIter.next();
            ind.findNearestNeighbors(this);

            // Is it all alone
            if (0 == ind.getNearestNeighborCount()) {
                // Yup
                lonelyInds.add(ind);
                _LOG.warn("Ind [" + ind.getID() + "] has no neighbors");
            }
        }

        // If an individual has no neighbors, delete it
        Iterator<SpatialIndividual> lonelyIndIter = lonelyInds.iterator();
        while (lonelyIndIter.hasNext()) {
            SpatialIndividual lonely = lonelyIndIter.next();
            _LOG.warn("Removing lonely individual [" + lonely.getID() + "]");
            _allIndividuals.remove(lonely);
        }

        // Find out if any individuals have no mimicing neighbors
        indIter = _allIndividuals.iterator();
        while (indIter.hasNext()) {
            SpatialIndividual ind = indIter.next();
            if (0 >= ind.getMimickingNeighborCount()) {
                _LOG.warn("Individual [" + ind.getID() + "] has no mimicing neighbors");
            }
        }

        _LOG.trace("Leaving createIndividuals()");
    }

    /**
     * Process the individual as now departed
     *
     * @param ind
     */
    private void depart(SpatialIndividual ind) {
        Object leaderID = null;
        if (null != ind.getFirstMover()) {
            leaderID = ind.getFirstMover().getIndividual().getID();
        }

        if (_LOG.isDebugEnabled()) {
            _LOG.debug("Depart ind=[" + ind.getID() + "]");
            _LOG.debug("Following [" + leaderID + "]");
            _LOG.debug("Checking [" + _eligibleInitiators.size() + "] possible initiators");
        }

        // Remove the individual from the remaining and eligible initiators groups
        _remaining.remove(ind.getID());
        _eligibleInitiators.remove(ind.getID());

        // Add it to the departed group
        _departed.put(ind.getID(), ind);

        // Get all the possible initiators for whom this individual is a neighbor
        List<SpatialIndividual> inds = new LinkedList<SpatialIndividual>();
        Iterator<SpatialIndividual> eligibleIter = _eligibleInitiators.values().iterator();
        while (eligibleIter.hasNext()) {
            SpatialIndividual current = eligibleIter.next();

            //            _LOG.debug( "  Checking ind=["
            //                    + current.getID()
            //                    + "]" );

            // Is the original individual a nearest neighbor?
            if (ind.isMimicingNeighbor(current)) {
                //                _LOG.debug( "[" + current.getID() + "] is a mimicking neighbor" );

                // Yup, add them to the list
                inds.add(current);

                // Also, tell them this is their immediate leader
                current.observeFirstMover(ind);
            }
        }

        // Remove all of these individuals from the eligible initiators group
        Iterator<SpatialIndividual> indIter = inds.iterator();
        while (indIter.hasNext()) {
            Object id = indIter.next().getID();
            _eligibleInitiators.remove(id);
            _LOG.debug("Removing [" + id + "] from eligible initiators group");
        }

        // Update the max number of departed individuals (if needed)
        if (_maxDepartedCount < _departed.size()) {
            _maxDepartedCount = _departed.size();
        }
    }

    /**
     * TODO Method description
     *
     * @param initiator
     * @return
     */
    private List<SpatialIndividual> findPotentialFollowers(SpatialIndividual initiator) {
        List<SpatialIndividual> potentialFollowers = new LinkedList<SpatialIndividual>();

        // Get the initiator's group ID
        Object initiatorGroupID = initiator.getGroupID();

        StringBuilder builder = new StringBuilder();

        // Iterate through all the remaining individuals
        Iterator<SpatialIndividual> remainingIter = _remaining.values().iterator();
        while (remainingIter.hasNext()) {
            SpatialIndividual currentRemaining = remainingIter.next();

            // Does any of this individual's neighbors belong to the same group?
            boolean foundSameGroup = false;
            Iterator<Neighbor> neighborIter = currentRemaining.getNearestNeighbors().iterator();
            while (!foundSameGroup && neighborIter.hasNext()) {
                Neighbor neighbor = neighborIter.next();

                // Do the group ID's match?
                if (initiatorGroupID.equals(neighbor.getIndividual().getGroupID())) {
                    // Yup, note it
                    foundSameGroup = true;
                }
            }

            if (foundSameGroup) {
                // The current individual is a potential follower
                potentialFollowers.add(currentRemaining);
                builder.append(currentRemaining.getID());
                builder.append(" ");
            }
        }

        //        if( _LOG.isDebugEnabled() )
        //        {
        //            _LOG.debug( "Initiator=["
        //                    + initiator.getID()
        //                    + "] has ["
        //                    + potentialFollowers.size()
        //                    + "] potential followers: "
        //                    + builder.toString() );
        //        }

        return potentialFollowers;
    }

}