package eva2.optimization.strategies;

import eva2.optimization.individuals.AbstractEAIndividual;
import eva2.optimization.individuals.ESIndividualDoubleData;
import eva2.optimization.individuals.IndividualInterface;
import eva2.optimization.individuals.InterfaceDataTypeDouble;
import eva2.optimization.operator.distancemetric.EuclideanMetric;
import eva2.optimization.population.Population;
import eva2.problems.AbstractOptimizationProblem;
import eva2.problems.InterfaceOptimizationProblem;
import eva2.tools.math.Mathematics;

import java.util.Vector;


/**
 * This implements a particle swarm optimizer which is used by the NichePSO and ANPSO
 * to represent and train the mainswarm or any subswarm.
 * This is done by extending the standard particle swarm optimizer so that
 * - additional member variables (characterizing and parameterizing the subswarm) are available
 * - additional information is added to the particles during an optimization loop
 * - additional functions to add, remove and reinitialize particles are available
 */
public class ParticleSubSwarmOptimization extends ParticleSwarmOptimizationGCPSO {

    protected double maxAllowedSwarmRadiusNormal; // maximal allowed swarmradius (relative to the search space)
    public static boolean hideFromGOE = true;   // dont want it to be available in the GUI
    protected boolean active;  // marks the swarm active or inactive

    protected double maxPosDist; // maximal possible distance in the search space, depends on problem -> initMaxPosDist()
    private int particleIndexCounter; // used to give each particle a unique index (for debbugging and plotting)
    private int fitnessArchiveSize = 15; // maximal number of fitnessvalues remembered from former iterations

    /**
     * @tested ps
     * ctor
     */
    public ParticleSubSwarmOptimization() {
        updateMaxPosDist();
        this.maxAllowedSwarmRadiusNormal = 0.1; // set similar to "particle swarms for multimodal optimization" by Oezcan and Yilmaz
        this.active = true;
        particleIndexCounter = getPopulation().size(); // newly added particles will start at this index
//		setInitialVelocity(1.);
    }

    /**
     * @tested cpyctor
     */
    public ParticleSubSwarmOptimization(ParticleSubSwarmOptimization a) {
        super(a);
        if (a.bestIndividual != null) {
            bestIndividual = (AbstractEAIndividual) a.bestIndividual.clone();
        }

        maxAllowedSwarmRadiusNormal = a.maxAllowedSwarmRadiusNormal;
        active = a.active;
        maxPosDist = a.maxPosDist;
        particleIndexCounter = a.particleIndexCounter;
//		inertnessAging = (InterfaceParameterAging)a.inertnessAging.clone();
        fitnessArchiveSize = a.fitnessArchiveSize;
    }

    /**
     * @tested (non-Javadoc) @see javaeva.server.oa.go.Strategies.ParticleSwarmOptimization#clone()
     */
    @Override
    public Object clone() {
        return new ParticleSubSwarmOptimization(this);
    }


/**********************************************************************************************************************
 * inits
 */
    /**
     * @tested ps
     * (non-Javadoc) @see javaeva.server.oa.go.Strategies.ParticleSwarmOptimization#initialize()
     */
    @Override
    public void initialize() {
        super.initialize();

        initIndividuals();

        updateMBestIndividual();
        updateMaxPosDist();
        particleIndexCounter = getPopulation().size();
//		setInertnessOrChi(inertnessAging.getStartValue());
    }

    /**
     * @tested ps
     * (non-Javadoc) @see javaeva.server.oa.go.Strategies.ParticleSwarmOptimization#initializeByPopulation(javaeva.server.oa.go.Populations.Population, boolean)
     */
    @Override
    public void initializeByPopulation(Population pop, boolean reset) {
        super.initializeByPopulation(pop, reset);
        initIndividuals();

        updateMBestIndividual();
        updateMaxPosDist();
        particleIndexCounter = getPopulation().size();
    }

    /**
     * @tested junit&
     * adds a vector for fitnessvalues to all individuals in the swarm
     * and sets the current fitness as the first value
     */
    protected void initIndividuals() {
        for (int i = 0; i < population.size(); ++i) {
            AbstractEAIndividual indy = population.getEAIndividual(i);
            initSubSwarmDefaultsOf(indy);
        }
    }

