ContiguousNeatNodeLayout.java

package net.bmahe.genetics4j.neat.spec;

import java.util.List;
import java.util.Objects;
import java.util.stream.IntStream;

import org.apache.commons.lang3.Validate;

/**
 * Node layout using the traditional contiguous NEAT external IDs.
 *
 * <p>Inputs occupy {@code [0, numInputs)} and outputs occupy {@code [numInputs, numInputs + numOutputs)}. The
 * configured
 * hidden-node range must not overlap those external IDs.
 */
public class ContiguousNeatNodeLayout implements NeatNodeLayout {

	private final int numInputs;
	private final int numOutputs;
	private final long hiddenNodeIdStartInclusive;
	private final long hiddenNodeIdEndExclusive;

	/**
	 * Creates a contiguous node layout.
	 *
	 * @param _numInputs                  number of input nodes; must be positive
	 * @param _numOutputs                 number of output nodes; must be positive
	 * @param _hiddenNodeIdStartInclusive inclusive hidden-node range start
	 * @param _hiddenNodeIdEndExclusive   exclusive hidden-node range end
	 * @throws IllegalArgumentException if the counts or hidden-node range are invalid, or if the ranges overlap
	 */
	public ContiguousNeatNodeLayout(final int _numInputs,
			final int _numOutputs,
			final long _hiddenNodeIdStartInclusive,
			final long _hiddenNodeIdEndExclusive) {
		Validate.isTrue(_numInputs > 0, "Number of inputs must be positive");
		Validate.isTrue(_numOutputs > 0, "Number of outputs must be positive");
		final long externalEnd = (long) _numInputs + _numOutputs;
		Validate.isTrue(externalEnd <= Integer.MAX_VALUE, "External node IDs exceed the int domain");
		NeatNodeLayoutValidation.validate(
				IntStream.range(0, _numInputs).boxed().toList(),
					IntStream.range(_numInputs, _numInputs + _numOutputs).boxed().toList(),
					_hiddenNodeIdStartInclusive,
					_hiddenNodeIdEndExclusive);

		numInputs = _numInputs;
		numOutputs = _numOutputs;
		hiddenNodeIdStartInclusive = _hiddenNodeIdStartInclusive;
		hiddenNodeIdEndExclusive = _hiddenNodeIdEndExclusive;
	}

	@Override
	public int numInputs() {
		return numInputs;
	}

	@Override
	public int numOutputs() {
		return numOutputs;
	}

	@Override
	public long hiddenNodeIdStartInclusive() {
		return hiddenNodeIdStartInclusive;
	}

	@Override
	public long hiddenNodeIdEndExclusive() {
		return hiddenNodeIdEndExclusive;
	}

	@Override
	public List<Integer> inputNodeIds() {
		return IntStream.range(0, numInputs).boxed().toList();
	}

	@Override
	public List<Integer> outputNodeIds() {
		return IntStream.range(numInputs, numInputs + numOutputs).boxed().toList();
	}

	@Override
	public boolean isInput(final int nodeId) {
		return nodeId >= 0 && nodeId < numInputs;
	}

	@Override
	public boolean isOutput(final int nodeId) {
		return nodeId >= numInputs && nodeId < numInputs + numOutputs;
	}

	@Override
	public int hashCode() {
		return Objects.hash(hiddenNodeIdEndExclusive, hiddenNodeIdStartInclusive, numInputs, numOutputs);
	}

	@Override
	public boolean equals(final Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj instanceof ContiguousNeatNodeLayout other) {
			return hiddenNodeIdEndExclusive == other.hiddenNodeIdEndExclusive
					&& hiddenNodeIdStartInclusive == other.hiddenNodeIdStartInclusive && numInputs == other.numInputs
					&& numOutputs == other.numOutputs;
		}
		return false;
	}

	@Override
	public String toString() {
		return "ContiguousNeatNodeLayout [numInputs=" + numInputs + ", numOutputs=" + numOutputs
				+ ", hiddenNodeIdStartInclusive=" + hiddenNodeIdStartInclusive + ", hiddenNodeIdEndExclusive="
				+ hiddenNodeIdEndExclusive + "]";
	}
}