package eva2.optimization.strategies;

import eva2.OptimizerFactory;
import eva2.gui.editor.GenericObjectEditor;
import eva2.optimization.enums.PSOTopology;
import eva2.optimization.individuals.AbstractEAIndividual;
import eva2.optimization.individuals.InterfaceDataTypeDouble;
import eva2.optimization.modules.OptimizationParameters;
import eva2.optimization.operator.nichepso.deactivation.StandardDeactivationStrategy;
import eva2.optimization.operator.paramcontrol.LinearParamAdaption;
import eva2.optimization.operator.paramcontrol.ParamAdaption;
import eva2.optimization.operator.terminators.EvaluationTerminator;
import eva2.optimization.operator.terminators.InterfaceTerminator;
import eva2.optimization.population.Population;
import eva2.optimization.population.PopulationInterface;
import eva2.problems.AbstractOptimizationProblem;
import eva2.problems.Interface2DBorderProblem;
import eva2.problems.InterfaceAdditionalPopulationInformer;
import eva2.tools.ToolBox;
import eva2.tools.chart2d.DPoint;
import eva2.tools.chart2d.DPointSet;

import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Vector;

/**
 * The Adaptive Niching PSO(ANPSO)[1] extends the particle swarm optimizer (PSO)
 * by Kennedy and Eberhart to locate multiple optima of a multimodal objective function.
 * The Algorithm uses similar concepts as the NichePSO such as a main swarm to explore
 * the search space and subswarms to refine and represent single niches.
 * The implemented version uses the "inertness weight" PSO to train the main swarm.
 * mainSwarmInertness sets the inertia weight omega and weights the particles tendency to follow its former movement.
 * This controls exploration (favored by larger values) against exploitation (favored by smaller values).
 * mainSwarmPhi1 sets Phi1 and weights the cognitive component.
 * The term corresponds to the particles tendency to return to its personal best position.
 * mainSwarmPhi2 sets Phi2 and weights the social component.
 * The term corresonds to the particles tendency to be attracted towards its neighborhood best position.
 * A small value for mainSwarmPhi2 reduces the influence of the neighborhood and each particle tend to
 * perfom a local search on its own. This results in a high exploration ability of the main swarm and
 * potentially leads to a good amount of identified optima.
 * Larger values for mainSwarmPhi2 on the other hand might help to concentrate on fewer optima with superior fitness values.
 * <p>
 * To avoid further parameters and the need to specify adequate values, which are often difficult to decide,
 * the following adaption mechanism is employed:
 * ANPSO adaptively determines a threshold parameter r during every generation by computing the average
 * distance a particle has to its neighbor. Subsequently, a so called s-matrix is calculated that keeps
 * track of how long any two particles are close (i.e. within r) to each other.
 * The s-matrix is used to build a niche graph that represents particles as vertices and connects all particles
 * that have been close to each other for at least two consecutive generations.
 * Particles that are connected in the niche graph form a subswarm.
 * The implemented version uses a strategy to deactivate subswarms when all containing particles converged on a solution.
 * Furthermore, different neighborhood topologies can be choosen for the main swarm.
 * In case the Multi-Swarm topology is used, the species radius is adaptively determined as well.
 * <p>
 * [1] S. Bird and X. Li:
 * Adaptively choosing niching parameters in a PSO.
 * In: GECCO '06: Proceedings of the 8th annual conference on Genetic and evolutionary computation,
 * Seiten 3--10, New York, NY, USA, 2006. ACM
 *
 * @author aschoff, mkron
 */
public class ANPSO extends NichePSO implements InterfaceAdditionalPopulationInformer, java.io.Serializable {


    /**
     * *******************************************************************************************************************
     * members
     */
    // for ANPSO it is necessary to keep an own archiv of inactive subswarms, because the standard set of
    // subswarms is completely renewed every iteration.
    public Vector<ParticleSubSwarmOptimization> inactiveSubSwarms = new Vector<>();

    // the s matrix keeps track of how long particles are close to each other
    int[][] s = new int[mainSwarmSize][mainSwarmSize];

    // the niche graph represents particles as vertices and connects all particles that
    // have been close to each other for at least two consecutive generations
    protected NicheGraph nicheGraph = new NicheGraph();

    // defines the minimal distance for neighboring particles at which they form a subswarm
    // (this is proposed by Bird and Lee for further investigation)
    protected double minimalR = 0;
    private double updateRadius = 0.;
    // maximum subswarm size
    private int maxInitialSubSwarmSize = 0;

    private int maxNeighborCntNicheGraph = 4; // maximum for neighborhood matrix
    private int minNeighborCntNicheGraph = 0; // minimum for neighborhood matrix
    private int neighborCntNicheGraphForEdge = 2; // number of steps req. to build an edge