    public static void initSubSwarmDefaultsOf(AbstractEAIndividual indy) {
        initFitnessArchiveOf(indy);
        initFitnessStdDevOf(indy);
        initPersonalBestOf(indy);
        initPBestImprInARowOf(indy);
    }

    public static void initFitnessArchiveOf(AbstractEAIndividual indy) {
        Vector<Double> vec = new Vector<>();
        double scalarFitness = sum(indy.getFitness()); // if multiobjective, use the sum of all fitnessvalues (dont use the norm because fitnessvalues may be negative)
        vec.add(scalarFitness);
        indy.putData(NichePSO.fitArchiveKey, vec);
    }

    /**
     * @tested emp
     * adds a std deviation value to an individual
     * and initially sets this value to infinity.
     */
    public static void initFitnessStdDevOf(AbstractEAIndividual indy) {
        // initialize stddev to inf, dont want immediate convergence...
        indy.putData(NichePSO.stdDevKey, Double.POSITIVE_INFINITY);
    }

    public static void initPBestImprInARowOf(AbstractEAIndividual indy) {
        indy.putData("PBestImprovementsInARow", 0);
    }

    /**
     * @tested junit
     * adds a representation of the personal best to an individual
     * and initially sets the current individual as its personal best.
     */
    public static void initPersonalBestOf(AbstractEAIndividual indy) {
        AbstractEAIndividual newpbest = (AbstractEAIndividual) indy.clone();
        newpbest.putData("PersonalBestKey", null);  // dont want to have a chain of pbests
        indy.putData("PersonalBestKey", newpbest);
    }

/**********************************************************************************************************************
 * Optimization
 */
    /**
     * @tested ps
     * (non-Javadoc) @see javaeva.server.oa.go.Strategies.ParticleSwarmOptimization#optimize()
     */
    @Override
    public void optimize() {
        super.optimize();
        updateFitnessArchives();
        updateFitnessStdDev();
        updatePersonalBest();
        updateMBestIndividual();
//		updateParameters();
    }

    public void reinitIndividuals(Vector<int[]> indicesToReinit) {
        for (int[] indices : indicesToReinit) {
            addNewParticlesToPopulation(indices);
        }
    }

    /**
     * Get the next set of indices increasing the internal particle counter.
     * Should only be called immediately before adding the new individuals.
     *
     * @param num
     * @return
     */
    private int[] getNextIndices(int num) {
        int[] indices = new int[num];
        for (int i = 0; i < num; ++i) {
            indices[i] = particleIndexCounter;
            ++particleIndexCounter;
        }
        return indices;
    }

    public void reinitIndividuals(int numIndies) {
        addNewParticlesToPopulation(getNextIndices(numIndies));
    }

    /**
     * *******************************************************************************************************************
     * updateTopology
     */

    protected AbstractEAIndividual getIndyByParticleIndexAndPopulation(Population pop, Integer index) {
        for (int i = 0; i < pop.size(); ++i) {
            AbstractEAIndividual indy = pop.getEAIndividual(i);
//    		Integer tmp = (Integer)indy.getData("particleIndex"); // CPU-Time Hotspot
//    		if (index.equals(tmp)) return indy;
            if (index == indy.getIndividualIndex()) {
                return indy;
            }
        }
        return null;
    }

    /* (non-Javadoc)
     * @see eva2.optimization.strategies.ParticleSwarmOptimization#addSortedIndizesTo(eva2.optimization.populations.Population)
     */
    @Override
    protected void addSortedIndicesTo(Object[] sortedPopulation, Population pop) {
        int origIndex;
        for (int i = 0; i < pop.size(); i++) {
            // cross-link the sorted list for faster access
            // (since NichePSO and ANPSO allow individuals
            // to leave and enter a population the index in the
            // population does not necessarily match the individual index)
            origIndex = ((AbstractEAIndividual) sortedPopulation[i]).getIndividualIndex();
            AbstractEAIndividual indy = getIndyByParticleIndexAndPopulation(pop, origIndex);
            indy.putData(sortedIndexKey, i);
        }
    }

/**********************************************************************************************************************
 * updates
 */
    /**
     * @tested ps
     * when particles enter or leave the population,
     * call this method to update the status of the population
     */
    public void populationSizeHasChanged() {
        updateMBestIndividual();
    }

