001/*
002 * Java Genetic Algorithm Library (jenetics-8.0.0).
003 * Copyright (c) 2007-2024 Franz Wilhelmstötter
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * 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 * Author:
018 *    Franz Wilhelmstötter (franz.wilhelmstoetter@gmail.com)
019 */
020package io.jenetics;
021
022import static java.util.Objects.requireNonNull;
023import static io.jenetics.internal.util.SerialIO.readInt;
024import static io.jenetics.internal.util.SerialIO.writeInt;
025
026import java.io.DataInput;
027import java.io.DataOutput;
028import java.io.IOException;
029import java.io.InvalidObjectException;
030import java.io.ObjectInputStream;
031import java.io.Serial;
032import java.io.Serializable;
033import java.util.function.Function;
034import java.util.stream.DoubleStream;
035import java.util.stream.IntStream;
036import java.util.stream.Stream;
037
038import io.jenetics.util.DoubleRange;
039import io.jenetics.util.ISeq;
040import io.jenetics.util.IntRange;
041import io.jenetics.util.MSeq;
042
043/**
044 * Numeric chromosome implementation which holds 64-bit floating point numbers.
045 *
046 * @see DoubleGene
047 *
048 * @implNote
049 * This class is immutable and thread-safe.
050 *
051 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
052 * @since 1.6
053 * @version 6.1
054 */
055public class DoubleChromosome
056        extends AbstractBoundedChromosome<Double, DoubleGene>
057        implements
058                NumericChromosome<Double, DoubleGene>,
059                Serializable
060{
061        @Serial
062        private static final long serialVersionUID = 3L;
063
064        /**
065         * Create a new chromosome from the given {@code genes} and the allowed
066         * length range of the chromosome.
067         *
068         * @since 4.0
069         *
070         * @param genes the genes that form the chromosome.
071         * @param lengthRange the allowed length range of the chromosome
072         * @throws NullPointerException if one of the arguments is {@code null}.
073         * @throws IllegalArgumentException if the length of the gene sequence is
074         *         empty, doesn't match with the allowed length range, the minimum
075         *         or maximum of the range is smaller or equal zero, or the given
076         *         range size is zero.
077         */
078        protected DoubleChromosome(
079                final ISeq<DoubleGene> genes,
080                final IntRange lengthRange
081        ) {
082                super(genes, lengthRange);
083        }
084
085        @Override
086        public DoubleChromosome newInstance(final ISeq<DoubleGene> genes) {
087                return new DoubleChromosome(genes, lengthRange());
088        }
089
090        @Override
091        public DoubleChromosome newInstance() {
092                return of(_min, _max, lengthRange());
093        }
094
095        /**
096         * Maps the gene alleles of this chromosome, given as {@code double[]} array,
097         * by applying the given mapper function {@code f}. The mapped gene values
098         * are then wrapped into a newly created chromosome.
099         *
100         * {@snippet lang="java":
101         * final DoubleChromosome chromosome = null; // @replace substring='null' replacement="..."
102         * final DoubleChromosome normalized = chromosome.map(Main::normalize);
103         *
104         * static double[] normalize(final double[] values) {
105         *     final double sum = sum(values);
106         *     for (int i = 0; i < values.length; ++i) {
107         *         values[i] /= sum;
108         *     }
109         *     return values;
110         * }
111         * }
112         *
113         * @since 6.1
114         *
115         * @param f the mapper function
116         * @return a newly created chromosome with the mapped gene values
117         * @throws NullPointerException if the mapper function is {@code null}.
118         * @throws IllegalArgumentException if the length of the mapped
119         *         {@code double[]} array is empty or doesn't match with the allowed
120         *         length range
121         */
122        public DoubleChromosome map(final Function<? super double[], double[]> f) {
123                requireNonNull(f);
124
125                final var range = DoubleRange.of(_min, _max);
126                final var genes = DoubleStream.of(f.apply(toArray()))
127                        .mapToObj(v -> DoubleGene.of(v, range))
128                        .collect(ISeq.toISeq());
129
130                return newInstance(genes);
131        }
132
133        /**
134         * Returns a sequential stream of the alleles with this chromosome as its
135         * source.
136         *
137         * @since 4.3
138         *
139         * @return a sequential stream of alleles
140         */
141        public DoubleStream doubleStream() {
142                return IntStream.range(0, length()).mapToDouble(this::doubleValue);
143        }
144
145        /**
146         * Returns a double array containing all the elements in this chromosome
147         * in a proper sequence.  If the chromosome fits in the specified array, it is
148         * returned therein. Otherwise, a new array is allocated with the length of
149         * this chromosome.
150         *
151         * @since 3.0
152         *
153         * @param array the array into which the elements of these chromosomes are to
154         *        be stored, if it is big enough; otherwise, a new array is
155         *        allocated for this purpose.
156         * @return an array containing the elements of this chromosome
157         * @throws NullPointerException if the given {@code array} is {@code null}
158         */
159        public double[] toArray(final double[] array) {
160                final double[] a = array.length >= length()
161                        ? array
162                        : new double[length()];
163
164                for (int i = length(); --i >= 0;) {
165                        a[i] = doubleValue(i);
166                }
167
168                return a;
169        }
170
171        /**
172         * Returns a double array containing all the elements in this chromosome
173         * in a proper sequence.
174         *
175         * @since 3.0
176         *
177         * @return an array containing the elements of this chromosome
178         */
179        public double[] toArray() {
180                return toArray(new double[length()]);
181        }
182
183
184        /* *************************************************************************
185         * Static factory methods.
186         * ************************************************************************/
187
188        /**
189         * Create a new {@code DoubleChromosome} with the given genes.
190         *
191         * @param genes the genes of the chromosome.
192         * @return a new chromosome with the given genes.
193         * @throws IllegalArgumentException if the length of the genes array is
194         *         empty or the given {@code genes} doesn't have the same range.
195         * @throws NullPointerException if the given {@code genes} array is
196         *         {@code null}
197         */
198        public static DoubleChromosome of(final DoubleGene... genes) {
199                checkGeneRange(Stream.of(genes).map(DoubleGene::range));
200                return new DoubleChromosome(ISeq.of(genes), IntRange.of(genes.length));
201        }
202
203        /**
204         * Create a new {@code DoubleChromosome} with the given genes.
205         *
206         * @since 4.3
207         *
208         * @param genes the genes of the chromosome.
209         * @return a new chromosome with the given genes.
210         * @throws NullPointerException if the given {@code genes} are {@code null}
211         * @throws IllegalArgumentException if the of the genes iterable is empty or
212         *         the given {@code genes} doesn't have the same range.
213         */
214        public static DoubleChromosome of(final Iterable<DoubleGene> genes) {
215                final ISeq<DoubleGene> values = ISeq.of(genes);
216                checkGeneRange(values.stream().map(DoubleGene::range));
217                return new DoubleChromosome(values, IntRange.of(values.length()));
218        }
219
220        /**
221         * Create a new random chromosome.
222         *
223         * @since 4.0
224         *
225         * @param min the min value of the {@link DoubleGene}s (inclusively).
226         * @param max the max value of the {@link DoubleGene}s (exclusively).
227         * @param lengthRange the allowed length range of the chromosome.
228         * @return a new {@code DoubleChromosome} with the given parameter
229         * @throws IllegalArgumentException if the length of the gene sequence is
230         *         empty, doesn't match with the allowed length range, the minimum
231         *         or maximum of the range is smaller or equal zero, or the given
232         *         range size is zero.
233         * @throws NullPointerException if the given {@code lengthRange} is
234         *         {@code null}
235         */
236        public static DoubleChromosome of(
237                final double min,
238                final double max,
239                final IntRange lengthRange
240        ) {
241                final ISeq<DoubleGene> genes = DoubleGene.seq(min, max, lengthRange);
242                return new DoubleChromosome(genes, lengthRange);
243        }
244
245        /**
246         * Create a new random {@code DoubleChromosome}.
247         *
248         * @param min the min value of the {@link DoubleGene}s (inclusively).
249         * @param max the max value of the {@link DoubleGene}s (exclusively).
250         * @param length the length of the chromosome.
251         * @return a new {@code DoubleChromosome} with the given parameter
252         * @throws IllegalArgumentException if the {@code length} is smaller than
253         *         one.
254         */
255        public static DoubleChromosome of(
256                final double min,
257                final double max,
258                final int length
259        ) {
260                return of(min, max, IntRange.of(length));
261        }
262
263        /**
264         * Create a new random chromosome.
265         *
266         * @since 4.0
267         *
268         * @param range the integer range of the chromosome.
269         * @param lengthRange the allowed length range of the chromosome.
270         * @return a new {@code DoubleChromosome} with the given parameter
271         * @throws IllegalArgumentException if the length of the gene sequence is
272         *         empty, doesn't match with the allowed length range, the minimum
273         *         or maximum of the range is smaller or equal zero, or the given
274         *         range size is zero.
275         * @throws NullPointerException if the given {@code lengthRange} is
276         *         {@code null}
277         */
278        public static DoubleChromosome of(
279                final DoubleRange range,
280                final IntRange lengthRange
281        ) {
282                return of(range.min(), range.max(), lengthRange);
283        }
284
285        /**
286         * Create a new random {@code DoubleChromosome}.
287         *
288         * @since 3.2
289         *
290         * @param range the integer range of the chromosome.
291         * @param length the length of the chromosome.
292         * @return a new random {@code DoubleChromosome}
293         * @throws NullPointerException if the given {@code range} is {@code null}
294         * @throws IllegalArgumentException if the {@code length} is smaller than
295         *         one.
296         */
297        public static DoubleChromosome of(final DoubleRange range, final int length) {
298                return of(range.min(), range.max(), length);
299        }
300
301        /**
302         * Create a new random {@code DoubleChromosome} of length one.
303         *
304         * @param min the minimal value of this chromosome (inclusively).
305         * @param max the maximal value of this chromosome (exclusively).
306         * @return a new {@code DoubleChromosome} with the given parameter
307         */
308        public static DoubleChromosome of(final double min, final double max) {
309                return of(min, max, 1);
310        }
311
312        /**
313         * Create a new random {@code DoubleChromosome} of length one.
314         *
315         * @since 3.2
316         *
317         * @param range the double range of the chromosome.
318         * @return a new random {@code DoubleChromosome} of length one
319         * @throws NullPointerException if the given {@code range} is {@code null}
320         */
321        public static DoubleChromosome of(final DoubleRange range) {
322                return of(range.min(), range.max());
323        }
324
325
326        /* *************************************************************************
327         *  Java object serialization
328         * ************************************************************************/
329
330        @Serial
331        private Object writeReplace() {
332                return new SerialProxy(SerialProxy.DOUBLE_CHROMOSOME, this);
333        }
334
335        @Serial
336        private void readObject(final ObjectInputStream stream)
337                throws InvalidObjectException
338        {
339                throw new InvalidObjectException("Serialization proxy required.");
340        }
341
342        void write(final DataOutput out) throws IOException {
343                writeInt(length(), out);
344                writeInt(lengthRange().min(), out);
345                writeInt(lengthRange().max(), out);
346                out.writeDouble(_min);
347                out.writeDouble(_max);
348
349                for (int i = 0, n = length(); i < n; ++i) {
350                        out.writeDouble(doubleValue(i));
351                }
352        }
353
354        static DoubleChromosome read(final DataInput in) throws IOException {
355                final var length = readInt(in);
356                final var lengthRange = IntRange.of(readInt(in), readInt(in));
357                final var min = in.readDouble();
358                final var max = in.readDouble();
359
360                final MSeq<DoubleGene> values = MSeq.ofLength(length);
361                for (int i = 0; i < length; ++i) {
362                        values.set(i, DoubleGene.of(in.readDouble(), min, max));
363                }
364
365                return new DoubleChromosome(values.toISeq(), lengthRange);
366        }
367
368}