View Javadoc
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 }