NeuronString.java

/*
 * 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.neuralnet.oned;

import org.apache.commons.math4.neuralnet.internal.NeuralNetException;
import org.apache.commons.math4.neuralnet.FeatureInitializer;
import org.apache.commons.math4.neuralnet.Network;

/**
 * Neural network with the topology of a one-dimensional line.
 * Each neuron defines one point on the line.
 *
 * @since 3.3
 */
public class NeuronString {
    /** Minimal number of neurons. */
    private static final int MIN_NEURONS = 2;
    /** Underlying network. */
    private final Network network;
    /** Number of neurons. */
    private final int size;
    /** Wrap. */
    private final boolean wrap;

    /**
     * Mapping of the 1D coordinate to the neuron identifiers
     * (attributed by the {@link #network} instance).
     */
    private final long[] identifiers;

    /**
     * Constructor with restricted access, solely used for deserialization.
     *
     * @param wrap Whether to wrap the dimension (i.e the first and last
     * neurons will be linked together).
     * @param featuresList Arrays that will initialize the features sets of
     * the network's neurons.
     * @throws IllegalArgumentException if {@code num < 2}.
     */
    NeuronString(boolean wrap,
                 double[][] featuresList) {
        size = featuresList.length;

        if (size < MIN_NEURONS) {
            throw new NeuralNetException(NeuralNetException.TOO_SMALL, size, MIN_NEURONS);
        }

        this.wrap = wrap;

        final int fLen = featuresList[0].length;
        network = new Network(0, fLen);
        identifiers = new long[size];

        // Add neurons.
        for (int i = 0; i < size; i++) {
            identifiers[i] = network.createNeuron(featuresList[i]);
        }

        // Add links.
        createLinks();
    }

    /**
     * Creates a one-dimensional network:
     * Each neuron not located on the border of the mesh has two
     * neurons linked to it.
     * <br>
     * The links are bi-directional.
     * Neurons created successively are neighbours (i.e. there are
     * links between them).
     * <br>
     * The topology of the network can also be a circle (if the
     * dimension is wrapped).
     *
     * @param num Number of neurons.
     * @param wrap Whether to wrap the dimension (i.e the first and last
     * neurons will be linked together).
     * @param featureInit Arrays that will initialize the features sets of
     * the network's neurons.
     * @throws IllegalArgumentException if {@code num < 2}.
     */
    public NeuronString(int num,
                        boolean wrap,
                        FeatureInitializer[] featureInit) {
        if (num < MIN_NEURONS) {
            throw new NeuralNetException(NeuralNetException.TOO_SMALL, num, MIN_NEURONS);
        }

        size = num;
        this.wrap = wrap;
        identifiers = new long[num];

        final int fLen = featureInit.length;
        network = new Network(0, fLen);

        // Add neurons.
        for (int i = 0; i < num; i++) {
            final double[] features = new double[fLen];
            for (int fIndex = 0; fIndex < fLen; fIndex++) {
                features[fIndex] = featureInit[fIndex].value();
            }
            identifiers[i] = network.createNeuron(features);
        }

        // Add links.
        createLinks();
    }

    /**
     * Retrieves the underlying network.
     * A reference is returned (enabling, for example, the network to be
     * trained).
     * This also implies that calling methods that modify the {@link Network}
     * topology may cause this class to become inconsistent.
     *
     * @return the network.
     */
    public Network getNetwork() {
        return network;
    }

    /**
     * Gets the number of neurons.
     *
     * @return the number of neurons.
     */
    public int getSize() {
        return size;
    }

    /**
     * Indicates whether the line of neurons is wrapped.
     *
     * @return {@code true} if the last neuron is linked to the first neuron.
     */
    public boolean isWrapped() {
        return wrap;
    }

    /**
     * Retrieves the features set from the neuron at location
     * {@code i} in the map.
     *
     * @param i Neuron index.
     * @return the features of the neuron at index {@code i}.
     * @throws IllegalArgumentException if {@code i} is out of range.
     */
    public double[] getFeatures(int i) {
        if (i < 0 ||
            i >= size) {
            throw new NeuralNetException(NeuralNetException.OUT_OF_RANGE, i, 0, size - 1);
        }

        return network.getNeuron(identifiers[i]).getFeatures();
    }

    /**
     * Creates the neighbour relationships between neurons.
     */
    private void createLinks() {
        for (int i = 0; i < size - 1; i++) {
            network.addLink(network.getNeuron(i), network.getNeuron(i + 1));
        }
        for (int i = size - 1; i > 0; i--) {
            network.addLink(network.getNeuron(i), network.getNeuron(i - 1));
        }
        if (wrap) {
            network.addLink(network.getNeuron(0), network.getNeuron(size - 1));
            network.addLink(network.getNeuron(size - 1), network.getNeuron(0));
        }
    }
}