    /**
     * *******************************************************************************************************************
     * ctors, clone
     */
    public ANPSO() {
//		NichePSO.stdNPSO(anpso, problem, randSeed, evalCnt);
//		NichePSO.stdNPSO((ANPSO)this, (AbstractOptimizationProblem)this.problem, 0, 1000);

        /////////// from NichePSO
//		super.initMainSwarm(); // not really necessary if initialize is called before optimization but this way initialize doesnt change the parameters of a newly constructed object
//		super.initSubswarmOptimizerTemplate();
//		setMergingStrategy(new StandardMergingStrategy(0.001));
//		setAbsorptionStrategy(new StandardAbsorptionStrategy());
//		setSubswarmCreationStrategy(new StandardSubswarmCreationStrategy(0.0001));
//		
//		setMaxAllowedSwarmRadius(0.0001); // formally limits the swarm radius of the subswarms

        // Parameter for the mainswarm
//		npso.getMainSwarm().setSpeedLimit(avgRange/2.);
//		npso.getMainSwarm().setCheckSpeedLimit(true);

        // parameter for the subswarms
//		System.out.println(BeanInspector.niceToString(getSubswarmOptimizerTemplate()));
//		getSubswarmOptimizerTemplate().setGcpso(true);
//		getSubswarmOptimizerTemplate().setRho(0.1); // on 2D Problems empirically better than default value 1
//		getSubswarmOptimizerTemplate().setAlgoType(new SelectedTag("Constriction"));
//		getSubswarmOptimizerTemplate().setConstriction(2.05, 2.05);
//		System.out.println(BeanInspector.niceToString(getSubswarmOptimizerTemplate()));

        ///////////end from NichePSO
        getMainSwarm().setPhi1(2.05);
        getMainSwarm().setPhi2(2.05);
        getMainSwarm().setInertnessOrChi(0.7298437881283576);
        getMainSwarm().setAlgoType(ParticleSwarmOptimization.PSOType.Constriction);
        setMainSwarmAlgoType(ParticleSwarmOptimization.PSOType.Constriction); // constriction
        setMaxInitialSubSwarmSize(0); // deactivate early reinits
        setMainSwarmTopology(PSOTopology.grid);
        setMainSwarmTopologyRange(1);
        setDeactivationStrategy(new StandardDeactivationStrategy(0.000001, 8));
        setMainSwarmSize(100);
    }

	/*public ANPSO(){
        // Parameter for the mainswarm
//		//Earlier standard settings: 
//		this.setMainSwarmAlgoType(getMainSwarm().getAlgoType().setSelectedTag("Inertness"));
//		this.mainSwarmPhi1 = 1.2;
//		this.mainSwarmPhi2 = 0.6; // ANPSO uses communication in the main swarm 
//		this.mainSwarmTopology = PSOTopologyEnum.multiSwarm; //"Multi-Swarm" favors the formation of groups in the main swarm
//		this.mainSwarmTopologyRange = 2; // range for topologies like random, grid etc. (does not affect "Multi-Swarm")
//		this.setMainSwarmInertness(new NoParameterAging(0.73)); 
		this.setMainSwarmPhi1(2.05);
		this.setMainSwarmPhi2(2.05);
		this.setMainSwarmInertness(new NoParameterAging(0.7298437881283576));
		this.setMainSwarmAlgoType(getMainSwarm().getAlgoType().setSelectedTag("Constriction")); // constriction
		this.setMaxInitialSubSwarmSize(0); // deactivate early reinits
		this.setMainSwarmTopology(PSOTopologyEnum.grid);
		this.setMainSwarmTopologyRange(1); 
		this.setDeactivationStrategy(new StandardDeactivationStrategy(0.000001, 8));

		hideHideable();
		initMainSwarm();
	}*/

    public ANPSO(int mainSwarmSize, double phi1, double phi2, PSOTopology mainSwarmTopo, int mainSwarmTopoRange, int maxInitialSubSwarmSize) {
        this();
        setMainSwarmSize(mainSwarmSize);
        getMainSwarm().setPhi1(phi1);
        getMainSwarm().setPhi2(phi2);
//		setMainSwarmPhi1(phi1);
//		setMainSwarmPhi2(phi2);
        setMainSwarmTopologyRange(mainSwarmTopoRange);
        setMainSwarmTopology(mainSwarmTopo);
        setMaxInitialSubSwarmSize(maxInitialSubSwarmSize);
    }

    /**
     * Take care that all properties which may be hidden (and currently are) send a "hide" message to the Java Bean properties.
     * This is called by PropertySheetPanel in use with the GenericObjectEditor.
     */
    @Override
    public void hideHideable() {
        // hide the following unused properties from the GUI
        GenericObjectEditor.setHideProperty(getClass(), "subswarmCreationStrategy", true);
        GenericObjectEditor.setHideProperty(getClass(), "mergingStrategy", true);
        GenericObjectEditor.setHideProperty(getClass(), "absorptionStrategy", true);
        GenericObjectEditor.setHideProperty(getClass(), "maxAllowedSwarmRadius", true);
        GenericObjectEditor.setHideProperty(getClass(), "mainSwarmTopologyRange", mainSwarmTopology == PSOTopology.multiSwarm); // "Multi-Swarm" has no topologyRange

        // population size is set via setMainSwarmSize
        GenericObjectEditor.setHideProperty(getClass(), "population", true);
//		setGOEShowProperties(getClass()); 
    }

    public ANPSO(ANPSO o) {
        super(o);
        this.inactiveSubSwarms = (Vector<ParticleSubSwarmOptimization>) o.inactiveSubSwarms.clone();
        this.s = new int[mainSwarmSize][mainSwarmSize];
        for (int i = 0; i < s.length; ++i) {
            System.arraycopy(o.s[i], 0, s[i], 0, s[i].length);
        }

        this.nicheGraph = (NicheGraph) nicheGraph.clone();
        this.minimalR = o.minimalR;
        this.minNeighborCntNicheGraph = o.minNeighborCntNicheGraph;
        this.maxNeighborCntNicheGraph = o.maxNeighborCntNicheGraph;
        this.neighborCntNicheGraphForEdge = o.neighborCntNicheGraphForEdge;
    }

    @Override
    public Object clone() {
        return new ANPSO(this);
    }

    /**
     * *******************************************************************************************************************
     * inits
     */
    @Override
    public void initialize() { //  MOE: wird vor Optimierung / n�chstem multirun 1x aufgerufen
        super.initialize();
        initMainSwarm();
        initSTo(0);
        initNicheGraph();

        inactiveSubSwarms = new Vector<>(); // dont want to use subswarms from old optimization run (especially not in multiruns)...
    }

