From eb2adaeec37636342242f4ccc441faa61a343307 Mon Sep 17 00:00:00 2001 From: Pascal Serrarens Date: Tue, 30 Jun 2026 11:37:21 +0200 Subject: [PATCH] Fix dangling clusters --- Editor/ClusterEditor.cs | 16 ++++++++++------ Runtime/Scripts/Core/Cluster.cs | 11 +++++++++++ Runtime/Scripts/Core/Neuron.cs | 30 ++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 6 deletions(-) diff --git a/Editor/ClusterEditor.cs b/Editor/ClusterEditor.cs index a6e8ee5..c19cc2c 100644 --- a/Editor/ClusterEditor.cs +++ b/Editor/ClusterEditor.cs @@ -19,6 +19,8 @@ namespace NanoBrain.Unity { view.currentCluster ??= clusterPrefab.cluster; view.currentNucleus = clusterPrefab.cluster.defaultOutput; view.selectedOutput = view.currentNucleus; + + clusterPrefab.cluster.Cleanup(); } public override void OnInspectorGUI() { @@ -395,24 +397,26 @@ namespace NanoBrain.Unity { .Where(synapse => synapse.neuron != null) .Select(synapse => synapse.neuron); - IEnumerable nuclei = cluster.cluster.nuclei - .Except(synapseNuclei); + Nucleus[] nuclei = cluster.cluster.nuclei + .Except(synapseNuclei).Distinct().ToArray(); IEnumerable nucleiNames = nuclei .Select(n => { int idx = n.name.IndexOf(':'); return idx < 0 ? n.name : n.name[..idx]; - }) - .Distinct(); + }); string[] names = nucleiNames.ToArray(); EditorGUILayout.BeginHorizontal(); selectedConnectNucleus = EditorGUILayout.Popup(selectedConnectNucleus, names); bool connecting = GUILayout.Button("Connect", GUILayout.Width(80)); EditorGUILayout.EndHorizontal(); - if (connecting) { - Nucleus nucleus = nuclei.ElementAt(selectedConnectNucleus); + if (connecting && selectedConnectNucleus >= 0 && selectedConnectNucleus < nuclei.Length) { + Nucleus nucleus = nuclei[selectedConnectNucleus];//nuclei.ElementAt(selectedConnectNucleus); if (nucleus is Neuron neuron) neuron.AddReceiver(this.view.currentNucleus); + else if (nucleus is Cluster clusterToConnect) { + clusterToConnect.defaultOutput.AddReceiver(this.view.currentNucleus); + } this.view.currentCluster.Refresh(); } return connecting; diff --git a/Runtime/Scripts/Core/Cluster.cs b/Runtime/Scripts/Core/Cluster.cs index ef0cac0..18726f0 100644 --- a/Runtime/Scripts/Core/Cluster.cs +++ b/Runtime/Scripts/Core/Cluster.cs @@ -711,6 +711,7 @@ namespace NanoBrain { /// /// This can be used to recalculate derived properties after the set of nuclei has been changed public void Refresh() { + Cleanup(); // This should not be needed, but somehow somewhere the parent is changed... foreach (Nucleus nucleus in this.nuclei) { nucleus.parent = this; @@ -719,6 +720,16 @@ namespace NanoBrain { RefreshComputeOrders(); } + public void Cleanup() { + foreach (Nucleus nucleus in this.nuclei.ToArray()) { + if (nucleus is not Cluster cluster) + continue; + List receivers = cluster.CollectReceivers(); + Debug.Log($"cluster receiver count = {receivers.Count}"); + if (receivers.Count == 0) + this.nuclei.Remove(nucleus); + } + } } } \ No newline at end of file diff --git a/Runtime/Scripts/Core/Neuron.cs b/Runtime/Scripts/Core/Neuron.cs index 58e1946..cb5639c 100644 --- a/Runtime/Scripts/Core/Neuron.cs +++ b/Runtime/Scripts/Core/Neuron.cs @@ -363,6 +363,7 @@ namespace NanoBrain #region Combinator #if UNITY_MATHEMATICS + [NonSerialized] public float3 combinationValue; /// @@ -742,6 +743,35 @@ namespace NanoBrain Debug.Log($"Updated weight: {error.magnitude} {error} {scaledOutput} {synapse.weight}"); } + public void BackPropagationWithLoss(Synapse synapse, Vector3 loss, float learningRate) + { + // loss is a derivative of error + // Backpropagation = loss * d(combinator) + + Vector3 delta2; + switch (activator) + { + case ActivationType.Linear: + // Derivative of this (f'()) would be 1. + delta2 = loss * 1; + break; + case ActivationType.Power: + delta2 = loss * (2 * this.combinationValue); + break; + case ActivationType.Reciprocal: + delta2 = loss * (-1 / (this.combinationValue * this.combinationValue)); + break; + default: + delta2 = loss; + break; + } + + Vector3 scaledOutput = Vector3.Scale(delta2, synapse.neuron.outputValue); + float deltaWeight = Mathf.Abs(scaledOutput.x) + Mathf.Abs(scaledOutput.y) + Mathf.Abs(scaledOutput.z); + synapse.weight += learningRate * deltaWeight; + Debug.Log($"Updated weight: {loss.magnitude} {loss} {scaledOutput} {synapse.weight}"); + } + #endregion Back propagation ///