1 package net.bmahe.genetics4j.neat;
2
3 import java.util.Collection;
4 import java.util.Objects;
5 import java.util.Set;
6 import java.util.concurrent.ConcurrentHashMap;
7 import java.util.concurrent.atomic.AtomicLong;
8
9 import org.apache.commons.lang3.Validate;
10
11 import net.bmahe.genetics4j.neat.spec.NeatNodeLayout;
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 public class NodeIdManager {
29
30 private final NeatNodeLayout nodeLayout;
31 private final AtomicLong nextNodeId;
32 private final Set<Integer> allocatedNodeIds = ConcurrentHashMap.newKeySet();
33 private final ConcurrentHashMap<Integer, Integer> splitNodeIds = new ConcurrentHashMap<>();
34 private final ConcurrentHashMap<Integer, Integer> splitInnovationsByNodeId = new ConcurrentHashMap<>();
35
36
37
38
39
40
41
42 public NodeIdManager(final NeatNodeLayout _nodeLayout) {
43 nodeLayout = Objects.requireNonNull(_nodeLayout);
44 nextNodeId = new AtomicLong(nodeLayout.hiddenNodeIdStartInclusive());
45 }
46
47
48
49
50
51
52
53
54
55
56
57 public synchronized int nodeIdForSplit(final int connectionInnovation) {
58 Validate.isTrue(connectionInnovation >= 0, "Connection innovation must be non-negative");
59 return splitNodeIds.computeIfAbsent(connectionInnovation, ignored -> {
60 final int nodeId = allocateNodeId();
61 splitInnovationsByNodeId.put(nodeId, connectionInnovation);
62 return nodeId;
63 });
64 }
65
66
67
68
69
70
71
72
73
74
75
76 public synchronized void registerExistingNodeIds(final Collection<Integer> nodeIds) {
77 Objects.requireNonNull(nodeIds);
78 for (final Integer nodeId : nodeIds) {
79 Objects.requireNonNull(nodeId);
80 if (nodeLayout.isExternal(nodeId) == false) {
81 Validate.isTrue(nodeLayout.isHidden(nodeId), "Node ID %d is outside the layout namespaces", nodeId);
82 allocatedNodeIds.add(nodeId);
83 }
84 }
85 }
86
87
88
89
90
91
92
93
94
95 public synchronized void registerSplit(final int connectionInnovation, final int nodeId) {
96 Validate.isTrue(connectionInnovation >= 0, "Connection innovation must be non-negative");
97 Validate.isTrue(nodeLayout.isHidden(nodeId), "Node ID %d is outside the hidden-node range", nodeId);
98 final Integer existing = splitNodeIds.get(connectionInnovation);
99 if (existing != null && existing != nodeId) {
100 throw new IllegalArgumentException("Connection innovation %d is already mapped to hidden node %d"
101 .formatted(connectionInnovation, existing));
102 }
103 final Integer existingInnovation = splitInnovationsByNodeId.get(nodeId);
104 if (existingInnovation != null && existingInnovation != connectionInnovation) {
105 throw new IllegalArgumentException(
106 "Hidden node ID %d is already mapped to connection innovation %d".formatted(nodeId, existingInnovation));
107 }
108 splitNodeIds.put(connectionInnovation, nodeId);
109 splitInnovationsByNodeId.put(nodeId, connectionInnovation);
110 allocatedNodeIds.add(nodeId);
111 }
112
113 private int allocateNodeId() {
114 while (true) {
115 final long candidate = nextNodeId.getAndIncrement();
116 if (candidate >= nodeLayout.hiddenNodeIdEndExclusive()) {
117 throw new IllegalStateException("Hidden-node ID range [%d, %d) is exhausted"
118 .formatted(nodeLayout.hiddenNodeIdStartInclusive(), nodeLayout.hiddenNodeIdEndExclusive()));
119 }
120 final int nodeId = Math.toIntExact(candidate);
121 if (allocatedNodeIds.add(nodeId)) {
122 return nodeId;
123 }
124 }
125 }
126 }