/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.commons.math4.examples.sofm.chineserings;

import java.util.Iterator;

import org.apache.commons.rng.UniformRandomProvider;
import org.apache.commons.rng.simple.RandomSource;
import org.apache.commons.geometry.euclidean.threed.Vector3D;

import org.apache.commons.math4.neuralnet.SquareNeighbourhood;
import org.apache.commons.math4.neuralnet.FeatureInitializer;
import org.apache.commons.math4.neuralnet.FeatureInitializerFactory;
import org.apache.commons.math4.neuralnet.MapUtils;
import org.apache.commons.math4.neuralnet.DistanceMeasure;
import org.apache.commons.math4.neuralnet.EuclideanDistance;
import org.apache.commons.math4.neuralnet.twod.NeuronSquareMesh2D;
import org.apache.commons.math4.neuralnet.sofm.LearningFactorFunction;
import org.apache.commons.math4.neuralnet.sofm.LearningFactorFunctionFactory;
import org.apache.commons.math4.neuralnet.sofm.NeighbourhoodSizeFunction;
import org.apache.commons.math4.neuralnet.sofm.NeighbourhoodSizeFunctionFactory;
import org.apache.commons.math4.neuralnet.sofm.KohonenUpdateAction;
import org.apache.commons.math4.neuralnet.sofm.KohonenTrainingTask;
import org.apache.commons.math4.legacy.stat.descriptive.SummaryStatistics;

/**
 * SOFM for categorizing points that belong to each of two intertwined rings.
 */
class ChineseRingsClassifier {
    /** SOFM. */
    private final NeuronSquareMesh2D sofm;
    /** Rings. */
    private final ChineseRings rings;
    /** Distance function. */
    private final DistanceMeasure distance = new EuclideanDistance();

    /**
     * @param rings Training data.
     * @param dim1 Number of rows of the SOFM.
     * @param dim2 Number of columns of the SOFM.
     */
    public ChineseRingsClassifier(ChineseRings rings,
                                  int dim1,
                                  int dim2) {
        this.rings = rings;
        sofm = new NeuronSquareMesh2D(dim1, false,
                                      dim2, false,
                                      SquareNeighbourhood.MOORE,
                                      makeInitializers());
    }

    /**
     * Creates training tasks.
     *
     * @param numTasks Number of tasks to create.
     * @param numSamplesPerTask Number of training samples per task.
     * @return the created tasks.
     */
    public Runnable[] createParallelTasks(int numTasks,
                                          long numSamplesPerTask) {
        final Runnable[] tasks = new Runnable[numTasks];
        final LearningFactorFunction learning
            = LearningFactorFunctionFactory.exponentialDecay(1e-1,
                                                             5e-2,
                                                             numSamplesPerTask / 2);
        final double numNeurons = Math.sqrt(sofm.getNumberOfRows() * sofm.getNumberOfColumns());
        final NeighbourhoodSizeFunction neighbourhood
            = NeighbourhoodSizeFunctionFactory.exponentialDecay(0.5 * numNeurons,
                                                                0.2 * numNeurons,
                                                                numSamplesPerTask / 2);

        for (int i = 0; i < numTasks; i++) {
            final KohonenUpdateAction action = new KohonenUpdateAction(distance,
                                                                       learning,
                                                                       neighbourhood);
            tasks[i] = new KohonenTrainingTask(sofm.getNetwork(),
                                               createRandomIterator(numSamplesPerTask),
                                               action);
        }

        return tasks;
    }

    /**
     * Creates a training task.
     *
     * @param numSamples Number of training samples.
     * @return the created task.
     */
    public Runnable createSequentialTask(long numSamples) {
        return createParallelTasks(1, numSamples)[0];
    }

    /**
     * Computes various quality measures.
     *
     * @return the indicators.
     */
    public NeuronSquareMesh2D.DataVisualization computeQualityIndicators() {
        return sofm.computeQualityIndicators(rings.createIterable());
    }

    /**
     * Creates the features' initializers.
     * They are sampled from a uniform distribution around the barycentre of
     * the rings.
     *
     * @return an array containing the initializers for the x, y and
     * z coordinates of the features array of the neurons.
     */
    private FeatureInitializer[] makeInitializers() {
        final SummaryStatistics[] centre = new SummaryStatistics[] {
            new SummaryStatistics(),
            new SummaryStatistics(),
            new SummaryStatistics()
        };
        for (Vector3D p : rings.getPoints()) {
            centre[0].addValue(p.getX());
            centre[1].addValue(p.getY());
            centre[2].addValue(p.getZ());
        }

        final double[] mean = new double[] {
            centre[0].getMean(),
            centre[1].getMean(),
            centre[2].getMean()
        };
        final double s = 0.1;
        final double[] dev = new double[] {
            s * centre[0].getStandardDeviation(),
            s * centre[1].getStandardDeviation(),
            s * centre[2].getStandardDeviation()
        };

        final UniformRandomProvider rng = RandomSource.create(RandomSource.SPLIT_MIX_64);
        return new FeatureInitializer[] {
            FeatureInitializerFactory.uniform(rng, mean[0] - dev[0], mean[0] + dev[0]),
            FeatureInitializerFactory.uniform(rng, mean[1] - dev[1], mean[1] + dev[1]),
            FeatureInitializerFactory.uniform(rng, mean[2] - dev[2], mean[2] + dev[2])
        };
    }

    /**
     * Creates an iterator that will present a series of points coordinates in
     * a random order.
     *
     * @param numSamples Number of samples.
     * @return the iterator.
     */
    private Iterator<double[]> createRandomIterator(final long numSamples) {
        return new Iterator<double[]>() {
            /** Data. */
            final Vector3D[] points = rings.getPoints();
            /** RNG. */
            final UniformRandomProvider rng = RandomSource.create(RandomSource.KISS);
            /** Number of samples. */
            private long n = 0;

            /** {@inheritDoc} */
            public boolean hasNext() {
                return n < numSamples;
            }

            /** {@inheritDoc} */
            public double[] next() {
                ++n;
                return points[rng.nextInt(points.length)].toArray();
            }

            /** {@inheritDoc} */
            public void remove() {
                throw new UnsupportedOperationException();
            }
        };
    }
}

@@ -5877,38 +5877,8 @@
  %7D;%0A
-        final double s = 0.1;%0A
     
@@ -5921,33 +5921,35 @@
 %5D %7B%0A            
-s
+0.1
  * centre%5B0%5D.get
@@ -5973,33 +5973,35 @@
 (),%0A            
-s
+0.1
  * centre%5B1%5D.get
@@ -6033,17 +6033,19 @@
         
-s
+0.1
  * centr

Remove dead local store