    /**
     * @tested junit&
     * updates the bestIndividual member which represents the gbest individual
     * (i.e. the best position and fitness encountered by any particle of the swarm)
     * Beware: if a particle enters the swarm its knowledge about its pbest enters the swarm,
     * on the other side: if a particle leaves the swarm its knowledge leaves the swarm as well.
     */
    protected void updateMBestIndividual() {
        if (getPopulation().size() == 0) {
            return;
        }
        // First: set bestIndividual to any personal best position in the swarm  (-> bestindypbest).
        // This is necessary because the current bestIndividual my came from
        // an individual that left the swarm and would never be replaced if it
        // would dominate all remaining positions in the swarm.
        AbstractEAIndividual bestindy = getPopulation().getBestEAIndividual();
        AbstractEAIndividual bestindypbest = (AbstractEAIndividual) bestindy.getData("PersonalBestKey");
        bestIndividual = (AbstractEAIndividual) bestindypbest.clone();
        for (int i = 0; i < getPopulation().size(); ++i) {
            AbstractEAIndividual currentindy = getPopulation().getEAIndividual(i);
            AbstractEAIndividual currentindypbest = (AbstractEAIndividual) currentindy.getData("PersonalBestKey");
            if (currentindypbest.isDominating(bestIndividual)) {
                bestIndividual = (AbstractEAIndividual) currentindypbest.clone();
//				++gbestImprovmentsInARow;
            } //else gbestImprovmentsInARow = 0;
        }
    }

    /**
     * @tested junit, dbg
     * adds the current fitnessvalue to the fitnessarchive for every individual in the swarm.
     * Keeps the fitnessarchive at a limited size (lim+1).
     */
    public void updateFitnessArchives() {
        //int lim = 3; // maximal number of fitnessvalues remembered from former iterations
        for (int i = 0; i < population.size(); ++i) {
            AbstractEAIndividual indy = population.getEAIndividual(i);
            Vector<Double> fitArchive_old = (Vector<Double>) (indy.getData(NichePSO.fitArchiveKey));
            double scalarFitness = sum(indy.getFitness()); // if multiobjective, use the sum of all fitnessvalues (dont use the norm because fitnessvalues may be negative)
            Double fitness = scalarFitness;

            Vector<Double> fitArchive_new = new Vector<>();
            int end = fitArchive_old.size();
            int start = 0;
            if (end >= fitnessArchiveSize) {
                start = end - fitnessArchiveSize;
            }

            for (int j = start; j < end; ++j) {
                fitArchive_new.add(fitArchive_old.get(j));
            }
            fitArchive_new.add(fitness);
            indy.putData(NichePSO.fitArchiveKey, fitArchive_new);
        }
    }

    /**
     * @tested junit
     * sets the std dev value of all individuals in the swarm
     * to the std deviation over the last 3 fitness values
     */
    public void updateFitnessStdDev() {
        for (int i = 0; i < population.size(); ++i) {
            AbstractEAIndividual currentindy = population.getEAIndividual(i);
            Vector<Double> fitnessArchive = (Vector<Double>) (currentindy.getData(NichePSO.fitArchiveKey));
            // the stddev is computed over 3 values as suggested in
            // "a niching particle swarm optimizer" by Brits et al.
            double sd = stdDev(fitnessArchive, NichePSO.defaultFitStdDevHorizon);
            currentindy.putData(NichePSO.stdDevKey, sd);
        }
    }

    /**
     * @tested junit&, junit
     * update the personal best representation if the current individual is better than the pbest
     */
    public void updatePersonalBest() {
        for (int i = 0; i < population.size(); ++i) {
            AbstractEAIndividual currentindy = population.getEAIndividual(i);
            AbstractEAIndividual pbest = (AbstractEAIndividual) currentindy.getData("PersonalBestKey");
            if (currentindy.isDominating(pbest)) {
                initPersonalBestOf(currentindy);

                //PBestImprovementsInARow
                Integer counter = (Integer) currentindy.getData("PBestImprovementsInARow");
                counter = counter.intValue() + 1;
                currentindy.putData("PBestImprovementsInARow", counter);
            } else {
                initPBestImprInARowOf(currentindy);
            }
        }
    }

