1 package net.bmahe.genetics4j.neat.spec;
2
3 import java.util.Objects;
4
5 import org.apache.commons.lang3.Validate;
6 import org.immutables.value.Value;
7
8 import net.bmahe.genetics4j.core.spec.chromosome.ChromosomeSpec;
9
10 /**
11 * Specification for NEAT (NeuroEvolution of Augmenting Topologies) neural network chromosomes.
12 *
13 * <p>A specification defines the stable identities and vector order of the network's input and output nodes, the
14 * namespace available to hidden nodes, and the bounds applied to connection weights. Chromosome factories and genetic
15 * operators use the same {@link NeatNodeLayout} throughout an evolutionary run so structural mutations do not relabel
16 * existing nodes.
17 *
18 * <p>Contiguous layouts preserve the traditional NEAT numbering scheme. Inputs start at zero, outputs immediately
19 * follow the inputs, and hidden-node IDs start after the outputs:
20 *
21 * <pre>{@code
22 * NeatChromosomeSpec xorSpec = NeatChromosomeSpec.of(
23 * 2, // input IDs [0, 1]
24 * 1, // output ID [2]
25 * -1.0f, // minimum connection weight
26 * 1.0f); // maximum connection weight
27 * }</pre>
28 *
29 * <p>Explicit layouts support stable, ordered, non-contiguous external IDs and a dedicated hidden-node range:
30 *
31 * <pre>{@code
32 * NeatNodeLayout layout = NeatNodeLayout.explicit(
33 * List.of(10, 1_000, 1_001), // ordered input IDs
34 * List.of(102_000), // ordered output IDs
35 * 1_000_000, // hidden range start, inclusive
36 * 2_000_000); // hidden range end, exclusive
37 *
38 * NeatChromosomeSpec sparseSpec = new NeatChromosomeSpec.Builder().nodeLayout(layout)
39 * .minWeightValue(-2.0f)
40 * .maxWeightValue(2.0f)
41 * .build();
42 * }</pre>
43 *
44 * <p>Declaration order is significant: it defines how input and output vectors map to node IDs. Node IDs themselves
45 * are opaque identities and need not be contiguous. A valid layout contains at least one input and one output, uses
46 * unique non-negative external IDs, keeps input and output IDs disjoint, and reserves a non-empty hidden-node range
47 * that does not overlap external IDs.
48 *
49 * <p>The minimum connection weight must be strictly less than the maximum. Factories use these bounds when creating
50 * initial connections, and mutation operators use them when changing connection weights.
51 *
52 * @see NeatNodeLayout
53 * @see net.bmahe.genetics4j.neat.chromosomes.NeatChromosome
54 * @see net.bmahe.genetics4j.neat.chromosomes.factory.NeatConnectedChromosomeFactory
55 * @see net.bmahe.genetics4j.core.spec.EAConfiguration
56 */
57 @Value.Immutable
58 public abstract class NeatChromosomeSpec implements ChromosomeSpec {
59
60 /**
61 * Returns the stable external node identities and hidden-node namespace for chromosomes created from this
62 * specification.
63 *
64 * @return the node layout
65 */
66 @Value.Parameter
67 public abstract NeatNodeLayout nodeLayout();
68
69 /**
70 * Returns the minimum allowed connection weight.
71 *
72 * @return the inclusive lower bound for connection weights
73 */
74 @Value.Parameter
75 public abstract float minWeightValue();
76
77 /**
78 * Returns the maximum allowed connection weight.
79 *
80 * @return the inclusive upper bound for connection weights
81 */
82 @Value.Parameter
83 public abstract float maxWeightValue();
84
85 /**
86 * Returns the number of input nodes declared by {@link #nodeLayout()}.
87 *
88 * @return the positive number of input nodes
89 */
90 public int numInputs() {
91 return nodeLayout().numInputs();
92 }
93
94 /**
95 * Returns the number of output nodes declared by {@link #nodeLayout()}.
96 *
97 * @return the positive number of output nodes
98 */
99 public int numOutputs() {
100 return nodeLayout().numOutputs();
101 }
102
103 @Value.Check
104 protected void check() {
105 Objects.requireNonNull(nodeLayout());
106 Validate.isTrue(minWeightValue() < maxWeightValue(), "Minimum weight must be less than maximum weight");
107 }
108
109 /** Builds immutable NEAT chromosome specifications. */
110 public static class Builder extends ImmutableNeatChromosomeSpec.Builder {
111 }
112
113 /**
114 * Creates a specification using the supplied node layout and connection-weight bounds.
115 *
116 * @param nodeLayout stable external IDs and hidden-node namespace
117 * @param minWeightValue minimum allowed connection weight
118 * @param maxWeightValue maximum allowed connection weight
119 * @return a validated immutable chromosome specification
120 * @throws NullPointerException if {@code nodeLayout} is {@code null}
121 * @throws IllegalArgumentException if {@code minWeightValue} is not less than {@code maxWeightValue}
122 */
123 public static NeatChromosomeSpec of(final NeatNodeLayout nodeLayout, final float minWeightValue,
124 final float maxWeightValue) {
125 return new Builder().nodeLayout(nodeLayout).minWeightValue(minWeightValue).maxWeightValue(maxWeightValue).build();
126 }
127
128 /**
129 * Creates a specification using the traditional contiguous node layout.
130 *
131 * <p>Input IDs are {@code [0, numInputs)}, output IDs are {@code [numInputs, numInputs + numOutputs)}, and the
132 * hidden-node namespace starts at {@code numInputs + numOutputs}.
133 *
134 * @param numInputs number of input nodes; must be positive
135 * @param numOutputs number of output nodes; must be positive
136 * @param minWeightValue minimum allowed connection weight
137 * @param maxWeightValue maximum allowed connection weight
138 * @return a validated immutable chromosome specification
139 * @throws IllegalArgumentException if either node count is not positive or the weight bounds are not ordered
140 */
141 public static NeatChromosomeSpec of(final int numInputs, final int numOutputs, final float minWeightValue,
142 final float maxWeightValue) {
143 return of(NeatNodeLayout.contiguous(numInputs, numOutputs), minWeightValue, maxWeightValue);
144 }
145 }