    /**
     * resets all entries of the s matrix that correspond to any
     * particle in the given subswarm
     *
     * @param subswarm
     */
    private void resetSMatrixEntriesFor(ParticleSubSwarmOptimization subswarm) {
        Population pop = subswarm.getPopulation();
        for (int i = 0; i < pop.size(); ++i) {
            //Integer index = pop.getEAIndividual(i).getIndividualIndex();(Integer)pop.getEAIndividual(i).getData("particleIndex");
            resetSMatrixForIndex(pop.getEAIndividual(i).getIndividualIndex(), 0);
        }
    }

    /**
     * sets the given row and column to the given value
     * (i.e. all entries corresponding to a specific particle)
     *
     * @param index
     * @param val
     */
    private void resetSMatrixForIndex(int index, int val) {
        for (int i = 0; i < s.length; ++i) {
            for (int j = 0; j < s[i].length; ++j) {
                if (i == index || j == index) {
                    s[i][j] = val;
                }
            }
        }
    }

    /**
     * sets every entry of the s matrix to the given value
     *
     * @param val
     */
    private void initSTo(int val) {
        for (int i = 0; i < s.length; ++i) {
            for (int j = 0; j < s[i].length; ++j) {
                s[i][j] = val;
            }
        }
    }

    /**
     * this inits the "niche graph".
     * Every particle is represented as a vertex in the "niche graph".
     */
    private void initNicheGraph() {
        nicheGraph = new NicheGraph();
        Population activePop = getActivePopulation();
        for (int i = 0; i < activePop.size(); ++i) {
            //Integer index = activePop.getEAIndividual(i).getParticleIndex();//(Integer)activePop.getEAIndividual(i).getData("particleIndex");
            String vertex = "" + activePop.getEAIndividual(i).getIndividualIndex();
            nicheGraph.addVertex(vertex);
        }
    }

/**********************************************************************************************************************
 * ANPSO core	

 //	/**
 //	 * @param pop
 //	 * @param normalized use a normalized metric to compute distances?
 //	 * @return ave distance to neighbor in the given population
 //	 */
//	public double getAveDistToClosestNeighbor(Population pop, boolean normalized){
//		PhenotypeMetric metric = new PhenotypeMetric();
////		ArrayList<Double> distances = new ArrayList<Double>(pop.size());
//		double sum = 0;
//		double d=0;
//		for (int i = 0; i < pop.size(); ++i){
//			AbstractEAIndividual neighbor, indy = pop.getEAIndividual(i);
//			int neighborIndex = pop.getNeighborIndex(indy);
//			if (neighborIndex >= 0) neighbor = pop.getEAIndividual(neighborIndex);
//			else return -1;
//			if (normalized){
//				d = metric.distance(indy, neighbor);
//			} else { 
//				d = PhenotypeMetric.euclidianDistance(AbstractEAIndividual.getDoublePosition(indy),
//						AbstractEAIndividual.getDoublePosition(neighbor));
//			}
////			distances.add(d);
//			sum += d;
//		}
//		double avg = sum/(double)pop.size();
////		if (normalized && (pop.getGeneration()==1)) {
////			double var = 0;
////			for (int i=0; i<distances.size(); i++) {
////				var += Math.pow(distances.get(i)-avg, 2);
////			}
////			System.out.println("gen " + pop.getGeneration() + " dim " + ((AbstractProblemDouble)problem).getProblemDimension() +" avg " + avg + " variance " + var);
////		}
//		return avg;
//	}

//	
//	/**
//	 * @param pop
//	 * @param indy
//	 * @return closest neighbor (euclidian measure) of the given individual in the given population 
//	 */
//	private AbstractEAIndividual getNeighbor(Population pop, AbstractEAIndividual indy) {
//		if (pop.size() < 2){
//			System.out.println("getNeighbor: swarm empty or indy only particle");
//			return null;
//		}
//
//		// get the neighbor...
//		int index = -1;
//		double mindist = Double.POSITIVE_INFINITY;
//
//		for (int i = 0; i < pop.size(); ++i){ 
//			AbstractEAIndividual currentindy = pop.getEAIndividual(i);
//			if (!indy.equals(currentindy)){ // dont compare particle to itself or a copy of itself
//				double dist = getMainSwarm().distance(indy,currentindy);
//				if (dist  < mindist){ 
//					mindist = dist;
//					index = i;
//				}
//			}
//		}
//		if (index == -1){
//			System.out.println("getNeighbor: all individuals in population are equal !?");
//			return null;
//		}
//		return pop.getEAIndividual(index);
//	}

