NeatNodeLayout.java

package net.bmahe.genetics4j.neat.spec;

import java.util.List;

/**
 * Describes the stable external node identities and hidden-node namespace of a NEAT chromosome.
 *
 * <p>Input and output lists preserve declaration order. That order defines the mapping between external vectors and
 * node IDs, while the IDs themselves are opaque and may be sparse. The hidden-node range is half-open: its lower bound
 * is inclusive and its upper bound is exclusive.
 *
 * <p>Implementations must provide non-empty input and output lists containing unique, non-negative, disjoint IDs. No
 * external ID may fall inside the hidden-node range. Implementations should also provide value semantics when they are
 * used as chromosome layouts.
 *
 * @see ContiguousNeatNodeLayout
 * @see ExplicitNeatNodeLayout
 */
public interface NeatNodeLayout {

	/** Exclusive upper bound of the non-negative {@code int} node-ID domain. */
	long MAX_NODE_ID_EXCLUSIVE = (long) Integer.MAX_VALUE + 1L;

	/**
	 * Returns input node IDs in external vector order.
	 *
	 * @return an ordered, non-empty list of input node IDs
	 */
	List<Integer> inputNodeIds();

	/**
	 * Returns output node IDs in external vector order.
	 *
	 * @return an ordered, non-empty list of output node IDs
	 */
	List<Integer> outputNodeIds();

	/**
	 * Returns the first ID reserved for hidden nodes.
	 *
	 * @return the inclusive hidden-node range start
	 */
	long hiddenNodeIdStartInclusive();

	/**
	 * Returns the exclusive end of the hidden-node namespace.
	 *
	 * @return the exclusive hidden-node range end
	 */
	long hiddenNodeIdEndExclusive();

	/** @return the number of declared input nodes */
	default int numInputs() {
		return inputNodeIds().size();
	}

	/** @return the number of declared output nodes */
	default int numOutputs() {
		return outputNodeIds().size();
	}

	/**
	 * Tests whether an ID identifies an input node.
	 *
	 * @param nodeId node ID to test
	 * @return {@code true} when the ID is a declared input
	 */
	default boolean isInput(final int nodeId) {
		return inputNodeIds().contains(nodeId);
	}

	/**
	 * Tests whether an ID identifies an output node.
	 *
	 * @param nodeId node ID to test
	 * @return {@code true} when the ID is a declared output
	 */
	default boolean isOutput(final int nodeId) {
		return outputNodeIds().contains(nodeId);
	}

	/**
	 * Tests whether an ID identifies either an input or output node.
	 *
	 * @param nodeId node ID to test
	 * @return {@code true} when the ID is external
	 */
	default boolean isExternal(final int nodeId) {
		return isInput(nodeId) || isOutput(nodeId);
	}

	/**
	 * Tests whether an ID belongs to the reserved hidden-node range.
	 *
	 * @param nodeId node ID to test
	 * @return {@code true} when the ID is in the hidden-node namespace
	 */
	default boolean isHidden(final int nodeId) {
		return nodeId >= hiddenNodeIdStartInclusive() && nodeId < hiddenNodeIdEndExclusive();
	}

	/**
	 * Tests semantic compatibility with another layout.
	 *
	 * <p>Compatible layouts declare identical ordered input and output IDs and the same hidden-node range. Their
	 * concrete
	 * implementation classes do not need to match.
	 *
	 * @param other layout to compare
	 * @return {@code true} when chromosomes using the layouts can safely be combined
	 */
	default boolean isCompatibleWith(final NeatNodeLayout other) {
		return other != null && inputNodeIds().equals(other.inputNodeIds())
				&& outputNodeIds().equals(other.outputNodeIds())
				&& hiddenNodeIdStartInclusive() == other.hiddenNodeIdStartInclusive()
				&& hiddenNodeIdEndExclusive() == other.hiddenNodeIdEndExclusive();
	}

	/**
	 * Creates a traditional contiguous layout with the remainder of the non-negative {@code int} domain reserved for
	 * hidden nodes.
	 *
	 * @param numInputs  number of input nodes; must be positive
	 * @param numOutputs number of output nodes; must be positive
	 * @return a validated contiguous layout
	 */
	static NeatNodeLayout contiguous(final int numInputs, final int numOutputs) {
		final long hiddenStart = (long) numInputs + numOutputs;
		return contiguous(numInputs, numOutputs, hiddenStart, MAX_NODE_ID_EXCLUSIVE);
	}

	/**
	 * Creates a contiguous external layout with an explicit hidden-node range.
	 *
	 * @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
	 * @return a validated contiguous layout
	 */
	static NeatNodeLayout contiguous(final int numInputs, final int numOutputs, final long hiddenNodeIdStartInclusive,
			final long hiddenNodeIdEndExclusive) {
		return new ContiguousNeatNodeLayout(numInputs, numOutputs, hiddenNodeIdStartInclusive, hiddenNodeIdEndExclusive);
	}

	/**
	 * Creates a layout with explicitly ordered external IDs and a dedicated hidden-node range.
	 *
	 * @param inputNodeIds               input IDs in vector order
	 * @param outputNodeIds              output IDs in vector order
	 * @param hiddenNodeIdStartInclusive inclusive hidden-node range start
	 * @param hiddenNodeIdEndExclusive   exclusive hidden-node range end
	 * @return a validated explicit layout
	 */
	static NeatNodeLayout explicit(final List<Integer> inputNodeIds, final List<Integer> outputNodeIds,
			final long hiddenNodeIdStartInclusive, final long hiddenNodeIdEndExclusive) {
		return new ExplicitNeatNodeLayout(inputNodeIds,
				outputNodeIds,
				hiddenNodeIdStartInclusive,
				hiddenNodeIdEndExclusive);
	}
}