From bc0a79688d863d76748938df0420438cfaf8a605 Mon Sep 17 00:00:00 2001 From: Pascal Serrarens Date: Fri, 17 Apr 2026 11:50:10 +0200 Subject: [PATCH] Integrated clusterarray in cluster --- Editor/ClusterInspector.cs | 40 +- Editor/ClusterViewer.cs | 28 +- Runtime/Scripts/Core/Cluster.cs | 1116 ++++++++++++++------------ Runtime/Scripts/Core/ClusterArray.cs | 11 +- Runtime/Scripts/Core/Nucleus.cs | 4 +- 5 files changed, 654 insertions(+), 545 deletions(-) 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