    /**
     * Builds the s matrix and the niche graph.
     * The s matrix keeps track of the number of generations
     * each particle has been close to every other particle.
     * The niche graph connects particles that have been close
     * for at least two consecutive generations.
     */
    public void updateSMatrixAndNicheGraph() {
        // initialize the niche graph (all particles as vertices, no edges):
        initNicheGraph();

        // compute population statistic for parameter r:
        Population metapop = getActivePopulation();
        if (metapop.size() < 2) {
            // this might happen if all but a single individual are scheduled to be reinitialized after species convergence
//			System.err.println("Warning, active population of size " + metapop.size() +", radius will not be updated...");
        } else {
            updateRadius = metapop.getAvgDistToClosestNeighbor(false, false)[0];
        } // use all particles not mainswarm particles only...
        if (isVerbose()) {
            System.out.print(" radius is " + updateRadius);
        }

        // build the s matrix and add edges to the niche graph:
        // get particles i and j (unique in all swarms)...
        AbstractEAIndividual[] sortedPop = sortActivePopByParticleIndex(); // sort once to reduce cpu-time spend for accessing members...
//		System.out.println("metapop size is " + metapop.size());
        for (int i = 0; i < s.length - 1; ++i) {
            AbstractEAIndividual indy_i = sortedPop[i];
            if (indy_i == null) {
                continue;
            } // may happen is some sub swarm was deactivated and the indies are scheduled for reinit
            for (int j = i + 1; j < s[i].length; ++j) {
                // call hotspot
                AbstractEAIndividual indy_j = sortedPop[j];
                if (indy_j == null) {
                    continue;
                } // may happen is some sub swarm was deactivated and the indies are scheduled for reinit
                if (indy_i == null || indy_j == null) {
                    System.out.println("updateSMatrixAndNicheGraph: indices problem");
                }

                // ...check if they are "close to each other"
                double dist = getMainSwarm().distance(indy_i, indy_j);
                if (dist < updateRadius || dist < minimalR) {
                    // increment entry that counts the number of generations
                    // i and j have been close to each other
                    ++s[i][j];

                    // values must not exceed 4 to allow a particle to leave a niche
                    // in case it is further than r for two consecutive steps
                    if (s[i][j] > maxNeighborCntNicheGraph) {
                        s[i][j] = maxNeighborCntNicheGraph;
                    }

                    // if i and j had been close to each other for at least two generations
                    // they are connected by an edge
                    if (s[i][j] >= neighborCntNicheGraphForEdge) {
                        String pi = "" + i;
                        String pj = "" + j;
                        nicheGraph.addEdge(pi, pj);
                    }
                } else {
                    // decrement entry that counts the number of generations
                    // i and j have been close to each other
                    --s[i][j];
                    if (s[i][j] < minNeighborCntNicheGraph) {
                        s[i][j] = minNeighborCntNicheGraph;
                    }
                }
            }
        }
    }

    /**
     * The returned array may contain null entries (for indies scheduled for reinitialization).
     *
     * @return an array of references sorted according to the particle indices
     *         (i.e. returnedArray[i] = individual with individualIndex i)
     */
    protected AbstractEAIndividual[] sortActivePopByParticleIndex() {
        Population pop = getActivePopulation();
        AbstractEAIndividual[] sorted;
        if (pop.size() < mainSwarmSize) {
            int reinitSize = 0;
            if (indicesToReinit != null) {
                for (int i = 0; i < indicesToReinit.size(); i++) {
                    reinitSize += indicesToReinit.get(i).length;
                }
            }
            if (pop.size() + reinitSize == mainSwarmSize) {  // good case, extend pop size; null entries are to be tolerated.
                sorted = new AbstractEAIndividual[pop.size() + reinitSize];
            } else {
                throw new RuntimeException("Error, invalid size of active population (ANPSO.sortActivePopByParticleIndex()");
            }
        } else {
            sorted = new AbstractEAIndividual[pop.size()];
        }
        for (int i = 0; i < pop.size(); ++i) {
            AbstractEAIndividual indy = pop.getEAIndividual(i);
            if (sorted[indy.getIndividualIndex()] != null) {
                System.err.println("error in sortByParticleIndex!");
            }
            if (sorted[indy.getIndividualIndex()] != null) {
                throw new RuntimeException("Error, inconsistency in ANPSO! (index wrong)");
            }
            sorted[indy.getIndividualIndex()] = indy;
        }
        return sorted;
    }


    /**
     * uses the sets of particles as different subswarms and removes the particles from the mainswarm
     *
     * @param setOfSubswarms
     */
    public void useAsSubSwarms(Vector<Population> setOfSubswarms) {
        Vector<ParticleSubSwarmOptimization> newSubSwarms = new Vector<>();

        for (int i = 0; i < setOfSubswarms.size(); ++i) {
            Population pop = setOfSubswarms.get(i);
            ParticleSubSwarmOptimization subswarm = getNewSubSwarmOptimizer();
            subswarm.getPopulation().clear();
            subswarm.getPopulation().addAll(pop);
            subswarm.populationSizeHasChanged();
            newSubSwarms.add(subswarm);
            getMainSwarm().removeSubPopulation(pop, true); // the new subswarm can also come from an earlier subswarm
            getMainSwarm().populationSizeHasChanged();
        }
        if (isVerbose()) {
            System.out.println();
            for (int i = 0; i < newSubSwarms.size(); i++) {
                System.out.println("Swarm " + i + " (" + newSubSwarms.get(i).getPopulation().size() + "), best " + newSubSwarms.get(i).getBestIndividual());
            }
        }
        // add the function calls from the subswarms of the last iteration to the mainswarm before forgetting them
        // the function calls from the inactivated subswarms are transfered to the mainswarm as well
        // -> do not count them in getPopulation a second time
        int calls = 0;
        for (int i = 0; i < getSubSwarms().size(); ++i) {
            ParticleSubSwarmOptimization subswarm = getSubSwarms().get(i);
            calls += subswarm.getPopulation().getFunctionCalls();
        }
        getMainSwarm().getPopulation().incrFunctionCallsBy(calls);

        this.setSubSwarms(newSubSwarms);
    }

    /**
     * uses the population as the mainswarm
     *
     * @param pop
     */
    public void useAsMainSwarm(Population pop) {
        int generations = getMainSwarm().getPopulation().getGeneration();
        int calls = getMainSwarm().getPopulation().getFunctionCalls();
        getMainSwarm().setPopulation(pop);
        getMainSwarm().populationSizeHasChanged();
        getMainSwarm().getPopulation().setGeneration(generations);
        getMainSwarm().getPopulation().setFunctionCalls(calls);
    }

