InnovationManager.java

1
package net.bmahe.genetics4j.neat;
2
3
import java.util.concurrent.ConcurrentHashMap;
4
import java.util.concurrent.atomic.AtomicInteger;
5
6
import org.apache.commons.lang3.Validate;
7
import org.apache.logging.log4j.LogManager;
8
import org.apache.logging.log4j.Logger;
9
10
/**
11
 * Manages innovation numbers for the NEAT (NeuroEvolution of Augmenting Topologies) algorithm.
12
 * 
13
 * <p>The InnovationManager is a critical component of the NEAT algorithm that tracks structural innovations in neural
14
 * networks through unique innovation numbers. This system enables NEAT to perform meaningful genetic crossover between
15
 * neural networks with different topologies while preserving historical information about when specific connections
16
 * were first added to the population.
17
 * 
18
 * <p>Key responsibilities:
19
 * <ul>
20
 * <li><strong>Innovation tracking</strong>: Assigns unique numbers to each new connection type (from-to node pair)</li>
21
 * <li><strong>Historical marking</strong>: Maintains consistent innovation numbers across the population</li>
22
 * <li><strong>Crossover support</strong>: Enables alignment of neural network structures during recombination</li>
23
 * <li><strong>Cache management</strong>: Provides efficient lookup and generation of innovation numbers</li>
24
 * </ul>
25
 * 
26
 * <p>NEAT innovation number system:
27
 * <ul>
28
 * <li><strong>Unique identification</strong>: Each unique connection (from-node → to-node) gets one innovation
29
 * number</li>
30
 * <li><strong>Population consistency</strong>: Same connection type across individuals gets same innovation number</li>
31
 * <li><strong>Temporal ordering</strong>: Innovation numbers reflect the historical order of structural mutations</li>
32
 * <li><strong>Crossover alignment</strong>: Enables gene alignment during genetic recombination</li>
33
 * </ul>
34
 * 
35
 * <p>Innovation number workflow:
36
 * <ol>
37
 * <li><strong>Mutation occurs</strong>: Add-connection mutation creates new connection type</li>
38
 * <li><strong>Innovation check</strong>: Manager checks if this connection type was seen before</li>
39
 * <li><strong>Number assignment</strong>: New types get new innovation numbers, existing types reuse numbers</li>
40
 * <li><strong>Population tracking</strong>: All individuals with same connection type share same innovation number</li>
41
 * <li><strong>Crossover alignment</strong>: Innovation numbers enable proper gene alignment during recombination</li>
42
 * </ol>
43
 * 
44
 * <p>Common usage patterns:
45
 * 
46
 * <pre>{@code
47
 * // Create innovation manager for new evolution run
48
 * InnovationManager innovationManager = new InnovationManager();
49
 * 
50
 * // During add-connection mutation
51
 * int fromNode = 0, toNode = 3;
52
 * int innovationNumber = innovationManager.computeNewId(fromNode, toNode);
53
 * Connection newConnection = Connection.of(fromNode, toNode, weight, innovationNumber, true);
54
 * 
55
 * // Reset cache between generations if needed
56
 * innovationManager.resetCache();
57
 * 
58
 * // Start with specific innovation number
59
 * InnovationManager manager = new InnovationManager(1000);
60
 * }</pre>
61
 * 
62
 * <p>Cache management strategy:
63
 * <ul>
64
 * <li><strong>Per-generation caching</strong>: Cache innovation numbers within each generation</li>
65
 * <li><strong>Cross-generation persistence</strong>: Innovation numbers remain consistent across generations</li>
66
 * <li><strong>Memory management</strong>: Reset cache periodically to prevent memory growth</li>
67
 * <li><strong>Concurrent access</strong>: Thread-safe operations for parallel genetic operations</li>
68
 * </ul>
69
 * 
70
 * <p>Performance considerations:
71
 * <ul>
72
 * <li><strong>O(1) lookup</strong>: Fast innovation number retrieval through hash map caching</li>
73
 * <li><strong>Memory efficiency</strong>: Cache only unique connection types seen in current generation</li>
74
 * <li><strong>Thread safety</strong>: Concurrent operations supported for parallel evolution</li>
75
 * <li><strong>Cache lifecycle</strong>: Reset cache between evolution runs to prevent memory leaks</li>
76
 * </ul>
77
 * 
78
 * <p>Integration with NEAT algorithm:
79
 * <ul>
80
 * <li><strong>Structural mutations</strong>: Add-connection mutations use innovation manager</li>
81
 * <li><strong>Genetic crossover</strong>: Innovation numbers enable gene alignment</li>
82
 * <li><strong>Compatibility distance</strong>: Innovation numbers used to identify matching, excess, and disjoint
83
 * genes</li>
84
 * <li><strong>Population management</strong>: Shared innovation manager across entire population</li>
85
 * </ul>
86
 * 
87
 * @see Connection
88
 * @see ConnectionPair
89
 * @see NeatChromosome
90
 * @see net.bmahe.genetics4j.neat.mutation.AddConnectionPolicyHandler
91
 */