    /**
     * @tested junit ..
     * updates the member representing the maximal possible distance in the current searchspace
     */
    public void updateMaxPosDist() {
        //  compute the maximal possible distance in the search space:
        AbstractOptimizationProblem prob = (AbstractOptimizationProblem) optimizationProblem;
        // problem must have called initializeProblem, so that the template is set correctly. This shouls always be the case here...
        AbstractEAIndividual template = prob.getIndividualTemplate();
        if (template == null) {
            System.out.println("Problem does not implement getIndividualTemplate, updateMaxPosDist could not infer dimensions");
            return;
        }
        if (!(template instanceof ESIndividualDoubleData) && !(template instanceof InterfaceDataTypeDouble)) {
            System.out.println("Problem does not use ESIndividualDoubleData or InterfaceDataTypeDouble. UpdateMaxPosDist could not infer dimensions.");
            return;
        }
//		ESIndividualDoubleData min = (ESIndividualDoubleData)template.clone();
//		ESIndividualDoubleData max = (ESIndividualDoubleData)template.clone();

        double[][] range = null;
        if (template instanceof ESIndividualDoubleData) {
            range = ((ESIndividualDoubleData) template).getDoubleRange();
        } else {
            range = ((InterfaceDataTypeDouble) template).getDoubleRange();
        }
        double[] minValInDim = new double[range.length];
        double[] maxValInDim = new double[range.length];
        for (int i = 0; i < minValInDim.length; ++i) {
            minValInDim[i] = range[i][0]; // get lower boarder for dimension i
            maxValInDim[i] = range[i][1]; // get upper boarder for dimension i
        }
//		min.setDoubleGenotype(minValInDim); // set all dimensions to min
//		max.setDoubleGenotype(maxValInDim); // set all dimensions to max
        this.maxPosDist = Mathematics.euclideanDist(minValInDim, maxValInDim);
    }

//	/**
//	 * Parametervalues of the optimizer may change over time
//	 */
//	protected void updateParameters() {
//		setInertnessOrChi(inertnessAging.getNewParameterValue(getInertnessOrChi(), getPopulation().getGeneration()));
//	}
/**********************************************************************************************************************
 * dist, mean, stddev, sum
 */
    /**
     * @param indy1
     * @param indy2
     * @return
     * @tested junit
     * returns the euclidean distance in the search space between indy1 and indy2.
     */
    public double distance(AbstractEAIndividual indy1, AbstractEAIndividual indy2) {
        return EuclideanMetric.euclideanDistance(AbstractEAIndividual.getDoublePositionShallow(indy1),
                AbstractEAIndividual.getDoublePositionShallow(indy2));
    }

    /**
     * @param vec
     * @param range number of values (beginning at the end of vec!) considered to compute the mean
     * @return
     * @tested junit
     * returns the mean value for the provided data vec but only for a given number of values.
     * If range > vec.size() the mean is computed only for the available data.
     */
    protected static double mean(Vector<Double> vec, int range) {
        if (vec.size() < range) {
            range = vec.size();
        }
        double sum = 0;
        for (int i = vec.size() - range; i < vec.size(); ++i) {
            sum += vec.get(i);
        }
        return sum / range;
    }

    /**
     * @param vec   data
     * @param range number of values (beginning at the end of vec!) considered to compute the std deviation
     * @return
     * @tested junit
     * returns the std deviation for the provided data vec but only for a given number of values
     */
    public static double stdDev(Vector<Double> vec, int range) {
        double ssum = 0;
        if (vec.size() - range < 0 || range < 2) {
            return Double.POSITIVE_INFINITY;
        } // not enough values, dont risk early convergence
        double mean = mean(vec, range);
        for (int i = vec.size() - range; i < vec.size(); ++i) {
            ssum += Math.pow(vec.get(i) - mean, 2);
        }
        double result = Math.sqrt(ssum / (range - 1));
        return result;
    }