    /**
     * creates subswarms from all particles that correspond to connected vertices in the niche graph.
     * Particles corresponding to unconnected vertices belong to the mainswarm.
     */
    public void createSubswarmsFromNicheGraph() {
        // get all sets of vertices that are connected in the niche graph...
        //(subSwarms = new Vector<ParticleSubSwarmOptimization>(); // too early, particles would be lost...
        List<Set<String>> connectedComps = nicheGraph.getConnectedComponents();

        Population tmpPop = new Population(), newMainPop = new Population();
        Vector<Population> setOfSubswarms = new Vector<>();
        boolean reinitSuperfl = true;

        for (Set<String> connSet : connectedComps) {
            if (connSet.size() > 1) {// create niche
                Population pop = new Population(connSet.size());
                for (String indexStr : connSet) {
                    Integer index = Integer.valueOf(indexStr);
                    AbstractEAIndividual indy = getIndyByParticleIndex(index); // may be taken from a main swarm or current subwarm
                    if (indy == null) {
                        System.err.println("createNichesFromNicheGraph problem -> getIndyByParticleIndex returned null");
                    }
                    pop.add(indy);
                }
                if (maxInitialSubSwarmSize > 0 && (pop.size() > maxInitialSubSwarmSize)) {
                    tmpPop = pop.getWorstNIndividuals(pop.size() - maxInitialSubSwarmSize, -1);
                    tmpPop.synchSize();
                    pop.removeMembers(tmpPop, true);
                    if (reinitSuperfl) {
                        for (int i = 0; i < tmpPop.size(); i++) {
                            AbstractEAIndividual indy = tmpPop.getEAIndividual(i);
                            indy.initialize(optimizationProblem);
                            indy.resetFitness(Double.MAX_VALUE); // TODO this is not so nice... they should be collected in a reinit-list and inserted at the beginning of the next optimize step
                            ParticleSwarmOptimization.initIndividualDefaults(indy, 0.2);
                            ParticleSwarmOptimization.initIndividualMemory(indy);
                            ParticleSubSwarmOptimization.initSubSwarmDefaultsOf(indy);
                        }
                    }
                    newMainPop.addPopulation(tmpPop);
                    pop.synchSize();
                }
                setOfSubswarms.add(pop);
            } else { // move particles corresponding to unconnected vertices to the mainswarm
                Iterator<String> it = connSet.iterator();
                Integer index = Integer.valueOf(it.next());
                AbstractEAIndividual indy = getIndyByParticleIndex(index);
                newMainPop.add(indy);
            }
        }

        newMainPop.synchSize();
        for (Population setOfSubswarm : setOfSubswarms) {
            setOfSubswarm.synchSize();
        }
        useAsSubSwarms(setOfSubswarms);
        useAsMainSwarm(newMainPop);
    }

    /**
     */
    @Override
    public void optimize() {
        // main swarm:
        if (getMainSwarm().getPopulation().size() == 0) {// || mainSwarm.getPopulation().size() == 1){
            if (isVerbose()) {
                System.out.print("MainSwarm size is 0\n");
            }
            // increment the generationcount for the Terminator:
            // 1 Generation equals one optimize call including the optimization of the
            // (possibly empty) mainSwarm and all subSwarms
            getMainSwarm().getPopulation().incrGeneration();
        } else {
            getMainSwarm().optimize();
        }
        maybeReinitIndies();

        // sub swarms:
        for (int i = 0; i < getSubSwarms().size(); ++i) {
            ParticleSubSwarmOptimization subswarm = getSubSwarms().get(i);
            if (subswarm.isActive()) {
                subswarm.optimize();
            }
        }

        // deactivation:
        deactivateSubSwarmsIfPossible();

        if (isVerbose()) {
            System.out.print("active swarms: " + countActiveSubswarms() + " of " + getSubSwarms().size());
        }

        // build the s matrix and the niche graph
        // these data structures represent particles that have been close to each other
        // for at least two consecutive generations
        updateSMatrixAndNicheGraph();

        // create subswarms from the particles corresponding to connected vertices in the niche graph
        createSubswarmsFromNicheGraph();

        // one might create additional subswarms from the main swarm
        // using the standard strategie from the NichePSO
        // createSubswarmIfPossible();

        // adapt the species radius of the SPSO using similar population statistics as for the radius parameter r
        if ((mainSwarm.getTopology() == PSOTopology.multiSwarm) && (mainSwarm.getMaxSubSwarmSize() > 1)) { //Multi-Swarm
            double aveDistToNeighInMain = getMainSwarm().getPopulation().getAvgDistToClosestNeighbor(true, false)[0];
            getMainSwarm().setSubSwarmRadius(aveDistToNeighInMain);
        }

        if (isVerbose()) {
            System.out.println();
        }
        firePropertyChangedEvent("NextGenerationPerformed"); // calls Listener that sth changed...

        /** plotting **********************************************************************************/
        if (isPlot()) {
            doPlot();
        }

        // reset flags etc for:
        // deactivation
        deactivationOccured = false;
        deactivatedSwarm = new Vector<>();
        // merging
        mergingOccurd = false;
        borg = new Vector<>();
        others = new Vector<>();
        borgbest = new Vector<>();
        othersbest = new Vector<>();
        // absorbtion
        absorbtionOccurd = false;
        indytoabsorb = new Vector<>();
        // subswarmcreation
        creationOccurd = false;
        indyconverged = new Vector<>();
        convergedneighbor = new Vector<>();
        //clearing - deprecated
        //reinitoccurd = false;

    }


    /**
     * *******************************************************************************************************************
     * Deactivation
     */

    @Override
    protected void deactivationEventFor(ParticleSubSwarmOptimization subswarm) {
        super.deactivationEventFor(subswarm);
        resetSMatrixEntriesFor(subswarm);
        inactiveSubSwarms.add(subswarm); // ANPSO will later remove the inactive subswarm from the standard set of subswarms...
    }

/**********************************************************************************************************************
 * setter, getter
 */
    /**
     * @param size
     * sets the !initial! size of the mainswarm population
     * use this instead of getPopulation.setPopulationSize()
     */
    @Override
    public void setMainSwarmSize(int size) {
        // set member
        this.mainSwarmSize = size;
        // pass on to the mainswarm optimizer
        getMainSwarm().getPopulation().setTargetSize(size);
        // update s
        s = new int[size][size];
        initSTo(0);
        initNicheGraph();
    }

