From 537064d84be62d14ae41ce59265492bb70edfafb Mon Sep 17 00:00:00 2001 From: Pascal Serrarens Date: Thu, 12 Feb 2026 15:14:55 +0100 Subject: [PATCH] Receptorarrays seems to work --- Cluster.cs | 2 +- Editor/ClusterInspector.cs | 54 +++++-- Neuron.cs | 2 +- Nucleus.cs | 7 +- ReceptorArray.cs | 301 ++++++++++++++++++++++++++++--------- 5 files changed, 275 insertions(+), 91 deletions(-) diff --git a/Cluster.cs b/Cluster.cs index a05f20b..84ee436 100644 --- a/Cluster.cs +++ b/Cluster.cs @@ -368,7 +368,7 @@ public class Cluster : Nucleus { } public override void UpdateStateIsolated() { - Vector3 sum = this.bias; + float3 sum = this.bias; //Applying the weight factors foreach (Synapse synapse in this.synapses) { diff --git a/Editor/ClusterInspector.cs b/Editor/ClusterInspector.cs index bd0e313..22c786b 100644 --- a/Editor/ClusterInspector.cs +++ b/Editor/ClusterInspector.cs @@ -424,13 +424,16 @@ public class ClusterInspector : Editor { normal = { textColor = Color.white }, fontStyle = FontStyle.Bold, }; - //if (nucleus is Nucleus neuron) { + if (nucleus.array == null || nucleus.array.nuclei == null || nucleus.array.nuclei.Count() == 0) nucleus.array = new NucleusArray(nucleus); if ((!expandArray || nucleus.array.nuclei.First() != this.currentNucleus) && nucleus.array.nuclei.Count() > 1) { Handles.Label(labelPosition, nucleus.array.nuclei.Count().ToString(), style); } + if (!expandArray && nucleus is ReceptorArray receptor) { + Handles.Label(labelPosition, receptor.receptors.Count().ToString(), style); + } if (expandArray && nucleus.array.nuclei.First() == this.currentNucleus) { int arrayIx = 0; foreach (Nucleus n in nucleus.array.nuclei) { @@ -456,12 +459,6 @@ public class ClusterInspector : Editor { Handles.color = Color.white; Handles.DrawWireDisc(position, Vector3.forward, size + 10); } - // } - // else { - // style.alignment = TextAnchor.UpperCenter; - // Vector3 labelPos = position - Vector3.down * (size + 10); // below disc along up axis - // Handles.Label(labelPos, nucleus.name, style); - // } // Tooltip Rect neuronRect = new(position.x - size, position.y - size, size * 2, size * 2); @@ -565,6 +562,21 @@ public class ClusterInspector : Editor { if (this.currentNucleus is MemoryCell memory) { memory.staticMemory = EditorGUILayout.Toggle("Static Memory", memory.staticMemory); } + if (this.currentNucleus is ReceptorArray receptor) { + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.IntField("Receptor size", receptor.receptors.Count()); + if (GUILayout.Button("Add")) { + Undo.RecordObject(prefabAsset, "Receptor add " + prefabAsset.name); + receptor.AddReceptor(this.prefab); + anythingChanged = true; + } + if (GUILayout.Button("Del")) { + Undo.RecordObject(prefabAsset, "Receptor delete " + prefabAsset.name); + receptor.RemoveReceptor(); + anythingChanged = true; + } + EditorGUILayout.EndHorizontal(); + } // Synapses @@ -689,10 +701,20 @@ public class ClusterInspector : Editor { void OnSceneGUI(SceneView sceneView) { if (this.gameObject != null) { - foreach (Nucleus nucleus in this.currentNucleus.array.nuclei) { - Vector3 worldVector = this.gameObject.transform.TransformVector(nucleus.outputValue); - Handles.color = Color.yellow; - Handles.DrawLine(this.gameObject.transform.position, this.gameObject.transform.position + worldVector); + if (this.currentNucleus is ReceptorArray receptor) { + foreach (Nucleus nucleus in receptor.receptors) { + Vector3 worldVector = this.gameObject.transform.TransformVector(nucleus.outputValue); + Handles.color = Color.yellow; + Handles.DrawLine(this.gameObject.transform.position, this.gameObject.transform.position + worldVector); + } + + } + else { + foreach (Nucleus nucleus in this.currentNucleus.array.nuclei) { + Vector3 worldVector = this.gameObject.transform.TransformVector(nucleus.outputValue); + Handles.color = Color.yellow; + Handles.DrawLine(this.gameObject.transform.position, this.gameObject.transform.position + worldVector); + } } } } @@ -714,6 +736,9 @@ public class ClusterInspector : Editor { case Nucleus.Type.Pulsar: AddPulsarInput(nucleus); break; + case Nucleus.Type.Receptor: + AddReceptorInput(nucleus); + break; default: break; } @@ -793,6 +818,13 @@ public class ClusterInspector : Editor { ClusterPickerWindow.ShowPicker(brain => OnClusterPicked(nucleus, brain), "Select Cluster"); } + protected virtual void AddReceptorInput(Nucleus nucleus) { + ReceptorArray newReceptor = new(this.prefab, "New Receptor"); + newReceptor.AddReceiver(nucleus); + this.currentNucleus = newReceptor; + BuildLayers(); + } + private void OnClusterPicked(Nucleus nucleus, ClusterPrefab prefab) { Cluster subclusterInstance = new(prefab, this.prefab); subclusterInstance.AddReceiver(nucleus); diff --git a/Neuron.cs b/Neuron.cs index 0a58257..461240a 100644 --- a/Neuron.cs +++ b/Neuron.cs @@ -192,7 +192,7 @@ public class Neuron : Nucleus { }; public float3 CombinatorSum() { - Vector3 sum = this.bias; + float3 sum = this.bias; foreach (Synapse synapse in this.synapses) sum += synapse.weight * synapse.nucleus.outputValue; return sum; diff --git a/Nucleus.cs b/Nucleus.cs index 64d1fe4..83fb34a 100644 --- a/Nucleus.cs +++ b/Nucleus.cs @@ -13,7 +13,7 @@ public abstract class Nucleus { public Cluster parent { get; set; } protected float3 _outputValue; - public virtual Vector3 outputValue { + public virtual float3 outputValue { get { return _outputValue; } set { //this.stale = 0; @@ -40,7 +40,8 @@ public abstract class Nucleus { MemoryCell, Selector, Cluster, - Pulsar + Pulsar, + Receptor } #region Synapses @@ -110,7 +111,7 @@ public abstract class Nucleus { } } - public void ProcessStimulus(int thingId, Vector3 inputValue, string thingName = null) { + public virtual void ProcessStimulus(int thingId, Vector3 inputValue, string thingName = null) { this.array.ProcessStimulus(thingId, inputValue, thingName); } diff --git a/ReceptorArray.cs b/ReceptorArray.cs index 70afe30..27ba835 100644 --- a/ReceptorArray.cs +++ b/ReceptorArray.cs @@ -1,87 +1,238 @@ -/* +using System; using System.Collections.Generic; +using System.Linq; using UnityEngine; +using Unity.Mathematics; +using static Unity.Mathematics.math; -public class ReceptorArray { - public string name; - - public Cluster parent { get; set; } - - public NucleusArray nuclei; - public int size => nuclei.nuclei.Length; - - public Dictionary thingReceivers = new(); - - public ReceptorArray(Cluster parent, NucleusArray nuclei) { +[Serializable] +public class ReceptorInstance : Nucleus { + public ReceptorInstance(Cluster parent, string name) { this.parent = parent; - this.nuclei = nuclei; + this.name = name; + // We explicitly do not add this to the parent, as it is serialized in the ReceptorArray + } + public ReceptorInstance(ClusterPrefab prefab, string name) { + this.cluster = prefab; + this.name = name; + // We explicitly do not add this to the prefab, as it is serialized in the ReceptorArray + } + public override Nucleus ShallowCloneTo(Cluster parent) { + ReceptorInstance clone = new(parent, name + " +1"); + return clone; + } + public override Nucleus Clone(ClusterPrefab prefab) { + ReceptorInstance clone = new(prefab, name); + return clone; + } + public override void UpdateStateIsolated() { + } +} + +[Serializable] +public class ReceptorArray : Nucleus { + public ReceptorArray(Cluster parent, string name) { + this.parent = parent; + this.name = name; + this._receptors = new ReceptorInstance[1]; + this._receptors[0] = new ReceptorInstance(parent, this.name + "[0]"); + this.parent?.nuclei.Add(this); + } + public ReceptorArray(ClusterPrefab prefab, string name) { + this.cluster = prefab; + this.name = name; + this._receptors = new ReceptorInstance[1]; + this._receptors[0] = new ReceptorInstance(prefab, this.name + "[0]"); + if (this.cluster != null) + this.cluster.nuclei.Add(this); + else + Debug.LogError("No prefab when adding receptor to prefab"); + } - public virtual void ProcessStimulus(int thingId, Vector3 newLocalPositionVector, string thingName = null) { - if (!thingReceivers.TryGetValue(thingId, out INucleus selectedReceiver)) { - foreach (IReceptor receptor in this.nuclei.nuclei) { - if (receptor is not INucleus receiver) - continue; - if (thingReceivers.ContainsValue(receiver)) - continue; - // receiver is not used yet + public override Nucleus ShallowCloneTo(Cluster parent) { + ReceptorArray clone = new(parent, name) { + _receptors = new ReceptorInstance[this.receptors.Length] + }; + for (int ix = 0; ix < this.receptors.Length; ix++) { + clone._receptors[ix] = new ReceptorInstance(parent, $"{this.name} [{ix}]"); + } + + return clone; + } + + public override Nucleus Clone(ClusterPrefab prefab) { + ReceptorArray clone = new(prefab, this.name) { + }; + clone._receptors = new ReceptorInstance[this.receptors.Length]; + for (int ix = 0; ix < this.receptors.Length; ix++) { + clone._receptors[ix] = new ReceptorInstance(prefab, this.name); + } + + foreach (Synapse synapse in this.synapses) { + Synapse clonedSynapse = clone.AddSynapse(synapse.nucleus); + clonedSynapse.weight = synapse.weight; + } + foreach (Nucleus receiver in this.receivers) { + clone.AddReceiver(receiver); + } + return clone; + } + + [SerializeReference] + private ReceptorInstance[] _receptors; + public ReceptorInstance[] receptors { + get { + return _receptors; + } + } + + public void AddReceptor(ClusterPrefab prefab) { + if (this._receptors.Length == 0) { + Debug.LogError("Empty receptor array, cannot add"); + return; + } + int newLength = this._receptors.Length + 1; + ReceptorInstance[] newArray = new ReceptorInstance[newLength]; + + for (int i = 0; i < this._receptors.Length; i++) + newArray[i] = this._receptors[i]; + newArray[newLength - 1] = (ReceptorInstance)this._receptors[0].Clone(prefab); + + this._receptors = newArray; + } + + public void RemoveReceptor() { + int newLength = this._receptors.Length - 1; + if (newLength == 0) { + Debug.LogWarning("Receptor array cannot be empty"); + return; + } + ReceptorInstance[] newPerceptei = new ReceptorInstance[newLength]; + for (int i = 0; i < newLength; i++) + newPerceptei[i] = this._receptors[i]; + // Delete the last perception + if (this._receptors[newLength] is Nucleus nucleus) + Neuron.Delete(nucleus); //this._nuclei[newLength]); + + this._receptors = newPerceptei; + } + + private Dictionary thingReceivers = new(); + + public override void ProcessStimulus(int thingId, Vector3 inputValue, string thingName = null) { + ProcessStimulus(inputValue, thingId, thingName); + } + public void ProcessStimulus(Vector3 inputValue, int thingId = 0, string thingName = null) { + CleanupReceivers(); + if (!thingReceivers.TryGetValue(thingId, out Nucleus selectedReceiver)) { + Debug.Log($" no receiver found for {thingId}"); + // No existing nucleus for this thing + selectedReceiver = SelectReceptor(thingId, inputValue); + } + if (selectedReceiver == null) + return; + + if (thingName != null) { + string baseName = selectedReceiver.name; + int colonPos = selectedReceiver.name.IndexOf(":"); + if (colonPos > 0) + baseName = selectedReceiver.name[..colonPos]; + selectedReceiver.name = baseName + ": " + thingName; + } + + //if (selectedReceiver is Neuron selectedNucleus) { + selectedReceiver.stale = 0; + selectedReceiver.outputValue = inputValue; + this.parent.UpdateFromNucleus(this); + //selectedNucleus.ProcessStimulus(inputValue); + //} + } + + private void CleanupReceivers() { + // Remove a thing-receiver connection when the nucleus is inactive + List receiversToRemove = new(); + foreach (KeyValuePair item in thingReceivers) { + if (item.Value.isSleeping) + receiversToRemove.Add(item.Key); + } + foreach (int thingId in receiversToRemove) { + Nucleus selectedReceiver = thingReceivers[thingId]; + + Debug.Log($" removed receiver for {thingId}"); + thingReceivers.Remove(thingId); + + int colonPos = selectedReceiver.name.IndexOf(":"); + if (colonPos > 0) + selectedReceiver.name = selectedReceiver.name[..colonPos]; + } + } + + private Nucleus SelectReceptor(int thingId, float3 inputValue) { + // No existing nucleus for this thing + float inputMagnitude = length(inputValue); + Nucleus selectedReceiver = null; + float selectedMagnitude = 0; + foreach (Nucleus receiver in this._receptors) { + if (thingReceivers.ContainsValue(receiver) == false) { + // We found an unusued receiver + Debug.Log($"{thingId} -> [{receiver.name}]"); thingReceivers.Add(thingId, receiver); + return receiver; + } + else if (receiver.isSleeping) { + // A sleeping receiver is not active and can therefore always be used + thingReceivers.Add(thingId, receiver); + Debug.Log($"{thingId} -> [{selectedReceiver.name}]"); + return receiver; + } + else if (selectedReceiver == null) { + // If we haven't found a receiver yet, just start by taking the first selectedReceiver = receiver; + selectedMagnitude = length(selectedReceiver.outputValue); + } + // Look for the receiver with the lowest magnitude + else { + float magnitude = length(receiver.outputValue); + + if (magnitude < inputMagnitude && length(receiver.outputValue) < selectedMagnitude) { + selectedReceiver = receiver; + selectedMagnitude = length(selectedReceiver.outputValue); + } } } - //selectedReceiver.outputValue = newLocalPositionVector; - - - - // int receiverIx = 0; - // INucleus selectedReceiver = null; - // int selectedReceiverIx = 0; - // foreach (INucleus receiver in this.receivers) { - // if (thingIds[receiverIx] == thingId) { - // // We found an existing receiver for this thing - // selectedReceiver = receiver; - // selectedReceiverIx = receiverIx; - // // Do not look any further - // break; - // } - // else if (receiver.isSleeping) { - // // A sleeping receiver is not active and can therefore always be used - // selectedReceiver = receiver; - // selectedReceiverIx = receiverIx; - // // Look further because we may find an existing receiver for this thing - // } - // else if (selectedReceiver == null) { - // // If we haven't found a receiver yet, just start by taking the first - // selectedReceiver = receiver; - // selectedReceiverIx = receiverIx; - // } - // else if (selectedReceiver.isSleeping == false) { - // // If no existing or sleeping receiver is found, we look for - // // the receiver with the furthest/least interesting stimulus - // if (length(receiver.outputValue) < length(selectedReceiver.outputValue)) { - // // Debug.Log($"{selectedReceiver.name}[{selectedReceiverIx}] {length(selectedReceiver.outputValue)}" + - // // $" {receiver.name}[{receiverIx}] {length(receiver.outputValue)} "); - // selectedReceiver = receiver; - // selectedReceiverIx = receiverIx; - // } - // } - // receiverIx++; - // } - // if (selectedReceiverIx >= thingIds.Length) - // return; - - // thingIds[selectedReceiverIx] = thingId; - // if (thingName != null) { - // string baseName = selectedReceiver.name; - // int colonPos = selectedReceiver.name.IndexOf(":"); - // if (colonPos > 0) - // baseName = selectedReceiver.name.Substring(0, colonPos); - // selectedReceiver.name = baseName + ": " + thingName; - // } - // Debug.Log($"Receiver {selectedReceiver.name}[{selectedReceiverIx}] for thing {thingId}"); - // selectedReceiver.parent.UpdateStateIsolated(); - + if (selectedReceiver != null) { + // Replace the receiver + // Find the thingId current associated with the receiver + int keyToRemove = thingReceivers.FirstOrDefault(r => r.Value.Equals(selectedReceiver)).Key; + if (keyToRemove != 0 || thingReceivers.ContainsKey(keyToRemove)) + thingReceivers.Remove(keyToRemove); + // And add the new association + thingReceivers.Add(thingId, selectedReceiver); + } + return selectedReceiver; } -} -*/ \ No newline at end of file + public override void UpdateStateIsolated() { + float3 sum = this.bias; + + // Receptors do not have inputs, so we ignore the synapses + foreach (Nucleus nucleus in this._receptors) + sum += nucleus.outputValue; + + this.outputValue = sum / _receptors.Length; + this.stale = 0; + + UpdateNuclei(); + } + + public override void UpdateNuclei() { + foreach (Nucleus nucleus in this.receptors) { + nucleus.stale++; + if (nucleus.stale > staleValueForSleep && lengthsq(nucleus.outputValue) > 0) { + nucleus.outputValue = Vector3.zero; + this.UpdateStateIsolated(); + } + } + } +} \ No newline at end of file