92
public class InnovationManager {
93
	public static final Logger logger = LogManager.getLogger(InnovationManager.class);
94
95
	public static final int DEFAULT_INITIAL_ID = 0;
96
97
	private final AtomicInteger currentId;
98
99 2 1. <init> : Removed assignment to member variable innovationCache → KILLED
2. <init> : removed call to java/util/concurrent/ConcurrentHashMap::<init> → KILLED
	private final ConcurrentHashMap<ConnectionPair, Integer> innovationCache = new ConcurrentHashMap<>();
100
101
	/**
102
	 * Constructs an innovation manager with the specified initial innovation number.
103
	 * 
104
	 * @param initialValue the starting innovation number for new innovations
105
	 */
106
	public InnovationManager(final int initialValue) {
107 2 1. <init> : Removed assignment to member variable currentId → KILLED
2. <init> : removed call to java/util/concurrent/atomic/AtomicInteger::<init> → KILLED
		currentId = new AtomicInteger(initialValue);
108
	}
109
110
	/**
111
	 * Constructs an innovation manager with the default initial innovation number (0).
112
	 */
113
	public InnovationManager() {
114 1 1. <init> : Substituted 0 with 1 → SURVIVED
		this(DEFAULT_INITIAL_ID);
115
	}
116
117
	/**
118
	 * Computes or retrieves the innovation number for a connection between two nodes.
119
	 * 
120
	 * <p>If this connection type (from-node → to-node) has been seen before, returns the existing innovation number from
121
	 * the cache. Otherwise, generates a new innovation number and caches it for future use. This ensures that the same
122
	 * connection type across different individuals in the population receives the same innovation number.
123
	 * 
124
	 * @param from the source node index of the connection
125
	 * @param to   the target node index of the connection
126
	 * @return the innovation number for this connection type
127
	 * @throws IllegalArgumentException if from equals to (self-connections not allowed)
128
	 */
129
	public int computeNewId(final int from, final int to) {
130
		Validate.isTrue(from != to);
131
132 1 1. computeNewId : removed call to net/bmahe/genetics4j/neat/ConnectionPair::<init> → KILLED
		final var connectionPair = new ConnectionPair(from, to);
133 7 1. computeNewId : removed call to java/lang/Integer::intValue → KILLED
2. computeNewId : removed call to java/util/concurrent/ConcurrentHashMap::computeIfAbsent → KILLED
3. lambda$computeNewId$0 : removed call to java/util/concurrent/atomic/AtomicInteger::getAndIncrement → KILLED
4. lambda$computeNewId$0 : removed call to java/lang/Integer::valueOf → KILLED
5. lambda$computeNewId$0 : replaced Integer return value with 0 for net/bmahe/genetics4j/neat/InnovationManager::lambda$computeNewId$0 → KILLED
6. computeNewId : replaced call to java/util/concurrent/ConcurrentHashMap::computeIfAbsent with argument → KILLED
7. computeNewId : replaced int return with 0 for net/bmahe/genetics4j/neat/InnovationManager::computeNewId → KILLED
		return innovationCache.computeIfAbsent(connectionPair, k -> currentId.getAndIncrement());
134
	}
135
136
	/**
137
	 * Resets the innovation cache, clearing all cached connection-to-innovation-number mappings.
138
	 * 
139
	 * <p>This method should be called between evolution runs or generations to prevent memory growth and ensure that
140
	 * innovation number assignment starts fresh. Note that this does not reset the current innovation number counter, so
141
	 * new innovations will continue to receive unique numbers.
142
	 */
143
	public void resetCache() {
144
		logger.trace("Resetting cache with currently {} entries", innovationCache.size());
145 1 1. resetCache : removed call to java/util/concurrent/ConcurrentHashMap::clear → KILLED
		innovationCache.clear();
146
	}
147
}

Mutations