    /**
     * @param includeInactive
     * @return a population with clones from all subswarms
     */
    public Population getSubswarmMetapop(boolean includeInactive) {
        // construct a metapop with clones from all subswarms
        Population metapop = new Population();
        for (int i = 0; i < getSubSwarms().size(); ++i) {
            ParticleSubSwarmOptimization currentsubswarm = getSubSwarms().get(i);
            if (includeInactive || currentsubswarm.isActive()) {
                Population currentsubswarmpop = (Population) currentsubswarm.getPopulation().clone();
                metapop.addPopulation(currentsubswarmpop);
            }
        }
        return metapop;
    }

    /**
     * @return a population consisting of copies from the mainswarm and all subswarms.
     * returns a population consisting of copies from the mainswarm and all subswarms
     * (active and inactive, so the size of this Population is not necessarily constant).
     * Especially important for the call back regarding the output file...
     * Beware: getPopulation().getPopulationSize() returns the !initial! size of the main swarm,
     * the actual size of the complete population is accessed via getPopulation().size()
     */
    @Override
    public Population getPopulation() {
        // construct a metapop with clones from the mainswarm and all subswarms
        Population metapop = (Population) getMainSwarm().getPopulation().clone();
        Population currentsubswarm = new Population();
        for (int i = 0; i < getSubSwarms().size(); ++i) {
            currentsubswarm = (Population) getSubSwarms().get(i).getPopulation().clone();
            metapop.addPopulation(currentsubswarm);
        }
        for (ParticleSubSwarmOptimization inactiveSubSwarm : inactiveSubSwarms) { // in the case of ANPSO
            currentsubswarm = (Population) inactiveSubSwarm.getPopulation().clone();
            metapop.addPopulation(currentsubswarm);
        }

        // set correct number of generations
        metapop.setGeneration(getMainSwarm().getPopulation().getGeneration());

        // set correct number of function calls
        int calls = getMainSwarm().getPopulation().getFunctionCalls();
        for (int i = 0; i < getSubSwarms().size(); ++i) {
            ParticleSubSwarmOptimization subswarm = getSubSwarms().get(i);
            calls += subswarm.getPopulation().getFunctionCalls();
        }
        // calls from inactivated subswarms were transfered to the mainswarm, see useAsSubSwarms method

        metapop.setFunctionCalls(calls);

        return metapop;
    }

    /**
     * @return array with copies of the gbest individuals
     * returns the cloned global best individuals (ie best of all time) from every subswarm
     */
    public Population getSubswarmRepresentatives() {
        //boolean includeMainSwarm = false;
        int mainSize = 0;
        //if (includeMainSwarm) mainSize = getMainSwarm().getPopulation().size();
        Population elitePop = new Population(getSubSwarms().size() + inactiveSubSwarms.size() + mainSize);

        for (int i = 0; i < getSubSwarms().size(); ++i) {
            AbstractEAIndividual bestSS = getSubSwarms().get(i).getBestIndividual();
            elitePop.addIndividual((AbstractEAIndividual) getSubSwarms().get(i).bestIndividual.clone());
        }
        for (ParticleSubSwarmOptimization inactiveSubSwarm : inactiveSubSwarms) {
            elitePop.addIndividual((AbstractEAIndividual) inactiveSubSwarm.bestIndividual.clone());
        }
        return elitePop;
    }

    /**
     * @return descriptive string of the elite
     * returns a string that lists the global best individuals (ie best of all time) from every subswarm
     */
    public String getSubswarmRepresentativesAsString() {
        String result = "\nSubswarmRepresentatives: \n";
        Population elite = getSubswarmRepresentatives();
        for (int i = 0; i < getSubSwarms().size() + inactiveSubSwarms.size(); ++i) {
            result += elite.getEAIndividual(i).getStringRepresentation() + "\n";
        }
        return result;
    }

    /**
     */
    @Override
    protected void plotSubSwarms() {
        if (this.optimizationProblem instanceof Interface2DBorderProblem) {
            //DPointSet               popRep  = new DPointSet();
            InterfaceDataTypeDouble tmpIndy1;

            //cleanPlotSubSwarms();

            // for all inactive SubSwarms from ANPSO...
            for (int i = 0; i < this.inactiveSubSwarms.size(); i++) {
                ParticleSubSwarmOptimization currentsubswarm = this.inactiveSubSwarms.get(i);
                InterfaceDataTypeDouble best = (InterfaceDataTypeDouble) currentsubswarm.bestIndividual;
                plotCircleForIndy((AbstractEAIndividual) best, "[I]");
            }

            // for all SubSwarms...
            for (int i = 0; i < this.getSubSwarms().size(); i++) {
                ParticleSubSwarmOptimization currentsubswarm = this.getSubSwarms().get(i);
                Population currentsubswarmpop = currentsubswarm.getPopulation();
                //InterfaceDataTypeDouble best = (InterfaceDataTypeDouble)currentsubswarmpop.getBestIndividual();
                InterfaceDataTypeDouble best = (InterfaceDataTypeDouble) currentsubswarm.bestIndividual;
                DPointSet popRep = new DPointSet();

                //...draw SubSwarm as points
                for (int j = 0; j < currentsubswarmpop.size(); j++) {
                    popRep.setConnected(false);
                    tmpIndy1 = (InterfaceDataTypeDouble) currentsubswarmpop.get(j);
                    popRep.addDPoint(new DPoint(tmpIndy1.getDoubleData()[0], tmpIndy1.getDoubleData()[1]));
                }
                this.topoPlot.getFunctionArea().addDElement(popRep); // time consuming

                //...draw circle for best
                if (!currentsubswarm.isActive()) {
                    plotCircleForIndy((AbstractEAIndividual) best, "[I]");
                } else {
                    if (getSubswarmOptimizerTemplate().isGcpso()) {
                        String rhoAsString = String.format("%6.3f", currentsubswarm.getRho());
                    }
                }

                //...draw SubSwarm as connected lines to best
                popRep = new DPointSet();
                for (int j = 0; j < currentsubswarmpop.size(); j++) {
                    //popRep.setConnected(false);
                    tmpIndy1 = (InterfaceDataTypeDouble) currentsubswarmpop.get(j);
                    //popRep.addDPoint(new DPoint(tmpIndy1.getDoubleData()[0], tmpIndy1.getDoubleData()[1]));

                    popRep.setConnected(true);
                    popRep.addDPoint(new DPoint(tmpIndy1.getDoubleData()[0], tmpIndy1.getDoubleData()[1]));
                    popRep.addDPoint(new DPoint(best.getDoubleData()[0], best.getDoubleData()[1]));
                }
                this.topoPlot.getFunctionArea().addDElement(popRep); // time consuming
            }
        } // endif
    }