    private static double sum(double[] fitness) {
        double ret = 0.;
        for (double d : fitness) {
            ret += d;
        }
        return ret;
    }

    /**
     * @param normalisedRadius
     * @return
     * @tested ps
     * Interpretes the given maximal radius as normalised according to the current search space.
     * Values from [0,1], 1 means the radius can be as large as the maximal possible distance
     * (between any two points) in the search space.
     * Because the swarmradius and distances (e.g. in merging and absortion) are given in a standard euclidean metric,
     * this function converts the normalised distance into the standard euclidean distance.
     */
    public double interpreteAsNormalisedSwarmRadius(double normalisedRadius) {
        if (normalisedRadius > 1 || normalisedRadius < 0) {
            System.out.println("interpreteAsNormalisedSwarmRadius: Radius not normalised to [0,1]");
        }
        // compute standard euclidean radius from normalised radius:
        return normalisedRadius * maxPosDist;
    }

/**********************************************************************************************************************
 * addNewParticlesToPopulation ...
 */

    /**
     * @param particleIndices set of indices that should be used for the added particles, if null new indices are created
     * @tested junit ...
     * adds new particles to this swarm, rndly inited over the search space by the problem
     */
    private void addNewParticlesToPopulation(int[] particleIndices) {
        if (particleIndices == null) {
            throw new RuntimeException("Error, unable to use null index array (ParticleSubSwarmOptimization.addNewParticlesToPOpulation)");
        }

        Population tmp = new Population();
        tmp.setTargetSize(particleIndices.length);
        //////////////
        AbstractOptimizationProblem prob = (AbstractOptimizationProblem) optimizationProblem;
        AbstractEAIndividual template = prob.getIndividualTemplate(); // problem must be inited at this point
        AbstractEAIndividual tmpIndy;

        for (int i = 0; i < tmp.getTargetSize(); i++) {
            tmpIndy = (AbstractEAIndividual) template.clone();
            tmpIndy.initialize(prob);
            tmp.add(tmpIndy);
        }
        tmp.initialize();
        ///////////

        ParticleSubSwarmOptimization tmpopt = new ParticleSubSwarmOptimization();
        tmpopt.setProblem(this.optimizationProblem);
        tmpopt.evaluatePopulation(tmp);
        tmpopt.initializeByPopulation(tmp, false); // + size FCs

        if (particleIndices != null) { // use given indices
            for (int i = 0; i < tmpopt.getPopulation().size(); ++i) {
                AbstractEAIndividual indy = tmpopt.getPopulation().getEAIndividual(i);
                indy.setIndividualIndex(particleIndices[i]);//SetData("particleIndex", new Integer(particleIndices[i]));
                indy.putData("newParticleFlag", Boolean.TRUE); // for plotting
            }
        }

        addPopulation(tmpopt); //  add to the mainswarm (FCs will be considered)
        populationSizeHasChanged();
    }

/**********************************************************************************************************************
 * add and remove functions that keep the function calls of the population "accurate"
 * (here "accurate" applies to the situation where an added population is always deleted in the next step, like in merging...)
 */
    /**
     * @param pop
     * @tested nn
     * adds a population and its function calls to this.population
     */
    public void addPopulation(ParticleSubSwarmOptimization pop) {
        addPopulation(pop.getPopulation());
    }

    /**
     * @param pop
     * @tested junit& ..
     * adds a population and its function calls to this.population
     */
    public void addPopulation(Population pop) {
        population.addPopulation(pop);

        // dont peculate the function calls from the added population (which is going to be deleted in NichePSO)
        population.incrFunctionCallsBy(pop.getFunctionCalls());
    }

    /**
     * @param ind
     * @return
     * @tested nn
     * adss an inidividual
     */
    public boolean addIndividual(IndividualInterface ind) {
        // nothing to do regarding function calls
        // old calls were counted in old population new calls are now counted in this population
        return population.addIndividual(ind);
    }

    /**
     * @param o
     * @return
     * @tested nn
     */
    public boolean add(Object o) {
        return addIndividual((IndividualInterface) o);
    }

    /**
     * @param indy
     * @tested nn
     * adds indy to the swarm
     */
    public void add(AbstractEAIndividual indy) {
        addIndividual(indy);
    }

