NeatChromosomeSpec.java
package net.bmahe.genetics4j.neat.spec;
import org.apache.commons.lang3.Validate;
import org.immutables.value.Value;
import net.bmahe.genetics4j.core.spec.chromosome.ChromosomeSpec;
/**
* Specification for NEAT (NeuroEvolution of Augmenting Topologies) neural network chromosomes.
*
* <p>A specification defines the stable identities and vector order of the network's input and output nodes, the
* namespace available to hidden nodes, and the bounds applied to connection weights. Chromosome factories and genetic
* operators use the same {@link NeatNodeLayout} throughout an evolutionary run so structural mutations do not relabel
* existing nodes.
*
* <p>Contiguous layouts preserve the traditional NEAT numbering scheme. Inputs start at zero, outputs immediately
* follow the inputs, and hidden-node IDs start after the outputs:
*
* <pre>{@code
* NeatChromosomeSpec xorSpec = NeatChromosomeSpec.of(
* 2, // input IDs [0, 1]
* 1, // output ID [2]
* -1.0f, // minimum connection weight
* 1.0f); // maximum connection weight
* }</pre>
*
* <p>Explicit layouts support stable, ordered, non-contiguous external IDs and a dedicated hidden-node range:
*
* <pre>{@code
* NeatNodeLayout layout = NeatNodeLayout.explicit(
* List.of(10, 1_000, 1_001), // ordered input IDs
* List.of(102_000), // ordered output IDs
* 1_000_000, // hidden range start, inclusive
* 2_000_000); // hidden range end, exclusive
*
* NeatChromosomeSpec sparseSpec = new NeatChromosomeSpec.Builder().nodeLayout(layout)
* .minWeightValue(-2.0f)
* .maxWeightValue(2.0f)
* .build();
* }</pre>
*
* <p>Declaration order is significant: it defines how input and output vectors map to node IDs. Node IDs themselves
* are opaque identities and need not be contiguous. A valid layout contains at least one input and one output, uses
* unique non-negative external IDs, keeps input and output IDs disjoint, and reserves a non-empty hidden-node range
* that does not overlap external IDs.
*
* <p>The minimum connection weight must be strictly less than the maximum. Factories use these bounds when creating
* initial connections, and mutation operators use them when changing connection weights.
*
* @see NeatNodeLayout
* @see net.bmahe.genetics4j.neat.chromosomes.NeatChromosome
* @see net.bmahe.genetics4j.neat.chromosomes.factory.NeatConnectedChromosomeFactory
* @see net.bmahe.genetics4j.core.spec.EAConfiguration
*/
@Value.Immutable
public abstract class NeatChromosomeSpec implements ChromosomeSpec {
/**
* Returns the stable external node identities and hidden-node namespace for chromosomes created from this
* specification.
*
* @return the node layout
*/
@Value.Parameter
public abstract NeatNodeLayout nodeLayout();
/**
* Returns the minimum allowed connection weight.
*
* @return the inclusive lower bound for connection weights
*/
@Value.Parameter
public abstract float minWeightValue();
/**
* Returns the maximum allowed connection weight.
*
* @return the inclusive upper bound for connection weights
*/
@Value.Parameter
public abstract float maxWeightValue();
/**
* Returns the number of input nodes declared by {@link #nodeLayout()}.
*
* @return the positive number of input nodes
*/
public int numInputs() {
return nodeLayout().numInputs();
}
/**
* Returns the number of output nodes declared by {@link #nodeLayout()}.
*
* @return the positive number of output nodes
*/
public int numOutputs() {
return nodeLayout().numOutputs();
}
@Value.Check
protected void check() {
Validate.notNull(nodeLayout());
Validate.isTrue(minWeightValue() < maxWeightValue(), "Minimum weight must be less than maximum weight");
}
/** Builds immutable NEAT chromosome specifications. */
public static class Builder extends ImmutableNeatChromosomeSpec.Builder {
}
/**
* Creates a specification using the supplied node layout and connection-weight bounds.
*
* @param nodeLayout stable external IDs and hidden-node namespace
* @param minWeightValue minimum allowed connection weight
* @param maxWeightValue maximum allowed connection weight
* @return a validated immutable chromosome specification
* @throws NullPointerException if {@code nodeLayout} is {@code null}
* @throws IllegalArgumentException if {@code minWeightValue} is not less than {@code maxWeightValue}
*/
public static NeatChromosomeSpec of(final NeatNodeLayout nodeLayout, final float minWeightValue,
final float maxWeightValue) {
return new Builder().nodeLayout(nodeLayout).minWeightValue(minWeightValue).maxWeightValue(maxWeightValue).build();
}
/**
* Creates a specification using the traditional contiguous node layout.
*
* <p>Input IDs are {@code [0, numInputs)}, output IDs are {@code [numInputs, numInputs + numOutputs)}, and the
* hidden-node namespace starts at {@code numInputs + numOutputs}.
*
* @param numInputs number of input nodes; must be positive
* @param numOutputs number of output nodes; must be positive
* @param minWeightValue minimum allowed connection weight
* @param maxWeightValue maximum allowed connection weight
* @return a validated immutable chromosome specification
* @throws IllegalArgumentException if either node count is not positive or the weight bounds are not ordered
*/
public static NeatChromosomeSpec of(final int numInputs, final int numOutputs, final float minWeightValue,
final float maxWeightValue) {
return of(NeatNodeLayout.contiguous(numInputs, numOutputs), minWeightValue, maxWeightValue);
}
}