    /**
     * @param index
     * @return inactive particles with given index
     *         (may return more than one particle for a given index because indizes are reused during deactivation
     *         and the reinitialized particle may be deactivated again...)
     */
    public Vector<AbstractEAIndividual> getInactiveIndiesByParticleIndex(Integer index) {
        Vector<AbstractEAIndividual> indies = null;
        AbstractEAIndividual indy = null;
        for (int i = 0; i < inactiveSubSwarms.size(); ++i) {
            Population pop = inactiveSubSwarms.get(i).getPopulation();
            indy = getIndyByParticleIndexAndPopulation(pop, index); // fix if needed: only returns the first occurence...
            if (indy != null) {
                indies.add(indy);
            }
        }
        return indies;
    }

    /**
     * @return The name of the algorithm
     * This method will return a naming String
     */
    @Override
    public String getName() {
        return "ANPSO-" + getMainSwarmSize();
    }

    public void SetMinimalR(double minimalR) {
        this.minimalR = minimalR;
    }

    public int getMaxInitialSubSwarmSize() {
        return maxInitialSubSwarmSize;
    }

    public String maxInitialSubSwarmSizeTipText() {
        return "The maximum size of sub swarms at creation time.";
    }

    public void setMaxInitialSubSwarmSize(int maxSubSwarmSize) {
        this.maxInitialSubSwarmSize = maxSubSwarmSize;
    }


    public String mainSwarmTopologyTipText() {
        return "sets the topology type used to train the main swarm";
    }

    public String mainSwarmTopologyRangeTipText() {
        return "sets the range of the neighborhood topology for the main swarm";
    }

    public static OptimizationParameters aNichePSO(AbstractOptimizationProblem problem, long randSeed, InterfaceTerminator term) {
        ANPSO anpso = new ANPSO();
        anpso.setMainSwarmSize(75);

        return OptimizerFactory.makeParams(anpso, 75, problem, randSeed, term);
    }

    /**
     * Creates a standard ANPSO variant with default constricted PSO parameters and a grid topology.
     *
     * @param problem
     * @param randSeed
     * @param evalCnt
     * @return
     */
    public static OptimizationParameters stdANPSO(AbstractOptimizationProblem problem, long randSeed, int evalCnt) {
        ANPSO anpso = new ANPSO();
        NichePSO.stdNPSO(anpso, problem, randSeed, evalCnt);

        anpso.getMainSwarm().setPhi1(2.05);
        anpso.getMainSwarm().setPhi2(2.05);
        anpso.getMainSwarm().setInertnessOrChi(0.7298437881283576);
        anpso.getMainSwarm().setAlgoType(ParticleSwarmOptimization.PSOType.Constriction);
        anpso.setMainSwarmAlgoType(ParticleSwarmOptimization.PSOType.Constriction); // constriction
        anpso.setMaxInitialSubSwarmSize(0); // deactivate early reinits
        anpso.setMainSwarmTopology(PSOTopology.grid);
        anpso.setMainSwarmTopologyRange(1);
        anpso.setDeactivationStrategy(new StandardDeactivationStrategy(0.000001, 8));

//		es gibt kein species size limit wie im orig-paper, aber sie berichten dort, dass sie für
//		höhere dimensionsn (3,4), eh keines benutzen.

        return OptimizerFactory.makeParams(anpso, anpso.getMainSwarmSize(), problem, randSeed, new EvaluationTerminator(evalCnt));
    }

    public static OptimizationParameters starANPSO(AbstractOptimizationProblem problem, long randSeed, int evalCnt) {
        ANPSO anpso = new ANPSO();
        NichePSO.starNPSO(anpso, problem, randSeed, evalCnt);

        anpso.getMainSwarm().setParameterControl(new ParamAdaption[]{new LinearParamAdaption("inertnessOrChi", 0.7, 0.2)});
//		anpso.setMainSwarmInertness(new LinearParameterAging(0.7, 0.2, evalCnt/anpso.getMainSwarmSize()));

        anpso.getMainSwarm().setAlgoType(ParticleSwarmOptimization.PSOType.Inertness);
        anpso.setMainSwarmAlgoType(ParticleSwarmOptimization.PSOType.Inertness); // inertness
        anpso.getMainSwarm().setPhi1(1.2);
        anpso.getMainSwarm().setPhi2(0.6);  // ANPSO uses communication in the main swarm
        //Possible topologies are: "Linear", "Grid", "Star", "Multi-Swarm", "Tree", "HPSO", "Random" in that order starting by 0.
        anpso.setMainSwarmTopologyTag(3); //"Multi-Swarm" favors the formation of groups in the main swarm
        anpso.setMainSwarmTopologyRange(2); // range for topologies like random, grid etc. (does not affect "Multi-Swarm")
        anpso.setMaxInitialSubSwarmSize(0); // deactivate early reinits

//		es gibt kein species size limit wie im orig-paper, aber sie berichten dort, dass sie für
//		höhere dimensionsn (3,4), eh keines benutzen.

        return OptimizerFactory.makeParams(anpso, anpso.getMainSwarmSize(), problem, randSeed, new EvaluationTerminator(evalCnt));
    }