    /**
     * @param ind
     * @tested nn
     * removes an individual
     */
    public boolean removeSubIndividual(IndividualInterface ind) {
        return population.removeMember(ind);
    }

    public void removeSubPopulation(Population pop, boolean allowMissing) { // this is very slow...
        for (int i = 0; i < pop.size(); ++i) {
            AbstractEAIndividual indy = pop.getEAIndividual(i);
            if (!removeSubIndividual(indy)) {
                if (!allowMissing) {
                    throw new RuntimeException("Warning, assumed sub population was not contained (ParticleSubSwarmOptimization).");
                }
            }
        }
    }

	/*
    public void removeNIndividuals(int n) {
	}
	public void removeDoubleInstances() {
	}
	public void removeDoubleInstancesUsingFitness() {
	}
	public void removeIndexSwitched(int index) {
	}*/

/**********************************************************************************************************************
 * getter, setter
 */
    /**
     * @param problem
     * @tested ps
     * This method will set the problem that is to be optimized
     */
    @Override
    public void setProblem(InterfaceOptimizationProblem problem) {
        this.optimizationProblem = problem;
        updateMaxPosDist();
    }

    /**
     * @return the maximal euclidean distance between the swarms gbest and any other particle in the swarm.
     * @tested junit
     */
    public double getSwarmRadius() {
        if (getPopulation().size() == 0 || getPopulation().size() == 1) {
            return 0;
        }

        double max = Double.NEGATIVE_INFINITY;
        //PhenotypeMetric metric = new PhenotypeMetric();

        for (int i = 0; i < population.size(); ++i) {
            AbstractEAIndividual indy = population.getEAIndividual(i);
            //double dist = metric.distance(bestIndividual, indy);
            double sqrdDist = EuclideanMetric.squaredEuclideanDistance(AbstractEAIndividual.getDoublePositionShallow(bestIndividual),
                    AbstractEAIndividual.getDoublePositionShallow(indy));

            //dist = distance(bestIndividual, indy);
            if (sqrdDist > max) {
                max = sqrdDist;
            }
        }
        return Math.sqrt(max);
    }

    /**
     * @return
     * @tested ps
     * returns the maximal distance between the gbest position and any individual in the swarm
     * this distance is not allowed to exceed a given threshold
     */
    public double getBoundSwarmRadius() {
        // convert the normalised (i.e. relative) maxrad to a standard euclidean (i.e. absolute) maxrad
        double maxAllowedSwarmRadiusAbs = getMaxAllowedSwarmRadiusAbs();
        // only compare (absolute) euclidean distances
        return Math.min(getSwarmRadius(), maxAllowedSwarmRadiusAbs);
    }

    public double getMaxAllowedSwarmRadiusAbs() {
        // convert the normalised (i.e. relative) maxrad to a standard euclidean (i.e. absolute) maxrad
        return interpreteAsNormalisedSwarmRadius(maxAllowedSwarmRadiusNormal);
    }

    /**
     * @return the average distance a particle has to its neighbor
     */
    public double getAveDistToNeighbor() {
        Population pop = getPopulation();
        double sum = 0;
        for (int i = 0; i < pop.size(); ++i) {
            AbstractEAIndividual indy = pop.getEAIndividual(i);
            AbstractEAIndividual neigbor = getMemberNeighbor(indy);
            if (neigbor == null) {
                return -1;
            }
            sum += distance(indy, neigbor);
        }
        return sum / (double) pop.size();
    }

    /**
     * @return a measure for the diversity of the swarm
     */
    public double getEuclideanDiversity() {
        double meanDistanceFromGBestPos = 0;
        AbstractEAIndividual gbest = getGBestIndividual();
        for (int i = 0; i < getPopulation().size(); ++i) {
            AbstractEAIndividual indy = getPopulation().getEAIndividual(i);
            meanDistanceFromGBestPos += distance(gbest, indy);
        }
        meanDistanceFromGBestPos /= (double) getPopulation().size();
        return meanDistanceFromGBestPos;
    }

