001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.math4.neuralnet;
019
020import java.util.concurrent.atomic.AtomicReference;
021import java.util.concurrent.atomic.AtomicLong;
022
023import org.apache.commons.numbers.core.Precision;
024import org.apache.commons.math4.neuralnet.internal.NeuralNetException;
025
026/**
027 * Describes a neuron element of a neural network.
028 *
029 * This class aims to be thread-safe.
030 *
031 * @since 3.3
032 */
033public class Neuron {
034    /** Identifier. */
035    private final long identifier;
036    /** Length of the feature set. */
037    private final int size;
038    /** Neuron data. */
039    private final AtomicReference<double[]> features;
040    /** Number of attempts to update a neuron. */
041    private final AtomicLong numberOfAttemptedUpdates = new AtomicLong(0);
042    /** Number of successful updates  of a neuron. */
043    private final AtomicLong numberOfSuccessfulUpdates = new AtomicLong(0);
044
045    /**
046     * Creates a neuron.
047     * The size of the feature set is fixed to the length of the given
048     * argument.
049     * <br>
050     * Constructor is package-private: Neurons must be
051     * {@link Network#createNeuron(double[]) created} by the network
052     * instance to which they will belong.
053     *
054     * @param identifier Identifier (assigned by the {@link Network}).
055     * @param features Initial values of the feature set.
056     */
057    Neuron(long identifier,
058           double[] features) {
059        this.identifier = identifier;
060        this.size = features.length;
061        this.features = new AtomicReference<>(features.clone());
062    }
063
064    /**
065     * Performs a deep copy of this instance.
066     * Upon return, the copied and original instances will be independent:
067     * Updating one will not affect the other.
068     *
069     * @return a new instance with the same state as this instance.
070     * @since 3.6
071     */
072    public synchronized Neuron copy() {
073        final Neuron copy = new Neuron(getIdentifier(),
074                                       getFeatures());
075        copy.numberOfAttemptedUpdates.set(numberOfAttemptedUpdates.get());
076        copy.numberOfSuccessfulUpdates.set(numberOfSuccessfulUpdates.get());
077
078        return copy;
079    }
080
081    /**
082     * Gets the neuron's identifier.
083     *
084     * @return the identifier.
085     */
086    public long getIdentifier() {
087        return identifier;
088    }
089
090    /**
091     * Gets the length of the feature set.
092     *
093     * @return the number of features.
094     */
095    public int getSize() {
096        return size;
097    }
098
099    /**
100     * Gets the neuron's features.
101     *
102     * @return a copy of the neuron's features.
103     */
104    public double[] getFeatures() {
105        return features.get().clone();
106    }
107
108    /**
109     * Tries to atomically update the neuron's features.
110     * Update will be performed only if the expected values match the
111     * current values.<br>
112     * In effect, when concurrent threads call this method, the state
113     * could be modified by one, so that it does not correspond to the
114     * the state assumed by another.
115     * Typically, a caller {@link #getFeatures() retrieves the current state},
116     * and uses it to compute the new state.
117     * During this computation, another thread might have done the same
118     * thing, and updated the state: If the current thread were to proceed
119     * with its own update, it would overwrite the new state (which might
120     * already have been used by yet other threads).
121     * To prevent this, the method does not perform the update when a
122     * concurrent modification has been detected, and returns {@code false}.
123     * When this happens, the caller should fetch the new current state,
124     * redo its computation, and call this method again.
125     *
126     * @param expect Current values of the features, as assumed by the caller.
127     * Update will never succeed if the contents of this array does not match
128     * the values returned by {@link #getFeatures()}.
129     * @param update Features's new values.
130     * @return {@code true} if the update was successful, {@code false}
131     * otherwise.
132     * @throws IllegalArgumentException if the length of {@code update} is
133     * not the same as specified in the {@link #Neuron(long,double[])
134     * constructor}.
135     */
136    public boolean compareAndSetFeatures(double[] expect,
137                                         double[] update) {
138        if (update.length != size) {
139            throw new NeuralNetException(NeuralNetException.SIZE_MISMATCH,
140                                         update.length, size);
141        }
142
143        // Get the internal reference. Note that this must not be a copy;
144        // otherwise the "compareAndSet" below will always fail.
145        final double[] current = features.get();
146        if (!containSameValues(current, expect)) {
147            // Some other thread already modified the state.
148            return false;
149        }
150
151        // Increment attempt counter.
152        numberOfAttemptedUpdates.incrementAndGet();
153
154        if (features.compareAndSet(current, update.clone())) {
155            // The current thread could atomically update the state (attempt succeeded).
156            numberOfSuccessfulUpdates.incrementAndGet();
157            return true;
158        } else {
159            // Some other thread came first (attempt failed).
160            return false;
161        }
162    }
163
164    /**
165     * Retrieves the number of calls to the
166     * {@link #compareAndSetFeatures(double[],double[]) compareAndSetFeatures}
167     * method.
168     * Note that if the caller wants to use this method in combination with
169     * {@link #getNumberOfSuccessfulUpdates()}, additional synchronization
170     * may be required to ensure consistency.
171     *
172     * @return the number of update attempts.
173     * @since 3.6
174     */
175    public long getNumberOfAttemptedUpdates() {
176        return numberOfAttemptedUpdates.get();
177    }
178
179    /**
180     * Retrieves the number of successful calls to the
181     * {@link #compareAndSetFeatures(double[],double[]) compareAndSetFeatures}
182     * method.
183     * Note that if the caller wants to use this method in combination with
184     * {@link #getNumberOfAttemptedUpdates()}, additional synchronization
185     * may be required to ensure consistency.
186     *
187     * @return the number of successful updates.
188     * @since 3.6
189     */
190    public long getNumberOfSuccessfulUpdates() {
191        return numberOfSuccessfulUpdates.get();
192    }
193
194    /**
195     * Checks whether the contents of both arrays is the same.
196     *
197     * @param current Current values.
198     * @param expect Expected values.
199     * @throws IllegalArgumentException if the length of {@code expect}
200     * is not the same as specified in the {@link #Neuron(long,double[])
201     * constructor}.
202     * @return {@code true} if the arrays contain the same values.
203     */
204    private boolean containSameValues(double[] current,
205                                      double[] expect) {
206        if (expect.length != size) {
207            throw new NeuralNetException(NeuralNetException.SIZE_MISMATCH,
208                                         expect.length, size);
209        }
210
211        for (int i = 0; i < size; i++) {
212            if (!Precision.equals(current[i], expect[i])) {
213                return false;
214            }
215        }
216        return true;
217    }
218}