    public static OptimizationParameters gmakANPSO(AbstractOptimizationProblem problem, long randSeed, int evalCnt) {
        ANPSO anpso = new ANPSO();
        NichePSO.starNPSO(anpso, problem, randSeed, evalCnt);

        anpso.getMainSwarm().setParameterControl(new ParamAdaption[]{new LinearParamAdaption("inertnessOrChi", 0.7, 0.2)});

        anpso.getMainSwarm().setAlgoType(ParticleSwarmOptimization.PSOType.Inertness);
        anpso.setMainSwarmAlgoType(ParticleSwarmOptimization.PSOType.Inertness);
        anpso.getMainSwarm().setPhi1(1.2);
        anpso.getMainSwarm().setPhi2(1.2);  // ANPSO uses communication in the main swarm
        //Possible topologies are: "Linear", "Grid", "Star", "Multi-Swarm", "Tree", "HPSO", "Random" in that order starting by 0.
        anpso.setMainSwarmTopologyTag(3); //"Multi-Swarm" favors the formation of groups in the main swarm
        anpso.setMainSwarmTopologyRange(4); // range for topologies like random, grid etc. (does not affect "Multi-Swarm")

//		es gibt kein species size limit wie im orig-paper, aber sie berichten dort, dass sie für
//		höhere dimensionsn (3,4), eh keines benutzen.

        anpso.getSubswarmOptimizerTemplate().setRho(1);
        anpso.getSubswarmOptimizerTemplate().SetRhoIncreaseFactor(2);
        anpso.getSubswarmOptimizerTemplate().SetRhoDecreaseFactor(0.5);

        return OptimizerFactory.makeParams(anpso, anpso.getMainSwarmSize(), problem, randSeed, new EvaluationTerminator(evalCnt));
    }

    /**
     * Create a grid-star-ANPSO with range 2.
     *
     * @param problem
     * @param randSeed
     * @param evalCnt
     * @return
     */
    public static OptimizationParameters sgANPSO(AbstractOptimizationProblem problem, long randSeed, int evalCnt) {
        return starTopoANPSO(problem, randSeed, evalCnt, 1, 2);
    }

    /**
     * Create a starANPSO with a given main swarm topology.
     *
     * @param problem
     * @param randSeed
     * @param evalCnt
     * @param topology
     * @param topologyRange
     * @return
     */
    public static OptimizationParameters starTopoANPSO(AbstractOptimizationProblem problem, long randSeed, int evalCnt, int topology, int topologyRange) {
        OptimizationParameters params = starANPSO(problem, randSeed, evalCnt);
        ((ANPSO) params.getOptimizer()).setMainSwarmTopologyTag(topology);
        ((ANPSO) params.getOptimizer()).setMainSwarmTopologyRange(topologyRange);
        ((ANPSO) params.getOptimizer()).getMainSwarm().setInertnessOrChi(0.73);

        return params;
    }

    @Override
    public String[] getAdditionalDataHeader() {
        return ToolBox.appendArrays(super.getAdditionalDataHeader(), new String[]{"mainSwarmBestFit", "swarmRad"});
    }

    @Override
    public Object[] getAdditionalDataValue(PopulationInterface pop) {
        return ToolBox.appendArrays(super.getAdditionalDataValue(pop), new Object[]{getMainSwarm().getPopulation().getBestFitness()[0], updateRadius});
    }

    @Override
    public String[] getAdditionalDataInfo() {
        return ToolBox.appendArrays(super.getAdditionalDataInfo(), new String[]{"The best fitness within the main swarm", "The current value of the adapted swarm radius"});
    }

    @Override
    public Population getSubswarmRepresentatives(boolean onlyInactive) {

        Population representatives = super.getSubswarmRepresentatives(onlyInactive);
        // this vector does not yet contain the archived solutions
        for (int i = 0; i < inactiveSubSwarms.size(); i++) {
            representatives.add(inactiveSubSwarms.get(i).getBestIndividual());
        }
        representatives.synchSize();
        return representatives;
    }

    /**
     * Return the median correlation of the best individuals of the given set of swarms.
     */
    private double getMedCorrelation(
            Vector<ParticleSubSwarmOptimization> swarms) {
        Population pop = new Population(swarms.size());
        for (int i = 0; i < swarms.size(); i++) {
            if (swarms.get(i) != null) {
                pop.addIndividual(swarms.get(i).getBestIndividual());
            }
        }
        return pop.getCorrelations()[3];
    }

    /**
     * Return the mean distance of the best individuals of the given set of swarms.
     *
     * @param swarms
     * @return
     */
    private double getMeanDist(
            Vector<ParticleSubSwarmOptimization> swarms) {
        Population pop = new Population(swarms.size());
        for (int i = 0; i < swarms.size(); i++) {
            if (swarms.get(i) != null) {
                pop.addIndividual(swarms.get(i).getBestIndividual());
            }
        }
        return pop.getPopulationMeasures()[0];
    }

    @Override
    protected int getNumArchived() {
        return (inactiveSubSwarms.size());
    }
}