    /**
     * @param maxAllowedSwarmRadius value from the intervall [0,1]
     * @tested nn
     * binds the swarmradius to the given normalised value
     * (f.e. 1 means, that the swarmradius is allowed to take the maximal possible range in the search space)
     */
    public void SetMaxAllowedSwarmRadius(double maxAllowedSwarmRadius) {
        this.maxAllowedSwarmRadiusNormal = maxAllowedSwarmRadius;
    }

    /**
     * @return
     * @tested nn
     */
    public double getMaxAllowedSwarmRadius() {
        return this.maxAllowedSwarmRadiusNormal;
    }

    /**
     * @param active
     * @tested nn
     * marks the swarm as active or inactive
     */
    public void SetActive(boolean active) {
        this.active = active;
    }

    /**
     * @return
     * @tested nn
     */
    public boolean isActive() {
        return active;
    }

//	public InterfaceParameterAging getInertnessAging() {
//		return inertnessAging;
//	}
//
//	/**
//	 * sets a strategy that changes the value of the inertness parameter during an optimization run
//	 * @param inertnessAging
//	 */
//	public void setInertnessAging(InterfaceParameterAging inertnessAging) {
//		this.inertnessAging = inertnessAging;
//	}

    /**
     * @param indy particle should be from this swarm
     * @return null if there is no neighbor else neighbor
     * @tested junit
     * returns the particle with the minimal distance to indy
     */
    public AbstractEAIndividual getMemberNeighbor(AbstractEAIndividual indy) {
        if (getPopulation().size() == 0) {
            System.out.println("getNeighbor: swarm empty");
            return null;
        }
        // check if there is at least a second particle...
        if (getPopulation().size() == 1) {
            //System.out.println("getNeighbor: swarm too small, no neighbor available");
            //return (AbstractEAIndividual)indy.clone(); // would conflict with constant size for overall population...
            return null;
        }

        // get the neighbor...
        int index = -1;
        double mindist = Double.POSITIVE_INFINITY;
        boolean found = false;
        for (int i = 0; i < getPopulation().size(); ++i) {
            AbstractEAIndividual currentindy = getPopulation().getEAIndividual(i);
            if (indy.getIndyID() != currentindy.getIndyID()) { // dont compare particle to itself or a copy of itself
                double dist = distance(indy, currentindy);
                if (dist < mindist) {
                    mindist = dist;
                    index = i;
                }
            } else {
                found = true;
            }
        }

        if (!found) {
            System.err.println("getNeighbor: particle searching for neighbor is not part of the swarm");
        }
        return getPopulation().getEAIndividual(index);
    }

    public AbstractEAIndividual getGBestIndividual() {
        return bestIndividual;
    }

    /**
     * @param indyToExclude
     * @return particle with worst personal best position in the swarm. The given particle is excluded.
     */
    public AbstractEAIndividual getParticleWithWorstPBestButNot(AbstractEAIndividual indyToExclude) {
        Population pop = getPopulation();
        if (pop.size() < 2) {
            System.out.println("getParticleWithWorstPBestButNot: Population < 2 - returning null");
            return null;
        }
        AbstractEAIndividual indyWithWorstPBest = pop.getEAIndividual(0);
        if (indyWithWorstPBest == indyToExclude) {
            indyWithWorstPBest = pop.getEAIndividual(1);
        }
        AbstractEAIndividual worstPBest = (AbstractEAIndividual) indyWithWorstPBest.getData("PersonalBestKey");
        for (int i = 0; i < pop.size(); ++i) {
            AbstractEAIndividual currentindy = pop.getEAIndividual(i);
            AbstractEAIndividual currentpbest = (AbstractEAIndividual) currentindy.getData("PersonalBestKey");
            if (currentindy != indyToExclude && worstPBest.isDominating(currentpbest)) {
                indyWithWorstPBest = currentindy;
                worstPBest = currentpbest;
            }
        }
        return indyWithWorstPBest;
    }


    public int getFitnessArchiveSize() {
        return fitnessArchiveSize;
    }

    public void setFitnessArchiveSize(int fitnessArchiveSize) {
        this.fitnessArchiveSize = fitnessArchiveSize;
    }

    public String fitnessArchiveSizeTipText() {
        return "The number of fitness values stored per individual for deactivation strategies.";
    }
}
