From 2ef67fe1079ab51640224ed636c10eeac8025092 Mon Sep 17 00:00:00 2001 From: Pascal Serrarens Date: Tue, 5 May 2026 10:59:24 +0200 Subject: [PATCH] Cleanup and fix connect neuron --- Editor/ClusterEditor.cs | 110 ++++++----- Editor/ClusterViewer.cs | 10 +- Runtime/Scripts/Brain.cs | 2 +- Runtime/Scripts/Core/Cluster.cs | 108 +++++----- Runtime/Scripts/Core/Neuron.cs | 120 +++++++++-- Runtime/Scripts/Core/Nucleus.cs | 113 +++++------ .../ScriptableObjects/ClusterPrefab.cs | 187 +++++++++--------- 7 files changed, 384 insertions(+), 266 deletions(-) diff --git a/Editor/ClusterEditor.cs b/Editor/ClusterEditor.cs index 725b27d..ae1a535 100644 --- a/Editor/ClusterEditor.cs +++ b/Editor/ClusterEditor.cs @@ -64,22 +64,23 @@ namespace NanoBrain { public class GraphEditor : GraphView { protected ClusterPrefab prefab; - protected Nucleus currentPrefabNucleus; + //protected Nucleus currentPrefabNucleus; protected override Nucleus currentNucleus { get => base.currentNucleus; set { base.currentNucleus = value; - this.currentPrefabNucleus = value != null ? this.prefab.GetNucleus(value.name) : null; + // this.currentPrefabNucleus = value != null ? this.prefab.GetNucleus(value.name) : null; } } - public GraphEditor(ClusterPrefab prefab) : base(prefab.output.parent) { + public GraphEditor(ClusterPrefab prefab) : base(prefab.cluster.defaultOutput.parent) { this.prefab = prefab; // In a Prefab editor, no instance exists but we need it for the ClusterViewer. // So we create a temporary instance - this.currentCluster = new(prefab); + //this.currentCluster = new(prefab); + this.currentCluster = prefab.cluster; } public void SetGraph(GameObject gameObject, VisualElement inspectorContainer) { @@ -120,7 +121,7 @@ namespace NanoBrain { // create a SerializedObject wrapper so Unity inspector controls work (and Undo) SerializedObject so = new(prefabAsset); - foreach (Nucleus nucleus in this.prefab.nuclei) { + foreach (Nucleus nucleus in this.prefab.cluster.nuclei) { nucleus.Initialize(); } @@ -163,7 +164,8 @@ namespace NanoBrain { GUILayout.Label(nucleusType, headerStyle); // Nucleus name - Cluster cluster = this.currentPrefabNucleus as Cluster; + //Cluster cluster = this.currentPrefabNucleus as Cluster; + Cluster cluster = this.currentNucleus as Cluster; if (cluster != null) { EditorGUILayout.BeginHorizontal(); if (GUILayout.Button(this.currentNucleus.parent.name)) @@ -182,7 +184,7 @@ namespace NanoBrain { prefabNucleus.name = newName; // This changes it in the temporary cluster instance this.currentNucleus.name = newName; - this.prefab.RefreshOutputs(); + this.prefab.cluster.RefreshOutputs(); // outputsPopup.choices = this.prefab.outputs.Select(output => output.name).ToList(); anythingChanged = true; } @@ -281,9 +283,9 @@ namespace NanoBrain { if (breakOnWake && this.currentNucleus is Neuron currentNeuron) { if (currentNeuron.isSleeping == false) Debug.Break(); + trace = EditorGUILayout.Toggle("Trace", trace); + currentNeuron.trace = trace; } - trace = EditorGUILayout.Toggle("Trace", trace); - this.currentNucleus.trace = trace; } protected void SynapsesInspector(ref bool anythingChanged) { @@ -293,31 +295,32 @@ namespace NanoBrain { Neuron.CombinatorType newCombinator = (Neuron.CombinatorType)EditorGUILayout.EnumPopup("Combinator", neuron2.combinator); anythingChanged |= newCombinator != neuron2.combinator; neuron2.combinator = newCombinator; - } - EditorGUIUtility.wideMode = true; - float previousLabelWidth = EditorGUIUtility.labelWidth; - EditorGUIUtility.labelWidth = 100; + EditorGUIUtility.wideMode = true; + float previousLabelWidth = EditorGUIUtility.labelWidth; + EditorGUIUtility.labelWidth = 100; - Vector3 newBias = EditorGUILayout.Vector3Field("Bias", this.currentNucleus.bias); - if (newBias != this.currentPrefabNucleus.bias) { - anythingChanged |= newBias != this.currentNucleus.bias; - this.currentPrefabNucleus.bias = newBias; - this.currentNucleus.bias = newBias; + Vector3 newBias = EditorGUILayout.Vector3Field("Bias", neuron2.bias); + if (newBias != neuron2.bias) { + anythingChanged |= newBias != neuron2.bias; + neuron2.bias = newBias; + } + EditorGUIUtility.labelWidth = previousLabelWidth; } - EditorGUIUtility.labelWidth = previousLabelWidth; Nucleus[] array = null; int elementIx = -1; - if (this.currentPrefabNucleus.synapses.Count > 0) { - Synapse[] synapses = this.currentPrefabNucleus.synapses.ToArray(); + //if (this.currentPrefabNucleus is Neuron prefabNeuron && prefabNeuron.synapses.Count > 0) { + // Synapse[] synapses = prefabNeuron.synapses.ToArray(); + if (this.currentNucleus is Neuron currentNeuron && currentNeuron.synapses.Count > 0) { + Synapse[] synapses = currentNeuron.synapses.ToArray(); foreach (Synapse synapse in synapses) { if (synapse.neuron == null) continue; if (array != null) { if (synapse.neuron.parent is Cluster iCluster && elementIx > 0) { - int thisElementIx = Cluster.GetNucleusIndex(iCluster.clusterNuclei, synapse.neuron); + int thisElementIx = Cluster.GetNucleusIndex(iCluster.nuclei, synapse.neuron); if (thisElementIx == elementIx) continue; else @@ -332,7 +335,7 @@ namespace NanoBrain { if (synapse.neuron.parent is Cluster iReceptor) { array = iReceptor.siblingClusters; if (iReceptor is Cluster iCluster) - elementIx = Cluster.GetNucleusIndex(iCluster.clusterNuclei, synapse.neuron); + elementIx = Cluster.GetNucleusIndex(iCluster.nuclei, synapse.neuron); } } @@ -356,13 +359,13 @@ namespace NanoBrain { labelWidth = labelStyle.CalcSize(new GUIContent($"{synapse.neuron.clusterPrefab.name}.")).x; GUILayout.Label($"{synapse.neuron.clusterPrefab.name}", GUILayout.Width(labelWidth)); } - string[] options = synapse.neuron.clusterPrefab.nuclei.Select(n => n.name).ToArray(); + string[] options = synapse.neuron.clusterPrefab.cluster.nuclei.Select(n => n.name).ToArray(); int selectedIndex = System.Array.IndexOf(options, synapse.neuron.name); int newIndex = EditorGUILayout.Popup(selectedIndex, options); if (newIndex != selectedIndex) { // Nucleus selectedNucleus = synapse.neuron.parent.clusterNuclei[newIndex]; // Neuron newNeuron = selectedNucleus as Neuron; - Neuron newNeuron = synapse.neuron.clusterPrefab.nuclei[newIndex] as Neuron; + Neuron newNeuron = synapse.neuron.clusterPrefab.cluster.nuclei[newIndex] as Neuron; ChangeSynapse(synapse, newNeuron); } } @@ -370,13 +373,12 @@ namespace NanoBrain { GUILayout.Label(synapse.neuron.name); bool disconnecting = GUILayout.Button("Disconnect", GUILayout.Width(80)); - if (disconnecting && synapse.neuron is Neuron synapseNeuron) { - synapseNeuron.RemoveReceiver(this.currentNucleus); + if (disconnecting) { + synapse.neuron.RemoveReceiver(this.currentNucleus); this.prefab.GarbageCollection(); anythingChanged = true; } EditorGUILayout.EndHorizontal(); - } EditorGUI.indentLevel++; @@ -493,11 +495,12 @@ namespace NanoBrain { if (cluster == null) return false; - IEnumerable synapseNuclei = this.currentNucleus.synapses + Neuron currentNeuron = this.currentNucleus as Neuron; + IEnumerable synapseNuclei = currentNeuron.synapses .Where(synapse => synapse.neuron != null) .Select(synapse => synapse.neuron); - IEnumerable nuclei = cluster.nuclei + IEnumerable nuclei = cluster.cluster.nuclei .Except(synapseNuclei); IEnumerable nucleiNames = nuclei .Select(n => { @@ -513,14 +516,36 @@ namespace NanoBrain { EditorGUILayout.EndHorizontal(); if (connecting) { Nucleus nucleus = nuclei.ElementAt(selectedConnectNucleus); - if (nucleus is Cluster subCluster) + // Nucleus prefabNucleus = this.prefab.cluster.nuclei.ElementAt(selectedConnectNucleus); + if (nucleus is Cluster subCluster) { subCluster.AddArrayReceiver(this.currentNucleus); - else if (nucleus is Neuron neuron) + // Cluster prefabSubCluster = prefabNucleus as Cluster; + } + else if (nucleus is Neuron neuron) { neuron.AddReceiver(this.currentNucleus); + // if (prefabNucleus is Neuron prefabNeuron) + // prefabNeuron.AddReceiver(this.currentPrefabNucleus); + } } return connecting; } + protected virtual void DisconnectNucleus(Neuron nucleus) { + if (this.currentNucleus.clusterPrefab == null) + return; + Neuron currentNeuron = this.currentNucleus as Neuron; + string[] names = currentNeuron.synapses.Select(synapse => synapse.neuron.name).ToArray(); + int selectedIndex = -1; + selectedIndex = EditorGUILayout.Popup("Disconnect from", selectedIndex, names); + if (selectedIndex >= 0 && selectedIndex < this.currentNucleus.clusterPrefab.cluster.nuclei.Count) { + Synapse synapse = currentNeuron.synapses[selectedIndex]; + synapse.neuron.RemoveReceiver(this.currentNucleus); + + // synapse = currentNeuron.synapses[selectedIndex]; + // synapse.neuron.RemoveReceiver(this.currentPrefabNucleus); + } + } + protected virtual void DeleteNucleus(Nucleus nucleus) { if (nucleus == null) return; @@ -537,10 +562,10 @@ namespace NanoBrain { // this.prefab.nuclei.Remove(nucleus); // Neuron.Delete(nucleus); - this.prefab.RefreshOutputs(); + this.prefab.cluster.RefreshOutputs(); - this.currentNucleus = this.prefab.output; + this.currentNucleus = this.prefab.cluster.defaultOutput; this.selectedOutput = this.currentNucleus; } @@ -596,8 +621,8 @@ namespace NanoBrain { // } // else { // it is a neuron in a subcluster - synapseNeuron.RemoveReceiver(this.currentPrefabNucleus); - newNucleus.AddReceiver(this.currentPrefabNucleus); + synapseNeuron.RemoveReceiver(this.currentNucleus); + newNucleus.AddReceiver(this.currentNucleus); // } } else { @@ -606,19 +631,6 @@ namespace NanoBrain { } } - protected virtual void DisconnectNucleus(Neuron nucleus) { - if (this.currentNucleus.clusterPrefab == null) - return; - string[] names = this.currentNucleus.synapses.Select(synapse => synapse.neuron.name).ToArray(); - int selectedIndex = -1; - selectedIndex = EditorGUILayout.Popup("Disconnect from", selectedIndex, names); - if (selectedIndex >= 0 && selectedIndex < this.currentNucleus.clusterPrefab.nuclei.Count) { - Synapse synapse = this.currentNucleus.synapses[selectedIndex]; - Neuron synapseNeuron = synapse.neuron as Neuron; - synapseNeuron.RemoveReceiver(this.currentNucleus); - } - } - #endregion Synapses #endregion Inspector diff --git a/Editor/ClusterViewer.cs b/Editor/ClusterViewer.cs index eda9701..565fdc5 100644 --- a/Editor/ClusterViewer.cs +++ b/Editor/ClusterViewer.cs @@ -213,7 +213,8 @@ namespace NanoBrain { } private void DescendGraph(Dag.Node receiver, ref int ix, Dag dag) { - foreach (Synapse synapse in receiver.nucleus.synapses) { + Neuron receiverNeuron = receiver.nucleus as Neuron; + foreach (Synapse synapse in receiverNeuron.synapses) { Nucleus nucleus = synapse.neuron; if (nucleus.parent != null && nucleus.parent != currentNucleus.parent) { nucleus = nucleus.parent; @@ -386,6 +387,9 @@ namespace NanoBrain { } protected void DrawSynapses(Nucleus nucleus, Vector3 parentPos, float size) { + if (nucleus is not Neuron neuron) + return; + if (this.selectedSynapseNeuron != null) { DrawClusterSynapses(this.selectedSynapseNeuron, parentPos, size); return; @@ -398,7 +402,7 @@ namespace NanoBrain { float maxValue = 0; int neuronCount = 0; List drawnNeuronNames = new(); - foreach (Synapse synapse in nucleus.synapses) { + foreach (Synapse synapse in neuron.synapses) { if (synapse.neuron == null) continue; @@ -425,7 +429,7 @@ namespace NanoBrain { int row = 0; //List drawnNeurons = new(); drawnNeuronNames = new(); - foreach (Synapse synapse in nucleus.synapses) { + foreach (Synapse synapse in neuron.synapses) { if (synapse.neuron is null) continue; diff --git a/Runtime/Scripts/Brain.cs b/Runtime/Scripts/Brain.cs index acb28f3..afffba0 100644 --- a/Runtime/Scripts/Brain.cs +++ b/Runtime/Scripts/Brain.cs @@ -45,7 +45,7 @@ namespace NanoBrain { /// The name of the Neuron for which the weights are updated /// The new Synapse weight public static void UpdateWeight(Cluster brain, string name, float weight) { - Nucleus root = brain.defaultOutput; + Neuron root = brain.defaultOutput; foreach (Synapse synapse in root.synapses) { if (synapse.neuron.name == name) { if (synapse.weight != weight) { diff --git a/Runtime/Scripts/Core/Cluster.cs b/Runtime/Scripts/Core/Cluster.cs index 87366f3..657ae08 100644 --- a/Runtime/Scripts/Core/Cluster.cs +++ b/Runtime/Scripts/Core/Cluster.cs @@ -41,7 +41,7 @@ namespace NanoBrain { public Dictionary thingClusters = new(); [SerializeReference] - public List clusterNuclei = new(); + public List nuclei = new(); // the nuclei sorted using topological sorting // to ensure that the cluster is computer in the right order public List sortedNuclei; @@ -58,10 +58,10 @@ namespace NanoBrain { this.name = prefab.name; this.parent = parent; - this.parent?.clusterNuclei.Add(this); + this.parent?.nuclei.Add(this); ClonePrefab(); _ = this.inputs; - this.sortedNuclei = TopologicalSort(this.clusterNuclei); + this.sortedNuclei = TopologicalSort(this.nuclei); } /// @@ -75,11 +75,11 @@ namespace NanoBrain { this.clusterPrefab = parent; if (this.clusterPrefab != null) - this.clusterPrefab.nuclei.Add(this); + this.clusterPrefab.cluster.nuclei.Add(this); ClonePrefab(); _ = this.inputs; - this.sortedNuclei = TopologicalSort(this.clusterNuclei); + this.sortedNuclei = TopologicalSort(this.nuclei); } /// @@ -88,15 +88,13 @@ namespace NanoBrain { /// 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(); + Nucleus[] prefabNuclei = this.prefab.cluster.nuclei.ToArray(); // first clone the nuclei without their connections foreach (Nucleus nucleus in prefabNuclei) { nucleus.ShallowCloneTo(this); } - Nucleus[] clonedNuclei = this.clusterNuclei.ToArray(); - // foreach (Nucleus n in clonedNuclei) - // n.name += "(c)"; + Nucleus[] clonedNuclei = this.nuclei.ToArray(); // Now clone the connections for (int nucleusIx = 0; nucleusIx < prefabNuclei.Length; nucleusIx++) { @@ -113,16 +111,16 @@ namespace NanoBrain { if (synapseNeuron.clusterPrefab != null && synapseNeuron.clusterPrefab != this.prefab) { // Neuron is in another cluster, find the cloned cluster first ClusterPrefab prefabCluster = synapseNeuron.clusterPrefab; - Cluster clonedCluster = this.clusterNuclei.Find(n => n.name == prefabCluster.name) as Cluster; + Cluster clonedCluster = this.nuclei.Find(n => n.name == prefabCluster.name) as Cluster; if (clonedCluster == null) continue; // Now find the neuron in that cloned cluster - int neuronIx = GetNucleusIndex(prefabCluster.nuclei, prefabSynapse.neuron.name); + int neuronIx = GetNucleusIndex(prefabCluster.cluster.nuclei, prefabSynapse.neuron.name); if (neuronIx < 0) // Could not find the neuron in the prefab cluster continue; - if (clonedCluster.clusterNuclei[neuronIx] is not Neuron clonedSender) + if (clonedCluster.nuclei[neuronIx] is not Neuron clonedSender) // Could not find the neuron in the cloned cluster continue; @@ -291,16 +289,16 @@ namespace NanoBrain { if (synapseNeuron.clusterPrefab != null && synapseNeuron.clusterPrefab != this.prefab) { // Neuron is in another cluster, find the cloned cluster first ClusterPrefab prefabCluster = synapseNeuron.clusterPrefab; - Cluster clonedCluster = this.clusterNuclei.Find(n => n.name == prefabCluster.name) as Cluster; + Cluster clonedCluster = this.nuclei.Find(n => n.name == prefabCluster.name) as Cluster; if (clonedCluster == null) continue; // Now find the neuron in that cloned cluster - int neuronIx = GetNucleusIndex(prefabCluster.nuclei, prefabSynapse.neuron.name); + int neuronIx = GetNucleusIndex(prefabCluster.cluster.nuclei, prefabSynapse.neuron.name); if (neuronIx < 0) // Could not find the neuron in the prefab cluster continue; - if (clonedCluster.clusterNuclei[neuronIx] is not Neuron clonedSender) + if (clonedCluster.nuclei[neuronIx] is not Neuron clonedSender) // Could not find the neuron in the cloned cluster continue; @@ -308,7 +306,7 @@ namespace NanoBrain { //Debug.Log($"Add synapse {clonedCluster.name}.{clonedSender.name} -> {clonedNeuron.name} [{clonedSender.receivers.Count}]"); } else { - Neuron clonedSender = this.clusterNuclei.Find(n => n.name == prefabSynapse.neuron.name) as Neuron; + Neuron clonedSender = this.nuclei.Find(n => n.name == prefabSynapse.neuron.name) as Neuron; // Copy the receivers which will also create the synapse clonedSender.AddReceiver(clonedNeuron, prefabSynapse.weight); // Debug.Log($"Add synapse {clonedSender.name} -> {clonedNeuron.name}"); @@ -378,20 +376,20 @@ namespace NanoBrain { 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 (Synapse synapse in this.synapses) { + // Synapse clonedSynapse = clone.AddSynapse(synapse.neuron); + // clonedSynapse.weight = synapse.weight; + // } - foreach (Nucleus nucleus in this.clusterNuclei) { + foreach (Nucleus nucleus in this.nuclei) { if (nucleus is Neuron output) { foreach (Nucleus receiver in output.receivers) { - int ix = GetNucleusIndex(this.clusterNuclei, output); + int ix = GetNucleusIndex(this.nuclei, output); Debug.Log($"{output.name} -> {receiver.name}: {ix}"); if (ix < 0) continue; - if (clone.clusterNuclei[ix] is not Neuron clonedOutput) + if (clone.nuclei[ix] is not Neuron clonedOutput) continue; clonedOutput.AddReceiver(receiver); @@ -416,23 +414,26 @@ namespace NanoBrain { private static void CopyAllExternalReceivers(Cluster sourceCluster, Cluster sibling, ClusterPrefab prefabParent, Cluster clonedParent) { - for (int nucleusIx = 0; nucleusIx < sourceCluster.clusterNuclei.Count; nucleusIx++) { - Nucleus sourceNucleus = sourceCluster.clusterNuclei[nucleusIx]; + for (int nucleusIx = 0; nucleusIx < sourceCluster.nuclei.Count; nucleusIx++) { + Nucleus sourceNucleus = sourceCluster.nuclei[nucleusIx]; if (sourceNucleus is not Neuron sourceNeuron) continue; - if (sibling.clusterNuclei[nucleusIx] is not Neuron clonedNeuron) + if (sibling.nuclei[nucleusIx] is not Neuron clonedNeuron) continue; // copy the receivers (and thus synapses) from the source to the sibling foreach (Nucleus receiver in sourceNeuron.receivers) { - int ix = GetNucleusIndex(clonedParent.clusterNuclei, receiver); - if (ix < 0 || ix >= clonedParent.clusterNuclei.Count) + if (receiver is not Neuron receiverNeuron) + continue; + + int ix = GetNucleusIndex(clonedParent.nuclei, receiver); + if (ix < 0 || ix >= clonedParent.nuclei.Count) continue; // Find the synapse for the weight float weight = 1; - foreach (Synapse synapse in receiver.synapses) { + foreach (Synapse synapse in receiverNeuron.synapses) { // Find the weight for this synapse if (synapse.neuron == sourceNucleus) { weight = synapse.weight; @@ -603,9 +604,12 @@ namespace NanoBrain { get { if (this._inputs == null) { this._inputs = new(); - foreach (Nucleus nucleus in this.clusterNuclei) { + foreach (Nucleus nucleus in this.nuclei) { + if (nucleus is not Neuron neuron) + continue; + // inputs have no synapses - if (nucleus.synapses.Count == 0) + if (neuron.synapses.Count == 0) this._inputs.Add(nucleus); } ComputeOrders(); @@ -616,7 +620,7 @@ namespace NanoBrain { public Dictionary> computeOrders = new(); private void ComputeOrders() { - foreach (Nucleus nucleus in this.clusterNuclei) { + foreach (Nucleus nucleus in this.nuclei) { // if (nucleus is Cluster cluster) { // List synapses = this.CollectSynapsesTo(cluster); // foreach (Synapse synapse in synapses) { @@ -628,8 +632,8 @@ namespace NanoBrain { // // computeOrders[receiver] = TopologicalSort2(receiver); // } // else { - computeOrders[nucleus] = TopologicalSort2(nucleus); - Debug.Log($"{this.baseName} Order for {nucleus.name}"); + computeOrders[nucleus] = TopologicalSort2(nucleus); + //Debug.Log($"{this.baseName} Order for {nucleus.name}"); // } } } @@ -698,8 +702,8 @@ namespace NanoBrain { public virtual Neuron defaultOutput {//=> this.nuclei[0] as Nucleus; get { - if (this.clusterNuclei.Count > 0) - return this.clusterNuclei[0] as Neuron; + if (this.nuclei.Count > 0) + return this.nuclei[0] as Neuron; return null; } } @@ -708,7 +712,7 @@ namespace NanoBrain { get { if (this._outputs == null) { this._outputs = new(); - foreach (Nucleus nucleus in this.clusterNuclei) { + foreach (Nucleus nucleus in this.nuclei) { if (nucleus is Neuron neuron && neuron.receivers.Count == 0) this._outputs.Add(neuron); } @@ -721,7 +725,7 @@ namespace NanoBrain { } public bool TryGetNucleus(string nucleusName, out Nucleus foundNucleus) { - foreach (Nucleus receptor in this.clusterNuclei) { + foreach (Nucleus receptor in this.nuclei) { if (receptor is Nucleus nucleus) if (nucleus.name == nucleusName) { foundNucleus = nucleus; @@ -737,7 +741,7 @@ namespace NanoBrain { if (dotPosition >= 0) { string clusterName = nucleusName[..dotPosition]; string clusterName0 = clusterName + ": 0"; - foreach (Nucleus nucleus in this.clusterNuclei) { + foreach (Nucleus nucleus in this.nuclei) { if (nucleus is Cluster cluster) { if (cluster.name == clusterName || cluster.name == clusterName0) { string subNucleusName = nucleusName[(dotPosition + 1)..]; @@ -749,7 +753,7 @@ namespace NanoBrain { } else { string nucleusName0 = nucleusName + ": 0"; - foreach (Nucleus nucleus in this.clusterNuclei) { + foreach (Nucleus nucleus in this.nuclei) { if (nucleus is Cluster) { //IReceptor receptor) { if (nucleus.name == nucleusName | nucleus.name == nucleusName0) return nucleus; @@ -762,16 +766,16 @@ namespace NanoBrain { } public bool DeleteNucleus(Nucleus nucleus) { - if (this.clusterNuclei.Contains(nucleus) == false) { + if (this.nuclei.Contains(nucleus) == false) { // Try to find the nucleus by name if (TryGetNucleus(nucleus.name, out nucleus) == false) return false; } Neuron.Delete(nucleus); - int nucleusIx = this.clusterNuclei.IndexOf(nucleus); - this.clusterNuclei.Remove(nucleus); - this.prefab.nuclei.RemoveAt(nucleusIx); + int nucleusIx = this.nuclei.IndexOf(nucleus); + this.nuclei.Remove(nucleus); + this.prefab.cluster.nuclei.RemoveAt(nucleusIx); RefreshOutputs(); return true; @@ -781,7 +785,7 @@ namespace NanoBrain { public virtual List CollectReceivers(bool removeDuplicates = false) { List receivers = new(); - foreach (Nucleus outputNucleus in this.clusterNuclei) { + foreach (Nucleus outputNucleus in this.nuclei) { if (outputNucleus is not Neuron output) continue; @@ -802,7 +806,7 @@ namespace NanoBrain { public List<(Neuron, Nucleus)> CollectConnections() { List<(Neuron, Nucleus)> connections = new(); - foreach (Nucleus outputNucleus in this.clusterNuclei) { + foreach (Nucleus outputNucleus in this.nuclei) { if (outputNucleus is not Neuron output) continue; @@ -817,10 +821,10 @@ namespace NanoBrain { public List CollectSynapsesTo(Cluster otherCluster) { List collectedSynapses = new(); - foreach (Nucleus nucleus in this.clusterNuclei) { + foreach (Nucleus nucleus in this.nuclei) { if (nucleus is not Neuron neuron) continue; - foreach (Synapse synapse in nucleus.synapses) { + foreach (Synapse synapse in neuron.synapses) { if (synapse.neuron.parent == otherCluster) collectedSynapses.Add(synapse); } @@ -830,7 +834,7 @@ namespace NanoBrain { public void MoveReceivers(Cluster newCluster) { Debug.Log($"Move receivers for {this.name} to {newCluster.name}"); - foreach (Nucleus outputNucleus in this.clusterNuclei) { + foreach (Nucleus outputNucleus in this.nuclei) { if (outputNucleus is not Neuron output) continue; @@ -846,7 +850,9 @@ namespace NanoBrain { // Replace synapse with new synapse // to the new cluster Debug.Log($"move {receiver.name} from {this.name}.{output.name} to {newCluster.name}.{newOutput.name}"); - Synapse synapse = receiver.GetSynapse(output); + if (receiver is not Neuron receiverNeuron) + continue; + Synapse synapse = receiverNeuron.GetSynapse(output); newOutput.AddReceiver(receiver, synapse.weight); output.RemoveReceiver(receiver); } @@ -896,7 +902,7 @@ namespace NanoBrain { } public override void UpdateNuclei() { - foreach (Nucleus nucleus in this.clusterNuclei) + foreach (Nucleus nucleus in this.nuclei) nucleus.UpdateNuclei(); } diff --git a/Runtime/Scripts/Core/Neuron.cs b/Runtime/Scripts/Core/Neuron.cs index a4bbed8..f85a2e3 100644 --- a/Runtime/Scripts/Core/Neuron.cs +++ b/Runtime/Scripts/Core/Neuron.cs @@ -23,7 +23,7 @@ namespace NanoBrain { public Neuron(Cluster parent, string name) { this.parent = parent; this.name = name; - this.parent?.clusterNuclei.Add(this); + this.parent?.nuclei.Add(this); } /// /// Create a new Neuron in a Cluster Prefab @@ -34,8 +34,8 @@ namespace NanoBrain { this.clusterPrefab = prefab; this.name = name; if (this.clusterPrefab != null) { - this.clusterPrefab.nuclei.Add(this); - this.clusterPrefab.RefreshOutputs(); + this.clusterPrefab.cluster.nuclei.Add(this); + this.clusterPrefab.cluster.RefreshOutputs(); } else Debug.LogError("No prefab when adding neuron to prefab"); @@ -43,6 +43,71 @@ namespace NanoBrain { #region Serialization + /// + /// The bias + /// + /// The bias which a value which is always added to the combined value of the neuron + /// It does not have a synapse and therefore no weight of source nucleus + public Vector3 bias = Vector3.zero; + + + #region Synapses + + [SerializeField] + private List _synapses = new(); + /// + /// The synapses of the nucleus + /// + public List synapses => _synapses; + + /// + /// Add a new synapse to this nuclues + /// + /// The nucleus from which the signals may originate + /// The weight applied to the input. Default value = 1 + /// The created Synapse + /// This will add a new input to this nucleus with the given weight. + public Synapse AddSynapse(Neuron sendingNucleus, float weight = 1) { + Synapse synapse = new(sendingNucleus, weight); + this.synapses.Add(synapse); + return synapse; + } + + // public Synapse AddSynapse(ClusterPrefab clusterPrefab, string neuronName, float weight = 1) { + + // } + + /// + /// Find a synapse + /// + /// The sender of the input to the Synapse + /// The found Synapse or null when the sender has no synapse to this nucleus. + public Synapse GetSynapse(Nucleus sender) { + foreach (Synapse synapse in this.synapses) + if (synapse.neuron == sender) + return synapse; + return null; + } + + /// + /// Remove a synapse from a Nucleus + /// + /// Remote the synapse connecting to this Nucleus + public void RemoveSynapse(Nucleus sendingNucleus) { + this.synapses.RemoveAll(synapse => synapse.neuron == sendingNucleus); + } + + #endregion Synapses + + /// + /// Set the bias, recalculate the output and update all Nuclei receiving from this Nucleus + /// + /// + public virtual void SetBias(Vector3 inputValue) { + this.bias = inputValue; + this.parent.UpdateFromNucleus(this); + } + /// /// The type of combinators /// @@ -231,13 +296,20 @@ namespace NanoBrain { } } + /// + /// Toggle for printing debugging trace data + /// + public bool trace = false; + [NonSerialized] public float lastUpdate = 0; public readonly float timeToSleep = 1f; /// \copydoc NanoBrain::Nucleus::ShallowCloneTo public override Nucleus ShallowCloneTo(Cluster newParent) { - Neuron clone = new(newParent, this.name); + Neuron clone = new(newParent, this.name) { + // prefabNucleus = this + }; CloneFields(clone); return clone; } @@ -268,8 +340,8 @@ namespace NanoBrain { public static void Delete(Nucleus nucleus) { if (nucleus == null) return; - if (nucleus.synapses != null) { - foreach (Synapse synapse in nucleus.synapses) { + if (nucleus is Neuron neuron) { + foreach (Synapse synapse in neuron.synapses) { if (synapse.neuron is Neuron synapse_nucleus) { if (synapse_nucleus.receivers.Count > 1) { // there is another nucleus feeding into this input nucleus @@ -281,19 +353,21 @@ namespace NanoBrain { } } } - } - if (nucleus is Neuron neuron) { foreach (Nucleus receiver in neuron.receivers) { - if (receiver != null && receiver.synapses != null) - receiver.synapses.RemoveAll(s => s.neuron == nucleus); + if (receiver is not Neuron receiverNeuron) + continue; + if (receiver != null && receiverNeuron.synapses != null) + receiverNeuron.synapses.RemoveAll(s => s.neuron == nucleus); } } else if (nucleus is Cluster cluster) { // remove all receivers for this cluster - foreach (Nucleus clusterNucleus in cluster.clusterNuclei) { + foreach (Nucleus clusterNucleus in cluster.nuclei) { if (clusterNucleus is Neuron output) { foreach (Nucleus receiver in output.receivers) { - receiver.synapses.RemoveAll(s => s.neuron == output); + if (receiver is not Neuron receiverNeuron) + continue; + receiverNeuron.synapses.RemoveAll(s => s.neuron == output); } } } @@ -301,8 +375,8 @@ namespace NanoBrain { if (nucleus.clusterPrefab != null) { - nucleus.clusterPrefab.nuclei.RemoveAll(n => n == nucleus); - nucleus.clusterPrefab.RefreshOutputs(); + nucleus.clusterPrefab.cluster.nuclei.RemoveAll(n => n == nucleus); + nucleus.clusterPrefab.cluster.RefreshOutputs(); nucleus.clusterPrefab.GarbageCollection(); } } @@ -524,15 +598,25 @@ namespace NanoBrain { } public virtual void AddReceiver(Nucleus receiverToAdd, float weight = 1) { - this._receivers.Add(receiverToAdd); - receiverToAdd.AddSynapse(this, weight); + if (receiverToAdd is not Neuron receiverNeuron) + return; + this._receivers.Add(receiverNeuron); + receiverNeuron.AddSynapse(this, weight); //Debug.Log($"Add synapse {this.clusterPrefab.name}.{this.name} -> {receiverToAdd.name} --- [{this.receivers.Count}]"); } public virtual void RemoveReceiver(Nucleus receiverToRemove) { - this._receivers.RemoveAll(receiver => receiver == receiverToRemove); - receiverToRemove.synapses.RemoveAll(synapse => synapse.neuron == this); + if (receiverToRemove is not Neuron receiverNeuron) + return; + this._receivers.RemoveAll(receiver => receiver == receiverNeuron); + receiverNeuron.synapses.RemoveAll(synapse => synapse.neuron == this); + + // Nucleus prefabReceiver = receiverToRemove.prefabNucleus; + // if (this.prefabNucleus is Neuron prefabNeuron && prefabReceiver != null) { + // prefabNeuron.receivers.RemoveAll(receiver => receiver == prefabReceiver); + // prefabReceiver.synapses.RemoveAll(synapse => synapse.neuron == prefabNeuron); + // } } diff --git a/Runtime/Scripts/Core/Nucleus.cs b/Runtime/Scripts/Core/Nucleus.cs index 314fee3..ecd9579 100644 --- a/Runtime/Scripts/Core/Nucleus.cs +++ b/Runtime/Scripts/Core/Nucleus.cs @@ -17,6 +17,9 @@ public abstract class Nucleus { /// public string name; + // [NonSerialized] + // public Nucleus prefabNucleus; + /// /// The cluster prefab in which the nucleus is located /// @@ -31,7 +34,7 @@ public abstract class Nucleus { /// /// Toggle for printing debugging trace data /// - public bool trace = false; + //public bool trace = false; /// /// Function to make a partial clone of this nucleus @@ -61,60 +64,60 @@ public abstract class Nucleus { public virtual void Initialize() {} - #region Synapses + // #region Synapses - /// - /// The bias of the nucleus - /// - /// The bias which a value which is always added to the combined value of the nucleus - /// It does not have a synapse and therefore no weight of source nucleus - public Vector3 bias = Vector3.zero; + // /// + // /// The bias of the nucleus + // /// + // /// The bias which a value which is always added to the combined value of the nucleus + // /// It does not have a synapse and therefore no weight of source nucleus + // //public Vector3 bias = Vector3.zero; - [SerializeField] - private List _synapses = new(); - /// - /// The synapses of the nucleus - /// - public List synapses => _synapses; + // [SerializeField] + // private List _synapses = new(); + // /// + // /// The synapses of the nucleus + // /// + // public List synapses => _synapses; - /// - /// Add a new synapse to this nuclues - /// - /// The nucleus from which the signals may originate - /// The weight applied to the input. Default value = 1 - /// The created Synapse - /// This will add a new input to this nucleus with the given weight. - public Synapse AddSynapse(Neuron sendingNucleus, float weight = 1) { - Synapse synapse = new(sendingNucleus, weight); - this.synapses.Add(synapse); - return synapse; - } + // /// + // /// Add a new synapse to this nuclues + // /// + // /// The nucleus from which the signals may originate + // /// The weight applied to the input. Default value = 1 + // /// The created Synapse + // /// This will add a new input to this nucleus with the given weight. + // public Synapse AddSynapse(Neuron sendingNucleus, float weight = 1) { + // Synapse synapse = new(sendingNucleus, weight); + // this.synapses.Add(synapse); + // return synapse; + // } - // public Synapse AddSynapse(ClusterPrefab clusterPrefab, string neuronName, float weight = 1) { + // // public Synapse AddSynapse(ClusterPrefab clusterPrefab, string neuronName, float weight = 1) { - // } + // // } - /// - /// Find a synapse - /// - /// The sender of the input to the Synapse - /// The found Synapse or null when the sender has no synapse to this nucleus. - public Synapse GetSynapse(Nucleus sender) { - foreach (Synapse synapse in this.synapses) - if (synapse.neuron == sender) - return synapse; - return null; - } + // /// + // /// Find a synapse + // /// + // /// The sender of the input to the Synapse + // /// The found Synapse or null when the sender has no synapse to this nucleus. + // public Synapse GetSynapse(Nucleus sender) { + // foreach (Synapse synapse in this.synapses) + // if (synapse.neuron == sender) + // return synapse; + // return null; + // } - /// - /// Remove a synapse from a Nucleus - /// - /// Remote the synapse connecting to this Nucleus - public void RemoveSynapse(Nucleus sendingNucleus) { - this.synapses.RemoveAll(synapse => synapse.neuron == sendingNucleus); - } + // /// + // /// Remove a synapse from a Nucleus + // /// + // /// Remote the synapse connecting to this Nucleus + // public void RemoveSynapse(Nucleus sendingNucleus) { + // this.synapses.RemoveAll(synapse => synapse.neuron == sendingNucleus); + // } - #endregion Synapses + // #endregion Synapses #region Update @@ -129,14 +132,14 @@ public abstract class Nucleus { public virtual void UpdateNuclei() { } - /// - /// Set the bias, recalculate the output and update all Nuclei receiving from this Nucleus - /// - /// - public virtual void SetBias(Vector3 inputValue) { - this.bias = inputValue; - this.parent.UpdateFromNucleus(this); - } + // /// + // /// Set the bias, recalculate the output and update all Nuclei receiving from this Nucleus + // /// + // /// + // public virtual void SetBias(Vector3 inputValue) { + // this.bias = inputValue; + // this.parent.UpdateFromNucleus(this); + // } /// /// Process an external stimulus diff --git a/Runtime/Scripts/ScriptableObjects/ClusterPrefab.cs b/Runtime/Scripts/ScriptableObjects/ClusterPrefab.cs index 2920733..df72c27 100644 --- a/Runtime/Scripts/ScriptableObjects/ClusterPrefab.cs +++ b/Runtime/Scripts/ScriptableObjects/ClusterPrefab.cs @@ -8,87 +8,94 @@ namespace NanoBrain { /// [CreateAssetMenu(menuName = "Passer/Cluster")] public class ClusterPrefab : ScriptableObject { - /// The nuclei in this cluster - [SerializeReference] - // This list should not include any clusters... - public List nuclei = new(); - /// - /// The output of this cluster - /// - /// This only returens the first(default) nucleus. Use outputs[0] instead - public virtual Nucleus output => this.nuclei[0] as Nucleus; + public Cluster cluster; + /* + /// The nuclei in this cluster + [SerializeReference] + // This list should not include any clusters... + public List nuclei = new(); - /// - /// The nuclei in this cluster which are meant for receiving signals from outside the cluster - /// - /// This is currently the nuclei which do not have any incoming synapse - public List _inputs = null; - public virtual List inputs { - get { - if (this._inputs == null) { - this._inputs = new(); - foreach (Nucleus receptor in this.nuclei) { - if (receptor is Nucleus nucleus) { - // inputs have no incoming synapses yet. - if (nucleus.synapses.Count == 0) - this._inputs.Add(nucleus); + /// + /// The output of this cluster + /// + /// This only returens the first(default) nucleus. Use outputs[0] instead + public virtual Nucleus output => this.nuclei[0] as Nucleus; + + /// + /// The nuclei in this cluster which are meant for receiving signals from outside the cluster + /// + /// This is currently the nuclei which do not have any incoming synapse + public List _inputs = null; + public virtual List inputs { + get { + if (this._inputs == null) { + this._inputs = new(); + foreach (Nucleus receptor in this.nuclei) { + if (receptor is Nucleus nucleus) { + // inputs have no incoming synapses yet. + if (nucleus.synapses.Count == 0) + this._inputs.Add(nucleus); + } + } } + return this._inputs; } } - return this._inputs; - } - } - /// - /// The nuclei in this cluster which are meant for sending signals onward - /// - private List _outputs = null; - public List outputs { - get { - if (this._outputs == null) - RefreshOutputs(); - return this._outputs; - } - } - /// - /// Redetermine the outpus in the cluster - /// - public void RefreshOutputs() { - this._outputs = new(); - foreach (Nucleus nucleus in this.nuclei) { - if (nucleus is Neuron neuron && neuron.receivers.Count == 0) - this._outputs.Add(nucleus); - } - } - + /// + /// The nuclei in this cluster which are meant for sending signals onward + /// + private List _outputs = null; + public List outputs { + get { + if (this._outputs == null) + RefreshOutputs(); + return this._outputs; + } + } + /// + /// Redetermine the outpus in the cluster + /// + public void RefreshOutputs() { + this._outputs = new(); + foreach (Nucleus nucleus in this.nuclei) { + if (nucleus is Neuron neuron && neuron.receivers.Count == 0) + this._outputs.Add(nucleus); + } + } + */ /// /// Retrieve a nucleus in this cluster /// /// The name of the nucleus /// The Nucleus with the given name or null if no such Nucleus could be found public Nucleus GetNucleus(string nucleusName) { - foreach (Nucleus nucleus in this.nuclei) { - if (nucleus.name == nucleusName) - return nucleus; - } - return null; + return cluster.GetNucleus(nucleusName); + // foreach (Nucleus nucleus in this.nuclei) { + // if (nucleus.name == nucleusName) + // return nucleus; + // } + // return null; } // Call this function to ensure that there is at least one nucleus // This is an invariant and should be ensured before the nucleus is used // because output requires it. public void EnsureInitialization() { - nuclei ??= new List(); - if (nuclei.Count == 0) + this.cluster.nuclei ??= new List(); + if (this.cluster.nuclei.Count <= 0) new Neuron(this, "Output"); // Every cluster should have at least 1 neuron + // nuclei ??= new List(); + // if (nuclei.Count == 0) + // new Neuron(this, "Output"); // Every cluster should have at least 1 neuron } public void GarbageCollection() { - HashSet visitedNuclei = new(); - foreach (Nucleus output in this.outputs) - MarkNuclei(visitedNuclei, output); - //Debug.Log($"Garbage collection found {visitedNuclei.Count} Nuclei"); - this.nuclei.RemoveAll(nucleus => visitedNuclei.Contains(nucleus) == false); + // HashSet visitedNuclei = new(); + // foreach (Nucleus output in this.outputs) + // MarkNuclei(visitedNuclei, output); + // //Debug.Log($"Garbage collection found {visitedNuclei.Count} Nuclei"); + // this.nuclei.RemoveAll(nucleus => visitedNuclei.Contains(nucleus) == false); } public void MarkNuclei(HashSet visitedNuclei, Nucleus nucleus) { @@ -99,43 +106,45 @@ namespace NanoBrain { visitedNuclei.Add(nucleus.parent); else visitedNuclei.Add(nucleus); - if (nucleus.synapses != null) { - HashSet visitedSynapses = new(); - foreach (Synapse synapse in nucleus.synapses) { - if (synapse != null && synapse.neuron != null) { - visitedSynapses.Add(synapse); - if (synapse.neuron is Nucleus synapse_nucleus) - MarkNuclei(visitedNuclei, synapse_nucleus); + if (nucleus is Neuron neuron) { + if (neuron.synapses != null) { + HashSet visitedSynapses = new(); + foreach (Synapse synapse in neuron.synapses) { + if (synapse != null && synapse.neuron != null) { + visitedSynapses.Add(synapse); + if (synapse.neuron is Nucleus synapse_nucleus) + MarkNuclei(visitedNuclei, synapse_nucleus); + } } + neuron.synapses.RemoveAll(synapse => visitedSynapses.Contains(synapse) == false); } - nucleus.synapses.RemoveAll(synapse => visitedSynapses.Contains(synapse) == false); - } - if (nucleus is Neuron neuron && neuron.receivers != null) { - HashSet visitedReceivers = new(); - foreach (Nucleus receiver in neuron.receivers) { - if (receiver != null && receiver != null) { - visitedReceivers.Add(receiver); - visitedNuclei.Add(receiver); + if (neuron.receivers != null) { + HashSet visitedReceivers = new(); + foreach (Nucleus receiver in neuron.receivers) { + if (receiver != null && receiver != null) { + visitedReceivers.Add(receiver); + visitedNuclei.Add(receiver); + } } + neuron.receivers.RemoveAll(receiver => visitedReceivers.Contains(receiver) == false); } - neuron.receivers.RemoveAll(receiver => visitedReceivers.Contains(receiver) == false); } } - public virtual void UpdateNuclei() { - foreach (Nucleus nucleus in this.nuclei) - nucleus.UpdateNuclei(); - } + // public virtual void UpdateNuclei() { + // foreach (Nucleus nucleus in this.nuclei) + // nucleus.UpdateNuclei(); + // } - public int GetNucleusIndex(Nucleus receiver) { - int ix = 0; - foreach (Nucleus nucleus in this.nuclei) { - if (receiver == nucleus) - return ix; - ix++; - } - return -1; - } + // public int GetNucleusIndex(Nucleus receiver) { + // int ix = 0; + // foreach (Nucleus nucleus in this.nuclei) { + // if (receiver == nucleus) + // return ix; + // ix++; + // } + // return -1; + // } } }