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.ml.neuralnet;
019
020import java.io.Serializable;
021import java.io.ObjectInputStream;
022import java.util.concurrent.atomic.AtomicReference;
023import java.util.concurrent.atomic.AtomicLong;
024
025import org.apache.commons.math4.exception.DimensionMismatchException;
026import org.apache.commons.numbers.core.Precision;
027
028
029/**
030 * Describes a neuron element of a neural network.
031 *
032 * This class aims to be thread-safe.
033 *
034 * @since 3.3
035 */
036public class Neuron implements Serializable {
037    /** Serializable. */
038    private static final long serialVersionUID = 20130207L;
039    /** Identifier. */
040    private final long identifier;
041    /** Length of the feature set. */
042    private final int size;
043    /** Neuron data. */
044    private final AtomicReference<double[]> features;
045    /** Number of attempts to update a neuron. */
046    private final AtomicLong numberOfAttemptedUpdates = new AtomicLong(0);
047    /** Number of successful updates  of a neuron. */
048    private final AtomicLong numberOfSuccessfulUpdates = new AtomicLong(0);
049
050    /**
051     * Creates a neuron.
052     * The size of the feature set is fixed to the length of the given
053     * argument.
054     * <br>
055     * Constructor is package-private: Neurons must be
056     * {@link Network#createNeuron(double[]) created} by the network
057     * instance to which they will belong.
058     *
059     * @param identifier Identifier (assigned by the {@link Network}).
060     * @param features Initial values of the feature set.
061     */
062    Neuron(long identifier,
063           double[] features) {
064        this.identifier = identifier;
065        this.size = features.length;
066        this.features = new AtomicReference<>(features.clone());
067    }
068
069    /**
070     * Performs a deep copy of this instance.
071     * Upon return, the copied and original instances will be independent:
072     * Updating one will not affect the other.
073     *
074     * @return a new instance with the same state as this instance.
075     * @since 3.6
076     */
077    public synchronized Neuron copy() {
078        final Neuron copy = new Neuron(getIdentifier(),
079                                       getFeatures());
080        copy.numberOfAttemptedUpdates.set(numberOfAttemptedUpdates.get());
081        copy.numberOfSuccessfulUpdates.set(numberOfSuccessfulUpdates.get());
082
083        return copy;
084    }
085
086    /**
087     * Gets the neuron's identifier.
088     *
089     * @return the identifier.
090     */
091    public long getIdentifier() {
092        return identifier;
093    }
094
095    /**
096     * Gets the length of the feature set.
097     *
098     * @return the number of features.
099     */
100    public int getSize() {
101        return size;
102    }
103
104    /**
105     * Gets the neuron's features.
106     *
107     * @return a copy of the neuron's features.
108     */
109    public double[] getFeatures() {
110        return features.get().clone();
111    }
112
113    /**
114     * Tries to atomically update the neuron's features.
115     * Update will be performed only if the expected values match the
116     * current values.<br>
117     * In effect, when concurrent threads call this method, the state
118     * could be modified by one, so that it does not correspond to the
119     * the state assumed by another.
120     * Typically, a caller {@link #getFeatures() retrieves the current state},
121     * and uses it to compute the new state.
122     * During this computation, another thread might have done the same
123     * thing, and updated the state: If the current thread were to proceed
124     * with its own update, it would overwrite the new state (which might
125     * already have been used by yet other threads).
126     * To prevent this, the method does not perform the update when a
127     * concurrent modification has been detected, and returns {@code false}.
128     * When this happens, the caller should fetch the new current state,
129     * redo its computation, and call this method again.
130     *
131     * @param expect Current values of the features, as assumed by the caller.
132     * Update will never succeed if the contents of this array does not match
133     * the values returned by {@link #getFeatures()}.
134     * @param update Features's new values.
135     * @return {@code true} if the update was successful, {@code false}
136     * otherwise.
137     * @throws DimensionMismatchException if the length of {@code update} is
138     * not the same as specified in the {@link #Neuron(long,double[])
139     * constructor}.
140     */
141    public boolean compareAndSetFeatures(double[] expect,
142                                         double[] update) {
143        if (update.length != size) {
144            throw new DimensionMismatchException(update.length, size);
145        }
146
147        // Get the internal reference. Note that this must not be a copy;
148        // otherwise the "compareAndSet" below will always fail.
149        final double[] current = features.get();
150        if (!containSameValues(current, expect)) {
151            // Some other thread already modified the state.
152            return false;
153        }
154
155        // Increment attempt counter.
156        numberOfAttemptedUpdates.incrementAndGet();
157
158        if (features.compareAndSet(current, update.clone())) {
159            // The current thread could atomically update the state (attempt succeeded).
160            numberOfSuccessfulUpdates.incrementAndGet();
161            return true;
162        } else {
163            // Some other thread came first (attempt failed).
164            return false;
165        }
166    }
167
168    /**
169     * Retrieves the number of calls to the
170     * {@link #compareAndSetFeatures(double[],double[]) compareAndSetFeatures}
171     * method.
172     * Note that if the caller wants to use this method in combination with
173     * {@link #getNumberOfSuccessfulUpdates()}, additional synchronization
174     * may be required to ensure consistency.
175     *
176     * @return the number of update attempts.
177     * @since 3.6
178     */
179    public long getNumberOfAttemptedUpdates() {
180        return numberOfAttemptedUpdates.get();
181    }
182
183    /**
184     * Retrieves the number of successful calls to the
185     * {@link #compareAndSetFeatures(double[],double[]) compareAndSetFeatures}
186     * method.
187     * Note that if the caller wants to use this method in combination with
188     * {@link #getNumberOfAttemptedUpdates()}, additional synchronization
189     * may be required to ensure consistency.
190     *
191     * @return the number of successful updates.
192     * @since 3.6
193     */
194    public long getNumberOfSuccessfulUpdates() {
195        return numberOfSuccessfulUpdates.get();
196    }
197
198    /**
199     * Checks whether the contents of both arrays is the same.
200     *
201     * @param current Current values.
202     * @param expect Expected values.
203     * @throws DimensionMismatchException if the length of {@code expected}
204     * is not the same as specified in the {@link #Neuron(long,double[])
205     * constructor}.
206     * @return {@code true} if the arrays contain the same values.
207     */
208    private boolean containSameValues(double[] current,
209                                      double[] expect) {
210        if (expect.length != size) {
211            throw new DimensionMismatchException(expect.length, size);
212        }
213
214        for (int i = 0; i < size; i++) {
215            if (!Precision.equals(current[i], expect[i])) {
216                return false;
217            }
218        }
219        return true;
220    }
221
222    /**
223     * Prevents proxy bypass.
224     *
225     * @param in Input stream.
226     */
227    private void readObject(ObjectInputStream in) {
228        throw new IllegalStateException();
229    }
230
231    /**
232     * Custom serialization.
233     *
234     * @return the proxy instance that will be actually serialized.
235     */
236    private Object writeReplace() {
237        return new SerializationProxy(identifier,
238                                      features.get());
239    }
240
241    /**
242     * Serialization.
243     */
244    private static class SerializationProxy implements Serializable {
245        /** Serializable. */
246        private static final long serialVersionUID = 20130207L;
247        /** Features. */
248        private final double[] features;
249        /** Identifier. */
250        private final long identifier;
251
252        /**
253         * @param identifier Identifier.
254         * @param features Features.
255         */
256        SerializationProxy(long identifier,
257                           double[] features) {
258            this.identifier = identifier;
259            this.features = features;
260        }
261
262        /**
263         * Custom serialization.
264         *
265         * @return the {@link Neuron} for which this instance is the proxy.
266         */
267        private Object readResolve() {
268            return new Neuron(identifier,
269                              features);
270        }
271    }
272}