99

1.1
Location : <init>
Killed by : net.bmahe.genetics4j.neat.InnovationManagerTest.[engine:junit-jupiter]/[class:net.bmahe.genetics4j.neat.InnovationManagerTest]/[method:simple()]
Removed assignment to member variable innovationCache → KILLED

2.2
Location : <init>
Killed by : net.bmahe.genetics4j.neat.InnovationManagerTest.[engine:junit-jupiter]/[class:net.bmahe.genetics4j.neat.InnovationManagerTest]/[method:simple()]
removed call to java/util/concurrent/ConcurrentHashMap::<init> → KILLED

107

1.1
Location : <init>
Killed by : net.bmahe.genetics4j.neat.InnovationManagerTest.[engine:junit-jupiter]/[class:net.bmahe.genetics4j.neat.InnovationManagerTest]/[method:simple()]
Removed assignment to member variable currentId → KILLED

2.2
Location : <init>
Killed by : net.bmahe.genetics4j.neat.InnovationManagerTest.[engine:junit-jupiter]/[class:net.bmahe.genetics4j.neat.InnovationManagerTest]/[method:simple()]
removed call to java/util/concurrent/atomic/AtomicInteger::<init> → KILLED

114

1.1
Location : <init>
Killed by : none
Substituted 0 with 1 → SURVIVED
Covering tests

132

1.1
Location : computeNewId
Killed by : net.bmahe.genetics4j.neat.InnovationManagerTest.[engine:junit-jupiter]/[class:net.bmahe.genetics4j.neat.InnovationManagerTest]/[method:simple()]
removed call to net/bmahe/genetics4j/neat/ConnectionPair::<init> → KILLED

133

1.1
Location : computeNewId
Killed by : net.bmahe.genetics4j.neat.InnovationManagerTest.[engine:junit-jupiter]/[class:net.bmahe.genetics4j.neat.InnovationManagerTest]/[method:simple()]
removed call to java/lang/Integer::intValue → KILLED

2.2
Location : computeNewId
Killed by : net.bmahe.genetics4j.neat.InnovationManagerTest.[engine:junit-jupiter]/[class:net.bmahe.genetics4j.neat.InnovationManagerTest]/[method:simple()]
removed call to java/util/concurrent/ConcurrentHashMap::computeIfAbsent → KILLED

3.3
Location : lambda$computeNewId$0
Killed by : net.bmahe.genetics4j.neat.InnovationManagerTest.[engine:junit-jupiter]/[class:net.bmahe.genetics4j.neat.InnovationManagerTest]/[method:simple()]
removed call to java/util/concurrent/atomic/AtomicInteger::getAndIncrement → KILLED

4.4
Location : lambda$computeNewId$0
Killed by : net.bmahe.genetics4j.neat.InnovationManagerTest.[engine:junit-jupiter]/[class:net.bmahe.genetics4j.neat.InnovationManagerTest]/[method:simple()]
removed call to java/lang/Integer::valueOf → KILLED

5.5
Location : lambda$computeNewId$0
Killed by : net.bmahe.genetics4j.neat.InnovationManagerTest.[engine:junit-jupiter]/[class:net.bmahe.genetics4j.neat.InnovationManagerTest]/[method:simple()]
replaced Integer return value with 0 for net/bmahe/genetics4j/neat/InnovationManager::lambda$computeNewId$0 → KILLED

6.6
Location : computeNewId
Killed by : net.bmahe.genetics4j.neat.InnovationManagerTest.[engine:junit-jupiter]/[class:net.bmahe.genetics4j.neat.InnovationManagerTest]/[method:simple()]
replaced call to java/util/concurrent/ConcurrentHashMap::computeIfAbsent with argument → KILLED

7.7
Location : computeNewId
Killed by : net.bmahe.genetics4j.neat.InnovationManagerTest.[engine:junit-jupiter]/[class:net.bmahe.genetics4j.neat.InnovationManagerTest]/[method:simple()]
replaced int return with 0 for net/bmahe/genetics4j/neat/InnovationManager::computeNewId → KILLED

145

1.1
Location : resetCache
Killed by : net.bmahe.genetics4j.neat.InnovationManagerTest.[engine:junit-jupiter]/[class:net.bmahe.genetics4j.neat.InnovationManagerTest]/[method:simple()]
removed call to java/util/concurrent/ConcurrentHashMap::clear → KILLED

Active mutators

Tests examined


Report generated by PIT 1.20.3