diff --git a/Editor/ClusterInspector.cs b/Editor/ClusterInspector.cs
index 4e4d684..2b94851 100644
--- a/Editor/ClusterInspector.cs
+++ b/Editor/ClusterInspector.cs
@@ -188,21 +188,25 @@ namespace NanoBrain {
anythingChanged = true;
}
EditorGUILayout.EndHorizontal();
-
- } else if (this.currentNucleus is Cluster cluster && cluster.clusterArray != null) {
+
+ }
+ else if (this.currentNucleus is Cluster cluster) {
EditorGUILayout.BeginHorizontal();
- EditorGUILayout.IntField("Array size", cluster.clusterArray.clusters.Count());
+ if (cluster.siblingClusters != null && cluster.siblingClusters.Length > 1)
+ EditorGUILayout.IntField("Array size", cluster.siblingClusters.Count());
+ else
+ EditorGUILayout.IntField("Array size", 1);
if (GUILayout.Button("Add")) {
Undo.RecordObject(prefabAsset, "Array add " + prefabAsset.name);
- cluster.clusterArray.Add(this.prefab);
+ cluster.AddInstance(this.prefab);
anythingChanged = true;
}
if (GUILayout.Button("Del")) {
Undo.RecordObject(prefabAsset, "Array delete " + prefabAsset.name);
- cluster.clusterArray.Remove();
+ cluster.RemoveInstance();
anythingChanged = true;
}
- EditorGUILayout.EndHorizontal();
+ EditorGUILayout.EndHorizontal();
}
// Synapses
@@ -408,12 +412,12 @@ namespace NanoBrain {
case Nucleus.Type.Receptor:
AddReceptorInput(nucleus);
break;
- case Nucleus.Type.ClusterReceptor:
- AddClusterReceptorInput(nucleus);
- break;
- case Nucleus.Type.ClusterArray:
- AddClusterArrayInput(nucleus);
- break;
+ // case Nucleus.Type.ClusterReceptor:
+ // AddClusterReceptorInput(nucleus);
+ // break;
+ // case Nucleus.Type.ClusterArray:
+ // AddClusterArrayInput(nucleus);
+ // break;
default:
break;
}
@@ -465,12 +469,12 @@ namespace NanoBrain {
var editor = Editor.CreateEditor(subCluster.prefab);
}
- protected virtual void AddClusterArrayInput(Nucleus nucleus) {
- ClusterPickerWindow.ShowPicker(prefab => OnPickedClusterArray(nucleus, prefab), "Select Cluster");
- }
- private void OnPickedClusterArray(Nucleus nucleus, ClusterPrefab selectedPrefab) {
- _ = new ClusterArray(selectedPrefab, this.prefab, 1, nucleus);
- }
+ // protected virtual void AddClusterArrayInput(Nucleus nucleus) {
+ // ClusterPickerWindow.ShowPicker(prefab => OnPickedClusterArray(nucleus, prefab), "Select Cluster");
+ // }
+ // private void OnPickedClusterArray(Nucleus nucleus, ClusterPrefab selectedPrefab) {
+ // _ = new ClusterArray(selectedPrefab, this.prefab, 1, nucleus);
+ // }
int selectedConnectNucleus = -1;
// Connect to another nucleus in the same cluster
diff --git a/Editor/ClusterViewer.cs b/Editor/ClusterViewer.cs
index dbc6984..c2f55c8 100644
--- a/Editor/ClusterViewer.cs
+++ b/Editor/ClusterViewer.cs
@@ -331,12 +331,16 @@ namespace NanoBrain {
continue;
drawnArrays.Add(clusterReceptor.nucleiArray);
}
- // Oops...
- else if (synapse.neuron.parent is Cluster cluster && cluster.clusterArray != null) {
- if (drawnArrays.Contains(cluster.clusterArray.clusters))
+ else if (synapse.neuron.parent is Cluster cluster && cluster.siblingClusters != null) {
+ if (drawnArrays.Contains(cluster.siblingClusters))
continue;
- drawnArrays.Add(cluster.clusterArray.clusters);
+ drawnArrays.Add(cluster.siblingClusters);
}
+ // else if (synapse.neuron.parent is Cluster cluster && cluster.clusterArray != null) {
+ // if (drawnArrays.Contains(cluster.clusterArray.clusters))
+ // continue;
+ // drawnArrays.Add(cluster.clusterArray.clusters);
+ // }
if (synapse.neuron is Neuron synapseNeuron) {
float value = synapseNeuron.outputMagnitude * synapse.weight;
// Debug.Log($"{synapse.nucleus.name}: {value} {length(synapse.nucleus.outputValue)} {synapse.weight}");
@@ -445,7 +449,7 @@ namespace NanoBrain {
style.normal.textColor = Color.white;
}
}
- else if (nucleus is Cluster cluster && cluster.clusterArray != null) {
+ else if (nucleus is Cluster cluster) {
if (expandArray) {
// Put array indices above elements
style.alignment = TextAnchor.LowerCenter;
@@ -457,13 +461,15 @@ namespace NanoBrain {
}
}
else {
- // draw the array size label
- if (color.grayscale > 0.5f)
- style.normal.textColor = Color.black;
- else
+ if (cluster.siblingClusters != null && cluster.siblingClusters.Length > 1) {
+ // draw the array size label
+ if (color.grayscale > 0.5f)
+ style.normal.textColor = Color.black;
+ else
+ style.normal.textColor = Color.white;
+ Handles.Label(labelPosition, cluster.siblingClusters.Length.ToString(), style);
style.normal.textColor = Color.white;
- Handles.Label(labelPosition, cluster.clusterArray.clusters.Length.ToString(), style);
- style.normal.textColor = Color.white;
+ }
}
}
diff --git a/Runtime/Scripts/Core/Cluster.cs b/Runtime/Scripts/Core/Cluster.cs
index 4626320..c96ea65 100644
--- a/Runtime/Scripts/Core/Cluster.cs
+++ b/Runtime/Scripts/Core/Cluster.cs
@@ -8,538 +8,634 @@ using static Unity.Mathematics.math;
namespace NanoBrain {
-///
-/// A Cluster combines a collection of Nuclei to implement reusable behaviour
-///
-/// A Cluster is an instantiation of a ClusterPrefab.
-/// Clusters can be nested inside other clusters.
-[Serializable]
-public class Cluster : Nucleus {
-
///
- /// The base name of the cluster. I don't think this is actively used at this moment
+ /// A Cluster combines a collection of Nuclei to implement reusable behaviour
///
- public string baseName {
- get {
- int colonPositon = this.name.IndexOf(':');
- if (colonPositon < 0)
- return this.name;
- return this.name[..colonPositon];
- }
- }
+ /// A Cluster is an instantiation of a ClusterPrefab.
+ /// Clusters can be nested inside other clusters.
+ [Serializable]
+ public class Cluster : Nucleus {
- [SerializeReference]
- public ClusterArray clusterArray;
-
- #region Init
-
- ///
- /// Instantiate a new copy of a ClusterPrefab in the given parent
- ///
- /// The prefab to use
- /// The cluster in which this new cluster will be placed
- public Cluster(ClusterPrefab prefab, Cluster parent) {
- this.prefab = prefab;
- this.name = prefab.name;
-
- this.parent = parent;
- this.parent?.clusterNuclei.Add(this);
-
- ClonePrefab();
- _ = this.inputs;
- this.sortedNuclei = TopologicalSort(this.clusterNuclei);
- }
-
- ///
- /// Add a new cluster to a ClusterPrefab
- ///
- /// The prefab to copy
- /// The prefab in which the new copy is placed
- public Cluster(ClusterPrefab prefab, ClusterPrefab parent = null) {
- this.prefab = prefab;
- this.name = prefab.name;
- this.clusterPrefab = parent;
-
- if (this.clusterPrefab != null)
- this.clusterPrefab.nuclei.Add(this);
-
- ClonePrefab();
- _ = this.inputs;
- this.sortedNuclei = TopologicalSort(this.clusterNuclei);
- }
-
- ///
- /// Clone a prefab.
- ///
- /// Strange that this does not take any parameters or return values.
- /// Where which the clone be found???
- private void ClonePrefab() {
- Nucleus[] prefabNuclei = this.prefab.nuclei.ToArray();
- // first clone the nuclei without their connections
- foreach (Nucleus nucleus in this.prefab.nuclei) {
- nucleus.ShallowCloneTo(this);
- }
- Nucleus[] clonedNuclei = this.clusterNuclei.ToArray();
-
- // Now clone the connections
- for (int nucleusIx = 0; nucleusIx < prefabNuclei.Length; nucleusIx++) {
- Nucleus prefabNucleus = prefabNuclei[nucleusIx];
- if (prefabNucleus is not Neuron prefabNeuron)
- continue;
-
- Nucleus clonedNucleus = clonedNuclei[nucleusIx];
- if (clonedNucleus == null || clonedNucleus is not Neuron clonedNeuron)
- continue;
-
- // Copy the receivers, which will also create the synapses
- // Clusters do not have receivers...
- foreach (Nucleus receiver in prefabNeuron.receivers.ToArray()) {
- int ix = GetNucleusIndex(prefabNuclei, receiver);
- if (ix < 0)
- continue;
-
- if (clonedNuclei[ix] is not Nucleus clonedReceiver)
- continue;
-
- // Find the synapse for the weight
- float weight = 1;
- foreach (Synapse synapse in receiver.synapses) {
- // Find the weight for this synapse
- if (synapse.neuron == prefabNucleus) {
- weight = synapse.weight;
- break;
- }
- }
-
- clonedNeuron.AddReceiver(clonedReceiver, weight);
+ ///
+ /// The base name of the cluster. I don't think this is actively used at this moment
+ ///
+ public string baseName {
+ get {
+ int colonPositon = this.name.IndexOf(':');
+ if (colonPositon < 0)
+ return this.name;
+ return this.name[..colonPositon];
}
}
- // Copy nucleus arrays for receptors
- for (int nucleusIx = 0; nucleusIx < prefabNuclei.Length; nucleusIx++) {
- Nucleus prefabNucleus = prefabNuclei[nucleusIx];
- if (prefabNucleus is not IReceptor prefabReceptor)
- continue;
+ //[SerializeReference]
+ //public ClusterArray clusterArray;
+ [SerializeReference]
+ public Cluster[] siblingClusters;
+ public Dictionary thingClusters = new();
- if (prefabReceptor.nucleiArray == null || prefabReceptor.nucleiArray.Length == 0)
- continue;
+ #region Init
- IReceptor clonedNucleus = clonedNuclei[nucleusIx] as IReceptor;
- if (prefabReceptor == prefabReceptor.nucleiArray[0]) {
- // We clone the array only for the first entry
- NucleusArray clonedArray = new(prefabReceptor.nucleiArray.Length);
- int arrayIx = 0;
- foreach (Nucleus prefabArrayNucleus in prefabReceptor.nucleiArray) {
- int arrayNucleusIx = GetNucleusIndex(prefabNuclei, prefabArrayNucleus);
- if (arrayNucleusIx >= 0) {
- Nucleus clonedArrayNucleus = clonedNuclei[arrayNucleusIx];
- clonedArray.nuclei[arrayIx] = clonedArrayNucleus;
+ ///
+ /// Instantiate a new copy of a ClusterPrefab in the given parent
+ ///
+ /// The prefab to use
+ /// The cluster in which this new cluster will be placed
+ public Cluster(ClusterPrefab prefab, Cluster parent) {
+ this.prefab = prefab;
+ this.name = prefab.name;
+
+ this.parent = parent;
+ this.parent?.clusterNuclei.Add(this);
+ ClonePrefab();
+ _ = this.inputs;
+ this.sortedNuclei = TopologicalSort(this.clusterNuclei);
+ }
+
+ ///
+ /// Add a new cluster to a ClusterPrefab
+ ///
+ /// The prefab to copy
+ /// The prefab in which the new copy is placed
+ public Cluster(ClusterPrefab prefab, ClusterPrefab parent = null) {
+ this.prefab = prefab;
+ this.name = prefab.name;
+ this.clusterPrefab = parent;
+
+ if (this.clusterPrefab != null)
+ this.clusterPrefab.nuclei.Add(this);
+
+ ClonePrefab();
+ _ = this.inputs;
+ this.sortedNuclei = TopologicalSort(this.clusterNuclei);
+ }
+
+ ///
+ /// Clone a prefab.
+ ///
+ /// Strange that this does not take any parameters or return values.
+ /// Where which the clone be found???
+ private void ClonePrefab() {
+ Nucleus[] prefabNuclei = this.prefab.nuclei.ToArray();
+ // first clone the nuclei without their connections
+ foreach (Nucleus nucleus in this.prefab.nuclei) {
+ nucleus.ShallowCloneTo(this);
+ }
+ Nucleus[] clonedNuclei = this.clusterNuclei.ToArray();
+
+ // Now clone the connections
+ for (int nucleusIx = 0; nucleusIx < prefabNuclei.Length; nucleusIx++) {
+ Nucleus prefabNucleus = prefabNuclei[nucleusIx];
+ if (prefabNucleus is not Neuron prefabNeuron)
+ continue;
+
+ Nucleus clonedNucleus = clonedNuclei[nucleusIx];
+ if (clonedNucleus == null || clonedNucleus is not Neuron clonedNeuron)
+ continue;
+
+ // Copy the receivers, which will also create the synapses
+ // Clusters do not have receivers...
+ foreach (Nucleus receiver in prefabNeuron.receivers.ToArray()) {
+ int ix = GetNucleusIndex(prefabNuclei, receiver);
+ if (ix < 0)
+ continue;
+
+ if (clonedNuclei[ix] is not Nucleus clonedReceiver)
+ continue;
+
+ // Find the synapse for the weight
+ float weight = 1;
+ foreach (Synapse synapse in receiver.synapses) {
+ // Find the weight for this synapse
+ if (synapse.neuron == prefabNucleus) {
+ weight = synapse.weight;
+ break;
+ }
}
- else {
- Debug.LogError($" Could not find prefab nucleus {prefabNucleus.name} in the clones");
- }
- arrayIx++;
+
+ clonedNeuron.AddReceiver(clonedReceiver, weight);
}
- //clonedNucleus.array = clonedArray;
- clonedNucleus.nucleiArray = clonedArray.nuclei;
+ }
+
+ // Copy nucleus arrays for receptors
+ for (int nucleusIx = 0; nucleusIx < prefabNuclei.Length; nucleusIx++) {
+ Nucleus prefabNucleus = prefabNuclei[nucleusIx];
+ if (prefabNucleus is not IReceptor prefabReceptor)
+ continue;
+
+ if (prefabReceptor.nucleiArray == null || prefabReceptor.nucleiArray.Length == 0)
+ continue;
+
+ IReceptor clonedNucleus = clonedNuclei[nucleusIx] as IReceptor;
+ if (prefabReceptor == prefabReceptor.nucleiArray[0]) {
+ // We clone the array only for the first entry
+ NucleusArray clonedArray = new(prefabReceptor.nucleiArray.Length);
+ int arrayIx = 0;
+ foreach (Nucleus prefabArrayNucleus in prefabReceptor.nucleiArray) {
+ int arrayNucleusIx = GetNucleusIndex(prefabNuclei, prefabArrayNucleus);
+ if (arrayNucleusIx >= 0) {
+ Nucleus clonedArrayNucleus = clonedNuclei[arrayNucleusIx];
+ clonedArray.nuclei[arrayIx] = clonedArrayNucleus;
+ }
+ else {
+ Debug.LogError($" Could not find prefab nucleus {prefabNucleus.name} in the clones");
+ }
+ arrayIx++;
+ }
+ //clonedNucleus.array = clonedArray;
+ clonedNucleus.nucleiArray = clonedArray.nuclei;
+ }
+ else {
+ // The others will refer to the array created for the first nucleus in the array
+ int firstNucleusIx = GetNucleusIndex(prefabNuclei, prefabReceptor.nucleiArray[0]);
+ IReceptor clonedFirstNucleus = clonedNuclei[firstNucleusIx] as IReceptor;
+ clonedNucleus.nucleiArray = clonedFirstNucleus.nucleiArray;
+ }
+ }
+
+ foreach (Nucleus nucleus in this.clusterNuclei) {
+ if (nucleus is Cluster clonedSubCluster)
+ RestoreAllExternalReceivers(clonedSubCluster, this.prefab, this);
+ }
+ }
+
+ ///
+ /// Sort the nuclei in a correct evaluation order
+ ///
+ ///
+ ///
+ ///
+ private List TopologicalSort(List nodes) {
+ Dictionary inDegree = new();
+ foreach (Nucleus node in nodes)
+ inDegree[node] = 0; // Initialize in-degree to zero
+
+ // Calculate in-degrees
+ foreach (Nucleus node in nodes) {
+ if (node is Cluster cluster) {
+ foreach (Nucleus receiver in cluster.CollectReceivers())
+ inDegree[receiver]++;
+ }
+ else if (node is Neuron neuron) {
+ foreach (Nucleus receiver in neuron.receivers)
+ inDegree[receiver]++;
+ }
+ }
+
+ Queue queue = new();
+ foreach (Nucleus node in nodes) {
+ if (inDegree[node] == 0) // Nodes with no dependencies
+ queue.Enqueue(node);
+ }
+ // The queue basically stores all input nuclei?
+
+ List sortedOrder = new();
+ while (queue.Count > 0) {
+ Nucleus current = queue.Dequeue();
+ sortedOrder.Add(current); // Process the node
+
+ if (current is Neuron neuron) {
+ foreach (Nucleus receiver in neuron.receivers) {
+ inDegree[receiver]--;
+ if (inDegree[receiver] == 0) // If all dependencies resolved
+ queue.Enqueue(receiver);
+ }
+ }
+ else if (current is Cluster cluster) {
+ foreach (Nucleus receiver in cluster.CollectReceivers()) {
+ inDegree[receiver]--;
+ if (inDegree[receiver] == 0) // If all dependencies resolved
+ queue.Enqueue(receiver);
+ }
+ }
+ }
+
+ // Check for cycles in the graph
+ if (sortedOrder.Count != nodes.Count)
+ throw new InvalidOperationException("Graph is not a DAG; a cycle exists.");
+
+ return sortedOrder;
+ }
+
+ public override Nucleus Clone(ClusterPrefab parent) {
+ Cluster clone = new(this.prefab, parent);
+
+ foreach (Synapse synapse in this.synapses) {
+ Synapse clonedSynapse = clone.AddSynapse(synapse.neuron);
+ clonedSynapse.weight = synapse.weight;
+ }
+
+ foreach (Neuron output in this.outputs) {
+ foreach (Nucleus receiver in output.receivers) {
+ int ix = GetNucleusIndex(this.clusterNuclei.ToArray(), output);
+ if (ix < 0)
+ continue;
+
+ if (clone.clusterNuclei[ix] is not Neuron clonedOutput)
+ continue;
+
+ clonedOutput.AddReceiver(receiver);
+ }
+ }
+
+ return clone;
+ }
+
+ public override Nucleus ShallowCloneTo(Cluster parent) {
+ Cluster clone = new(this.prefab, parent) {
+ name = this.name,
+ clusterPrefab = this.clusterPrefab,
+ };
+ // Somehow siblingClusters should be cloned too. Believe I do this in ClonePrefab right now.
+
+ return clone;
+ }
+
+ private static void RestoreAllExternalReceivers(Cluster clonedCluster, ClusterPrefab prefabParent, Cluster clonedParent) {
+ int clonedClusterIx = GetNucleusIndex(clonedParent.clusterNuclei, clonedCluster);
+ if (prefabParent.nuclei[clonedClusterIx] is not Cluster sourceCluster)
+ return;
+
+ for (int nucleusIx = 0; nucleusIx < sourceCluster.clusterNuclei.Count; nucleusIx++) {
+ Nucleus sourceNucleus = sourceCluster.clusterNuclei[nucleusIx];
+ if (sourceNucleus is not Neuron sourceNeuron)
+ continue;
+
+ if (clonedCluster.clusterNuclei[nucleusIx] is not Neuron clonedNeuron)
+ continue;
+
+ // copy the receivers (and thus synapses) from the source to the clone
+ foreach (Nucleus receiver in sourceNeuron.receivers) {
+ int ix = GetNucleusIndex(prefabParent.nuclei, receiver);
+ if (ix < 0 || ix >= clonedParent.clusterNuclei.Count)
+ continue;
+
+ Nucleus clonedReceiver = clonedParent.clusterNuclei[ix];
+
+ // Find the synapse for the weight
+ float weight = 1;
+ foreach (Synapse synapse in receiver.synapses) {
+ // Find the weight for this synapse
+ if (synapse.neuron == sourceNucleus) {
+ weight = synapse.weight;
+ break;
+ }
+ }
+
+ clonedNeuron.AddReceiver(clonedReceiver, weight);
+ // Debug.Log($"external: {clonedReceiver.name} receives from {clonedNeuron.name} {clonedNeuron.GetHashCode()}");
+ }
+ }
+ }
+
+ protected int GetNucleusIndex(Nucleus[] nuclei, Nucleus nucleus) {
+ for (int i = 0; i < nuclei.Length; i++) {
+ if (nucleus == nuclei[i])
+ return i;
+ }
+ return -1;
+ }
+
+ public static int GetNucleusIndex(List nuclei, Nucleus nucleus) {
+ int i = 0;
+ foreach (Nucleus nucleiElement in nuclei) {
+ //for (int i = 0; i < nuclei.Length; i++) {
+ if (nucleus == nucleiElement)
+ return i;
+ i++;
+ }
+ return -1;
+ }
+
+ #endregion Init
+
+ #region Cluster Array
+
+
+ public void AddInstance(ClusterPrefab prefab) {
+ // if (this.siblingClusters.Length == 0) {
+ // Debug.LogError("Empty perceptoid array, cannot add");
+ // return;
+ // }
+ this.siblingClusters ??= new Cluster[1] { this };
+
+ int newLength = this.siblingClusters.Length + 1;
+ Cluster[] newSiblings = new Cluster[newLength];
+
+ string baseName = this.name;
+ int colonPos = baseName.IndexOf(":");
+ if (colonPos > 0)
+ baseName = baseName[..colonPos];
+
+ for (int i = 0; i < newSiblings.Length - 1; i++)
+ newSiblings[i] = this.siblingClusters[i];
+ // Cluster sourceCluster = this.siblingClusters[0];
+ Cluster newCluster = this.Clone(prefab) as Cluster;
+ newCluster.name = $"{baseName}: {newLength - 1}";
+ //newCluster.clusterArray = this;
+ newSiblings[newLength - 1] = newCluster;
+
+ this.siblingClusters = newSiblings;
+ newCluster.siblingClusters = newSiblings;
+ }
+
+ public void RemoveInstance() {
+ int newLength = this.siblingClusters.Length - 1;
+ if (newLength == 0) {
+ Debug.LogWarning("Perceptoid array cannot be empty");
+ return;
+ }
+ Cluster[] newClusters = new Cluster[newLength];
+ for (int i = 0; i < newLength; i++)
+ newClusters[i] = this.siblingClusters[i];
+ // Delete the last perception
+ //Cluster.Delete(nucleus);
+ this.siblingClusters = newClusters;
+ }
+
+ public virtual Cluster GetThingCluster() {
+ Cluster selectedCluster = SelectCluster();
+ return selectedCluster;
+ }
+ public virtual Cluster GetThingCluster(int thingId, string thingName = null) {
+ if (thingClusters.TryGetValue(thingId, out Cluster cluster))
+ return cluster;
+
+ Cluster selectedCluster = SelectCluster();
+ thingClusters[thingId] = selectedCluster;
+ return selectedCluster;
+ }
+
+ private Cluster SelectCluster() {
+ if (this.siblingClusters == null)
+ return this;
+
+ // Find a sleeping cluster
+ foreach (Cluster cluster in this.siblingClusters) {
+ if (cluster.defaultOutput.isSleeping) {
+ RemoveThingCluster(cluster);
+ return cluster;
+ }
+ }
+
+ // Otherwise find the stalest cluster?
+ Cluster stalestCluster = this.siblingClusters[0];
+ for (int ix = 1; ix < this.siblingClusters.Length; ix++) {
+ if (this.siblingClusters[ix].defaultOutput.stale > stalestCluster.defaultOutput.stale)
+ stalestCluster = this.siblingClusters[ix];
+ }
+
+ RemoveThingCluster(stalestCluster);
+ return stalestCluster;
+ }
+
+ private void RemoveThingCluster(Cluster cluster) {
+ List keysToRemove = new();
+ foreach (KeyValuePair kvp in thingClusters) {
+ if (kvp.Value == cluster)
+ keysToRemove.Add(kvp.Key);
+ }
+
+ foreach (int thingId in keysToRemove)
+ thingClusters.Remove(thingId);
+ }
+
+ #endregion ClusterArray
+
+ public ClusterPrefab prefab;
+
+
+ [SerializeReference]
+ public List clusterNuclei = new();
+ // the nuclei sorted using topological sorting
+ // to ensure that the cluster is computer in the right order
+ public List sortedNuclei;
+ //public Dictionary nucleiDict = new();
+
+ public List _inputs = null;
+ public virtual List inputs {
+ get {
+ if (this._inputs == null) {
+ this._inputs = new();
+ foreach (Nucleus nucleus in this.clusterNuclei) {
+ // inputs have no synapses
+ if (nucleus.synapses.Count == 0)
+ this._inputs.Add(nucleus);
+ }
+ ComputeOrders();
+ }
+ return this._inputs;
+ }
+ }
+
+ public Dictionary> computeOrders = new();
+ private void ComputeOrders() {
+ foreach (Nucleus input in this._inputs)
+ computeOrders[input] = TopologicalSort2(input);
+ }
+
+ private List TopologicalSort2(Nucleus startNode) {
+ Dictionary inDegree = new();
+ HashSet visited = new();
+
+ // Initialize in-degrees and mark all nodes as unvisited
+ foreach (Nucleus node in this.clusterNuclei)
+ inDegree[node] = 0;
+
+ // Calculate in-degrees for all nodes reachable from the start node
+ Queue queue = new Queue();
+ queue.Enqueue(startNode);
+ visited.Add(startNode);
+
+ while (queue.Count > 0) {
+ Nucleus current = queue.Dequeue();
+ List receivers = null;
+ if (current is Neuron neuron)
+ receivers = neuron.receivers;
+ else if (current is Cluster cluster)
+ receivers = cluster.CollectReceivers();
+
+ // if (current is Neuron neuron) {
+ foreach (Nucleus receiver in receivers) {
+ if (!visited.Contains(receiver)) {
+ visited.Add(receiver);
+ queue.Enqueue(receiver);
+ }
+ inDegree[receiver]++;
+ }
+ // }
+ }
+
+ // Perform topological sort on all reachable nodes
+ queue.Clear();
+ foreach (Nucleus node in visited) {
+ if (inDegree[node] == 0)
+ queue.Enqueue(node);
+ }
+
+ List sortedOrder = new List();
+ while (queue.Count > 0) {
+ Nucleus current = queue.Dequeue();
+ sortedOrder.Add(current); // Process the node
+
+ List receivers = null;
+ if (current is Neuron neuron)
+ receivers = neuron.receivers;
+ else if (current is Cluster cluster)
+ receivers = cluster.CollectReceivers();
+
+ //if (current is Neuron neuron) {
+
+ foreach (Nucleus receiver in receivers) {
+ if (visited.Contains(receiver)) {
+ inDegree[receiver]--;
+ if (inDegree[receiver] == 0) // If all dependencies resolved
+ queue.Enqueue(receiver);
+ }
+ }
+ //}
+ }
+
+ // Check for cycles in the graph
+ if (sortedOrder.Count != visited.Count)
+ throw new InvalidOperationException("Graph is not a DAG; a cycle exists.");
+
+ return sortedOrder;
+ }
+
+ public virtual Neuron defaultOutput {//=> this.nuclei[0] as Nucleus;
+ get {
+ if (this.clusterNuclei.Count > 0)
+ return this.clusterNuclei[0] as Neuron;
+ return null;
+ }
+ }
+ protected List _outputs = null;
+ public List outputs {
+ get {
+ if (this._outputs == null) {
+ this._outputs = new();
+ foreach (Nucleus nucleus in this.clusterNuclei) {
+ if (nucleus is Neuron neuron) // && neuron.receivers.Count == 0)
+ this._outputs.Add(neuron);
+ }
+ }
+ return this._outputs;
+ }
+ }
+
+ public bool TryGetNucleus(string nucleusName, out Nucleus foundNucleus) {
+ foreach (Nucleus receptor in this.clusterNuclei) {
+ if (receptor is Nucleus nucleus)
+ if (nucleus.name == nucleusName) {
+ foundNucleus = nucleus;
+ return true;
+ }
+ }
+ foundNucleus = null;
+ return false;
+ }
+
+ public Nucleus GetNucleus(string nucleusName) {
+ int dotPosition = nucleusName.IndexOf('.');
+ if (dotPosition >= 0) {
+ string clusterName = nucleusName[..dotPosition];
+ string clusterName0 = clusterName + ": 0";
+ foreach (Nucleus nucleus in this.clusterNuclei) {
+ if (nucleus is Cluster cluster) {
+ if (cluster.name == clusterName || cluster.name == clusterName0) {
+ string subNucleusName = nucleusName[(dotPosition + 1)..];
+ return cluster.GetNucleus(subNucleusName);
+ }
+ }
+ }
+ return null;
}
else {
- // The others will refer to the array created for the first nucleus in the array
- int firstNucleusIx = GetNucleusIndex(prefabNuclei, prefabReceptor.nucleiArray[0]);
- IReceptor clonedFirstNucleus = clonedNuclei[firstNucleusIx] as IReceptor;
- clonedNucleus.nucleiArray = clonedFirstNucleus.nucleiArray;
- }
- }
-
- foreach (Nucleus nucleus in this.clusterNuclei) {
- if (nucleus is Cluster clonedSubCluster)
- RestoreAllExternalReceivers(clonedSubCluster, this.prefab, this);
- }
- }
-
- ///
- /// Sort the nuclei in a correct evaluation order
- ///
- ///
- ///
- ///
- private List TopologicalSort(List nodes) {
- Dictionary inDegree = new();
- foreach (Nucleus node in nodes)
- inDegree[node] = 0; // Initialize in-degree to zero
-
- // Calculate in-degrees
- foreach (Nucleus node in nodes) {
- if (node is Cluster cluster) {
- foreach (Nucleus receiver in cluster.CollectReceivers())
- inDegree[receiver]++;
- }
- else if (node is Neuron neuron) {
- foreach (Nucleus receiver in neuron.receivers)
- inDegree[receiver]++;
- }
- }
-
- Queue queue = new();
- foreach (Nucleus node in nodes) {
- if (inDegree[node] == 0) // Nodes with no dependencies
- queue.Enqueue(node);
- }
- // The queue basically stores all input nuclei?
-
- List sortedOrder = new();
- while (queue.Count > 0) {
- Nucleus current = queue.Dequeue();
- sortedOrder.Add(current); // Process the node
-
- if (current is Neuron neuron) {
- foreach (Nucleus receiver in neuron.receivers) {
- inDegree[receiver]--;
- if (inDegree[receiver] == 0) // If all dependencies resolved
- queue.Enqueue(receiver);
- }
- }
- else if (current is Cluster cluster) {
- foreach (Nucleus receiver in cluster.CollectReceivers()) {
- inDegree[receiver]--;
- if (inDegree[receiver] == 0) // If all dependencies resolved
- queue.Enqueue(receiver);
- }
- }
- }
-
- // Check for cycles in the graph
- if (sortedOrder.Count != nodes.Count)
- throw new InvalidOperationException("Graph is not a DAG; a cycle exists.");
-
- return sortedOrder;
- }
-
- public override Nucleus Clone(ClusterPrefab parent) {
- Cluster clone = new(this.prefab, parent);
-
- foreach (Synapse synapse in this.synapses) {
- Synapse clonedSynapse = clone.AddSynapse(synapse.neuron);
- clonedSynapse.weight = synapse.weight;
- }
-
- foreach (Neuron output in this.outputs) {
- foreach (Nucleus receiver in output.receivers) {
- int ix = GetNucleusIndex(this.clusterNuclei.ToArray(), output);
- if (ix < 0)
- continue;
-
- if (clone.clusterNuclei[ix] is not Neuron clonedOutput)
- continue;
-
- clonedOutput.AddReceiver(receiver);
- }
- }
-
- return clone;
- }
-
- public override Nucleus ShallowCloneTo(Cluster parent) {
- Cluster clone = new(this.prefab, parent) {
- name = this.name,
- clusterPrefab = this.clusterPrefab,
- };
-
- return clone;
- }
-
- private static void RestoreAllExternalReceivers(Cluster clonedCluster, ClusterPrefab prefabParent, Cluster clonedParent) {
- int clonedClusterIx = GetNucleusIndex(clonedParent.clusterNuclei, clonedCluster);
- if (prefabParent.nuclei[clonedClusterIx] is not Cluster sourceCluster)
- return;
-
- for (int nucleusIx = 0; nucleusIx < sourceCluster.clusterNuclei.Count; nucleusIx++) {
- Nucleus sourceNucleus = sourceCluster.clusterNuclei[nucleusIx];
- if (sourceNucleus is not Neuron sourceNeuron)
- continue;
-
- if (clonedCluster.clusterNuclei[nucleusIx] is not Neuron clonedNeuron)
- continue;
-
- // copy the receivers (and thus synapses) from the source to the clone
- foreach (Nucleus receiver in sourceNeuron.receivers) {
- int ix = GetNucleusIndex(prefabParent.nuclei, receiver);
- if (ix < 0 || ix >= clonedParent.clusterNuclei.Count)
- continue;
-
- Nucleus clonedReceiver = clonedParent.clusterNuclei[ix];
-
- // Find the synapse for the weight
- float weight = 1;
- foreach (Synapse synapse in receiver.synapses) {
- // Find the weight for this synapse
- if (synapse.neuron == sourceNucleus) {
- weight = synapse.weight;
- break;
- }
- }
-
- clonedNeuron.AddReceiver(clonedReceiver, weight);
- // Debug.Log($"external: {clonedReceiver.name} receives from {clonedNeuron.name} {clonedNeuron.GetHashCode()}");
- }
- }
- }
-
- protected int GetNucleusIndex(Nucleus[] nuclei, Nucleus nucleus) {
- for (int i = 0; i < nuclei.Length; i++) {
- if (nucleus == nuclei[i])
- return i;
- }
- return -1;
- }
-
- public static int GetNucleusIndex(List nuclei, Nucleus nucleus) {
- int i = 0;
- foreach (Nucleus nucleiElement in nuclei) {
- //for (int i = 0; i < nuclei.Length; i++) {
- if (nucleus == nucleiElement)
- return i;
- i++;
- }
- return -1;
- }
-
- #endregion Init
-
- public ClusterPrefab prefab;
-
-
- [SerializeReference]
- public List clusterNuclei = new();
- // the nuclei sorted using topological sorting
- // to ensure that the cluster is computer in the right order
- public List sortedNuclei;
- //public Dictionary nucleiDict = new();
-
- public List _inputs = null;
- public virtual List inputs {
- get {
- if (this._inputs == null) {
- this._inputs = new();
+ string nucleusName0 = nucleusName + ": 0";
foreach (Nucleus nucleus in this.clusterNuclei) {
- // inputs have no synapses
- if (nucleus.synapses.Count == 0)
- this._inputs.Add(nucleus);
- }
- ComputeOrders();
- }
- return this._inputs;
- }
- }
-
- public Dictionary> computeOrders = new();
- private void ComputeOrders() {
- foreach (Nucleus input in this._inputs)
- computeOrders[input] = TopologicalSort2(input);
- }
-
- private List TopologicalSort2(Nucleus startNode) {
- Dictionary inDegree = new();
- HashSet visited = new();
-
- // Initialize in-degrees and mark all nodes as unvisited
- foreach (Nucleus node in this.clusterNuclei)
- inDegree[node] = 0;
-
- // Calculate in-degrees for all nodes reachable from the start node
- Queue queue = new Queue();
- queue.Enqueue(startNode);
- visited.Add(startNode);
-
- while (queue.Count > 0) {
- Nucleus current = queue.Dequeue();
- List receivers = null;
- if (current is Neuron neuron)
- receivers = neuron.receivers;
- else if (current is Cluster cluster)
- receivers = cluster.CollectReceivers();
-
- // if (current is Neuron neuron) {
- foreach (Nucleus receiver in receivers) {
- if (!visited.Contains(receiver)) {
- visited.Add(receiver);
- queue.Enqueue(receiver);
- }
- inDegree[receiver]++;
- }
- // }
- }
-
- // Perform topological sort on all reachable nodes
- queue.Clear();
- foreach (Nucleus node in visited) {
- if (inDegree[node] == 0)
- queue.Enqueue(node);
- }
-
- List sortedOrder = new List();
- while (queue.Count > 0) {
- Nucleus current = queue.Dequeue();
- sortedOrder.Add(current); // Process the node
-
- List receivers = null;
- if (current is Neuron neuron)
- receivers = neuron.receivers;
- else if (current is Cluster cluster)
- receivers = cluster.CollectReceivers();
-
- //if (current is Neuron neuron) {
-
- foreach (Nucleus receiver in receivers) {
- if (visited.Contains(receiver)) {
- inDegree[receiver]--;
- if (inDegree[receiver] == 0) // If all dependencies resolved
- queue.Enqueue(receiver);
- }
- }
- //}
- }
-
- // Check for cycles in the graph
- if (sortedOrder.Count != visited.Count)
- throw new InvalidOperationException("Graph is not a DAG; a cycle exists.");
-
- return sortedOrder;
- }
-
- public virtual Neuron defaultOutput {//=> this.nuclei[0] as Nucleus;
- get {
- if (this.clusterNuclei.Count > 0)
- return this.clusterNuclei[0] as Neuron;
- return null;
- }
- }
- protected List _outputs = null;
- public List outputs {
- get {
- if (this._outputs == null) {
- this._outputs = new();
- foreach (Nucleus nucleus in this.clusterNuclei) {
- if (nucleus is Neuron neuron) // && neuron.receivers.Count == 0)
- this._outputs.Add(neuron);
- }
- }
- return this._outputs;
- }
- }
-
- public bool TryGetNucleus(string nucleusName, out Nucleus foundNucleus) {
- foreach (Nucleus receptor in this.clusterNuclei) {
- if (receptor is Nucleus nucleus)
- if (nucleus.name == nucleusName) {
- foundNucleus = nucleus;
- return true;
- }
- }
- foundNucleus = null;
- return false;
- }
-
- public Nucleus GetNucleus(string nucleusName) {
- int dotPosition = nucleusName.IndexOf('.');
- if (dotPosition >= 0) {
- string clusterName = nucleusName[..dotPosition];
- string clusterName0 = clusterName + ": 0";
- foreach (Nucleus nucleus in this.clusterNuclei) {
- if (nucleus is Cluster cluster) {
- if (cluster.name == clusterName || cluster.name == clusterName0) {
- string subNucleusName = nucleusName[(dotPosition + 1)..];
- return cluster.GetNucleus(subNucleusName);
+ if (nucleus is IReceptor receptor) {
+ if (nucleus.name == nucleusName | nucleus.name == nucleusName0)
+ return nucleus;
}
- }
- }
- return null;
- }
- else {
- string nucleusName0 = nucleusName + ": 0";
- foreach (Nucleus nucleus in this.clusterNuclei) {
- if (nucleus is IReceptor receptor) {
- if (nucleus.name == nucleusName | nucleus.name == nucleusName0)
+ else if (nucleus.name == nucleusName)
return nucleus;
}
- else if (nucleus.name == nucleusName)
- return nucleus;
- }
- return null;
- }
- }
-
- // [Obsolete("Use GetNucleus instead")]
- // public IReceptor GetReceptor(string receptorName) {
- // return GetNucleus(receptorName) as IReceptor;
- // }
-
- #region Receivers
-
- public virtual List CollectReceivers() {
- List receivers = new();
- foreach (Neuron output in this.outputs) {
- foreach (Nucleus receiver in output.receivers) {
- // Only add receivers outside this cluster
- if (receiver.clusterPrefab != this.prefab)
- receivers.Add(receiver);
- //receivers.AddRange(output.receivers);
+ return null;
}
}
- return receivers;
- }
- #endregion Receivers
-
- #region Update
-
- public void UpdateFromNucleus(Nucleus startNucleus) {
- // no bias+synapse input state calculation for now...
-
- if (this.computeOrders.ContainsKey(startNucleus) == false) {
- //Debug.LogError($"{this.name} compute orders does not contain an order for {startNucleus.name}");
- return;
- }
-
- List computeOrder = this.computeOrders[startNucleus];
- if (startNucleus.trace)
- Debug.Log($"Update from {startNucleus.name}");
- foreach (Nucleus nucleus in computeOrder) {
- nucleus.UpdateStateIsolated();
- if (startNucleus.trace && nucleus is Neuron neuron)
- Debug.Log($" {nucleus.name}[{nucleus.GetHashCode()}] = {neuron.outputValue}");
- }
-
- // continue in parent
- this.parent?.UpdateFromNucleus(this);
-
- UpdateNuclei();
- }
-
- public override void UpdateStateIsolated() {
- throw new Exception("Cluster should not be updated!");
- // float3 sum = this.bias;
-
- // //Applying the weight factors
- // foreach (Synapse synapse in this.synapses) {
- // if (lengthsq(synapse.neuron.outputValue) > 0) {
- // sum += synapse.weight * synapse.neuron.outputValue;
- // }
+ // [Obsolete("Use GetNucleus instead")]
+ // public IReceptor GetReceptor(string receptorName) {
+ // return GetNucleus(receptorName) as IReceptor;
// }
- // foreach (Nucleus nucleus in this.sortedNuclei)
- // nucleus.UpdateStateIsolated();
+ #region Receivers
+
+ public virtual List CollectReceivers() {
+ List receivers = new();
+ foreach (Neuron output in this.outputs) {
+ foreach (Nucleus receiver in output.receivers) {
+ // Only add receivers outside this cluster
+ if (receiver.clusterPrefab != this.prefab)
+ receivers.Add(receiver);
+ //receivers.AddRange(output.receivers);
+ }
+ }
+ return receivers;
+ }
+
+ #endregion Receivers
+
+ #region Update
+
+ public void UpdateFromNucleus(Nucleus startNucleus) {
+ // no bias+synapse input state calculation for now...
+
+ if (this.computeOrders.ContainsKey(startNucleus) == false) {
+ //Debug.LogError($"{this.name} compute orders does not contain an order for {startNucleus.name}");
+ return;
+ }
+
+ List computeOrder = this.computeOrders[startNucleus];
+ if (startNucleus.trace)
+ Debug.Log($"Update from {startNucleus.name}");
+ foreach (Nucleus nucleus in computeOrder) {
+ nucleus.UpdateStateIsolated();
+ if (startNucleus.trace && nucleus is Neuron neuron)
+ Debug.Log($" {nucleus.name}[{nucleus.GetHashCode()}] = {neuron.outputValue}");
+ }
+
+ // continue in parent
+ this.parent?.UpdateFromNucleus(this);
+
+ UpdateNuclei();
+ }
+
+ public override void UpdateStateIsolated() {
+ throw new Exception("Cluster should not be updated!");
+ // float3 sum = this.bias;
+
+ // //Applying the weight factors
+ // foreach (Synapse synapse in this.synapses) {
+ // if (lengthsq(synapse.neuron.outputValue) > 0) {
+ // sum += synapse.weight * synapse.neuron.outputValue;
+ // }
+ // }
+
+ // foreach (Nucleus nucleus in this.sortedNuclei)
+ // nucleus.UpdateStateIsolated();
+
+ // UpdateNuclei();
+ }
+
+ public override void UpdateNuclei() {
+ foreach (Nucleus nucleus in this.clusterNuclei)
+ nucleus.UpdateNuclei();
+ }
+
+ #endregion Update
- // UpdateNuclei();
}
- public override void UpdateNuclei() {
- foreach (Nucleus nucleus in this.clusterNuclei)
- nucleus.UpdateNuclei();
- }
-
- #endregion Update
-
-}
-
}
\ No newline at end of file
diff --git a/Runtime/Scripts/Core/ClusterArray.cs b/Runtime/Scripts/Core/ClusterArray.cs
index 490c216..3d0bab3 100644
--- a/Runtime/Scripts/Core/ClusterArray.cs
+++ b/Runtime/Scripts/Core/ClusterArray.cs
@@ -1,3 +1,4 @@
+/*
using System;
using System.Collections.Generic;
using UnityEngine;
@@ -8,6 +9,7 @@ namespace NanoBrain {
public class ClusterArray : Nucleus {
public ClusterPrefab prefab;
+ [SerializeReference]
public Cluster[] clusters;
public Dictionary thingClusters = new();
@@ -19,7 +21,7 @@ namespace NanoBrain {
for (int ix = 0; ix < size; ix++) {
Cluster cluster = new(prefab, parent);
cluster.defaultOutput.AddReceiver(receiver);
- cluster.clusterArray = this;
+ //cluster.clusterArray = this;
this.clusters[ix] = cluster;
}
}
@@ -31,7 +33,7 @@ namespace NanoBrain {
for (int ix = 0; ix < size; ix++) {
Cluster cluster = new(prefab, parent);
cluster.defaultOutput.AddReceiver(receiver);
- cluster.clusterArray = this;
+ //cluster.clusterArray = this;
this.clusters[ix] = cluster;
}
}
@@ -69,7 +71,7 @@ namespace NanoBrain {
Cluster sourceCluster = this.clusters[0];
Cluster newCluster = sourceCluster.Clone(prefab) as Cluster;
newCluster.name = $"{baseName}: {newLength - 1}";
- newCluster.clusterArray = this;
+ //newCluster.clusterArray = this;
newClusters[newLength - 1] = newCluster;
this.clusters = newClusters;
}
@@ -139,4 +141,5 @@ namespace NanoBrain {
}
}
-}
\ No newline at end of file
+}
+*/
\ No newline at end of file
diff --git a/Runtime/Scripts/Core/Nucleus.cs b/Runtime/Scripts/Core/Nucleus.cs
index b343d43..1491957 100644
--- a/Runtime/Scripts/Core/Nucleus.cs
+++ b/Runtime/Scripts/Core/Nucleus.cs
@@ -55,8 +55,8 @@ public abstract class Nucleus {
MemoryCell,
Cluster,
Receptor,
- ClusterReceptor,
- ClusterArray,
+ //ClusterReceptor,
+ //ClusterArray,
}
#region Synapses