From 0258ef11979cf5e1650ac09cc834a4a571c387c3 Mon Sep 17 00:00:00 2001 From: Pascal Serrarens Date: Thu, 5 Feb 2026 16:38:42 +0100 Subject: [PATCH] receptorarray is working --- Cluster.cs | 40 +++++++++++------- Editor/ClusterInspector.cs | 17 ++++---- Neuron.cs | 25 ++++++++--- Nucleus.cs | 13 ++++-- NucleusArray.cs | 59 ++++++++++++++++++++++++-- Receptor.cs | 17 ++------ ReceptorArray.cs | 85 ++++++++++++++++++++++++++++++++++++++ ReceptorArray.cs.meta | 2 + 8 files changed, 209 insertions(+), 49 deletions(-) create mode 100644 ReceptorArray.cs create mode 100644 ReceptorArray.cs.meta diff --git a/Cluster.cs b/Cluster.cs index 82faa5d..a1bfca2 100644 --- a/Cluster.cs +++ b/Cluster.cs @@ -41,16 +41,6 @@ public class Cluster : INucleus { this.sortedNuclei = TopologicalSort(this.nuclei); } - // public Cluster(ClusterPrefab parent, ClusterPrefab realPrefab) { - // this.prefab = realPrefab; - // this.name = realPrefab.name; - // this.cluster = parent; - // if (this.cluster != null) - // this.cluster.nuclei.Add(this); - - // ClonePrefab(); - // } - private void ClonePrefab() { IReceptor[] nuclei = this.prefab.nuclei.ToArray(); // first clone the nuclei without their connections @@ -157,7 +147,8 @@ public class Cluster : INucleus { } public virtual IReceptor Clone() { - Neuron clone = new(this.cluster, this.name) { + //Neuron clone = new(this.cluster, this.name) { + Neuron clone = new(this.parent, this.name) { array = this.array, }; @@ -232,6 +223,18 @@ public class Cluster : INucleus { set { _array = value; } } + public bool TryGetNucleus(string nucleusName, out Nucleus foundNucleus) { + foreach (IReceptor receptor in this.nuclei) { + if (receptor is Nucleus nucleus) + if (nucleus.name == nucleusName) { + foundNucleus = nucleus; + return true; + } + } + foundNucleus = null; + return false; + } + #region Synapses [SerializeField] @@ -321,16 +324,23 @@ public class Cluster : INucleus { //Applying the weight factors foreach (Synapse synapse in this.synapses) { - sum += synapse.weight * synapse.nucleus.outputValue; + if (lengthsq(synapse.nucleus.outputValue) > 0) { + sum += synapse.weight * synapse.nucleus.outputValue; + this.stale = 0; + } } //this.inputs[0].UpdateState(sum); this.inputs[0].UpdateStateIsolated(sum); foreach (IReceptor receptor in this.sortedNuclei) { - if (receptor is INucleus nucleus && nucleus != this.inputs[0]) - nucleus.UpdateStateIsolated(); + if (receptor is INucleus nucleus && nucleus != this.inputs[0]) { + //if (nucleus.isSleeping == false) + nucleus.UpdateStateIsolated(); + } } this.outputValue = this.output.outputValue; + + UpdateNuclei(); } // public virtual void UpdateResult(Vector3 result) { @@ -347,7 +357,7 @@ public class Cluster : INucleus { public void UpdateNuclei() { this.stale++; - if (this.stale > 2) + if (this.stale > 5) _outputValue = Vector3.zero; //foreach (IReceptor nucleus in this.prefab.nuclei) diff --git a/Editor/ClusterInspector.cs b/Editor/ClusterInspector.cs index c805b8e..b642079 100644 --- a/Editor/ClusterInspector.cs +++ b/Editor/ClusterInspector.cs @@ -428,17 +428,17 @@ public class ClusterInspector : Editor { private void HandleMouseHover(IReceptor nucleus, Rect rect) { GUIContent tooltip; - if (nucleus is INucleus n) { - tooltip = new( - $"{nucleus.name}" + - $"\nsynapse count {n.synapses.Count}" + - $"\nValue: {length(nucleus.outputValue)}"); - } - else { + // if (nucleus is INucleus n) { + // tooltip = new( + // $"{nucleus.name}" + + // //$"\nsynapse count {n.synapses.Count}" + + // $"\nValue: {length(nucleus.outputValue)}"); + // } + // else { tooltip = new( $"{nucleus.name}" + $"\nValue: {length(nucleus.outputValue)}"); - } + // } Vector2 mousePosition = Event.current.mousePosition; @@ -616,6 +616,7 @@ public class ClusterInspector : Editor { } protected virtual void AddInputNeuron(INucleus nucleus) { + //Neuron newNeuroid = new(this.cluster, "New neuron"); Neuron newNeuroid = new(this.cluster, "New neuron"); newNeuroid.AddReceiver(nucleus); this.currentNucleus = newNeuroid; diff --git a/Neuron.cs b/Neuron.cs index 742817c..2d29e3f 100644 --- a/Neuron.cs +++ b/Neuron.cs @@ -193,7 +193,8 @@ public class Neuron : Nucleus, INucleus { } public override IReceptor Clone() { - Neuron clone = new(this.cluster, this.name) { + //Neuron clone = new(this.cluster, this.name) { + Neuron clone = new(this.parent, this.name) { array = this.array, curve = this.curve, curvePreset = this.curvePreset, @@ -299,8 +300,10 @@ public class Neuron : Nucleus, INucleus { // public virtual void UpdateStateIsolated() { // UpdateStateIsolated(new float3(0, 0, 0)); // } - public override void UpdateStateIsolated(float3 bias) { - float3 sum = bias; + + public float3 bias = float3(0, 0, 0); + public override void UpdateStateIsolated(float3 bias_unused) { + float3 sum = this.bias; int n = 0; //Applying the weight factgors @@ -308,8 +311,10 @@ public class Neuron : Nucleus, INucleus { sum += synapse.weight * synapse.nucleus.outputValue; // Perhaps synapses should be removed when the output value goes to 0.... - if (lengthsq(synapse.nucleus.outputValue) != 0) + if (lengthsq(synapse.nucleus.outputValue) != 0) { n++; + this.stale = 0; + } } if (this.average && n > 0) sum /= n; @@ -337,7 +342,17 @@ public class Neuron : Nucleus, INucleus { result = normalize(sum) * activatedValue; break; } - this.outputValue = result; + if (this.stale > 5) + this.outputValue = new float3(0,0,0); + else + this.outputValue = result; + } + + public virtual void ProcessStimulus(Vector3 inputValue, string thingName = null) { + //this.outputValue = inputValue; + this.stale = 0; + Debug.Log($"{this.name} processed stimulus"); + this.bias = inputValue; } // public virtual void UpdateResult(Vector3 result) { diff --git a/Nucleus.cs b/Nucleus.cs index f7ac9ac..1d71fbe 100644 --- a/Nucleus.cs +++ b/Nucleus.cs @@ -20,7 +20,8 @@ public abstract class Nucleus : IReceptor { public virtual float3 outputValue { get { return _outputValue; } set { - this.stale = 0; + //Debug.Log($"{this.name}: stale is reset, was: {this.stale}"); + //this.stale = 0; // this._isSleeping = false; _outputValue = value; } @@ -28,7 +29,7 @@ public abstract class Nucleus : IReceptor { public bool isSleeping => lengthsq(this.outputValue) == 0; [NonSerialized] - private int stale = 1000; + public int stale = 1000; // Cannot clone an abstract nucleus... public virtual IReceptor ShallowCloneTo(Cluster parent) { return null; } @@ -96,8 +97,14 @@ public abstract class Nucleus : IReceptor { public void UpdateNuclei() { this.stale++; - if (this.stale > 2) + if (this.stale > 5) { + //Debug.Log($"{this.name} goes to sleep, stale = {this.stale}"); _outputValue = Vector3.zero; + } + } + + public void ProcessStimulus(int thingId, Vector3 inputValue, string thingName = null) { + this.array.ProcessStimulus(thingId, inputValue, thingName); } #endregion Update diff --git a/NucleusArray.cs b/NucleusArray.cs index bcccf49..b16ad97 100644 --- a/NucleusArray.cs +++ b/NucleusArray.cs @@ -6,7 +6,6 @@ using UnityEngine; public class NucleusArray { [SerializeReference] private IReceptor[] _nuclei; - //private ClusterPrefab[] _clusters; public IReceptor[] nuclei { get { return _nuclei; @@ -18,13 +17,10 @@ public class NucleusArray { this.name = nucleus.name; this._nuclei = new INucleus[1]; this._nuclei[0] = nucleus; - //this._clusters = new ClusterPrefab[0]; } public NucleusArray(ClusterPrefab cluster) { this.name = cluster.name; this._nuclei = new INucleus[0]; - // this._clusters = new ClusterPrefab[1]; - // this._clusters[0] = cluster; } public NucleusArray(int size, string name) { this.name = name; @@ -64,5 +60,60 @@ public class NucleusArray { this._nuclei = newPerceptei; } + public Dictionary thingReceivers = new(); + + public virtual void ProcessStimulus(int thingId, Vector3 inputValue, string thingName = null) { + CleanupReceivers(); + if (!thingReceivers.TryGetValue(thingId, out INucleus selectedReceiver)) { + Debug.Log($"No receiver found for {thingId}"); + foreach (IReceptor receptor in this.nuclei) { + if (receptor is not INucleus receiver) + continue; + + if (thingReceivers.ContainsValue(receiver) == false) { + // receiver is not used yet + Debug.Log($"{thingId} -> {receiver.name}"); + thingReceivers.Add(thingId, receiver); + selectedReceiver = receiver; + break; + } + } + } + + 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) + selectedNucleus.ProcessStimulus(inputValue); + selectedReceiver.parent.UpdateStateIsolated(); + } + + 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) { + Nucleus n = item.Value as Nucleus; + Debug.Log($"{item.Value.name} is sleeping, stale = {n.stale}"); + receiversToRemove.Add(item.Key); + } + } + foreach (int thingId in receiversToRemove) { + INucleus selectedReceiver = thingReceivers[thingId]; + + thingReceivers.Remove(thingId); + Debug.Log($"Cleanup receiver for {thingId}"); + + int colonPos = selectedReceiver.name.IndexOf(":"); + if (colonPos > 0) + selectedReceiver.name = selectedReceiver.name[..colonPos]; + + } + } } \ No newline at end of file diff --git a/Receptor.cs b/Receptor.cs index ad6d526..16d2ded 100644 --- a/Receptor.cs +++ b/Receptor.cs @@ -6,7 +6,6 @@ using static Unity.Mathematics.math; public class Receptor : IReceptor { - [SerializeField] protected string _name; public virtual string name { @@ -16,13 +15,7 @@ public class Receptor : IReceptor { public Receptor(Cluster parent) { this.parent = parent; - if (parent != null) - parent.nuclei.Add(this); - } - public Receptor(ClusterPrefab cluster) { - this.cluster = cluster; - if (cluster != null) - cluster.nuclei.Add(this); + parent?.nuclei.Add(this); } public Receptor(Cluster parent, string name, string nucleusName) { @@ -37,8 +30,7 @@ public class Receptor : IReceptor { } } - private ClusterPrefab cluster; - private Cluster parent; + private readonly Cluster parent; public virtual IReceptor ShallowCloneTo(Cluster parent) { Receptor clone = new(parent); @@ -46,7 +38,7 @@ public class Receptor : IReceptor { } public virtual IReceptor Clone() { - Receptor clone = new(this.cluster); + Receptor clone = new(this.parent); foreach (INucleus receiver in this.receivers) { clone.AddReceiver(receiver); @@ -90,9 +82,6 @@ public class Receptor : IReceptor { private bool _isSleeping = false; public bool isSleeping => _isSleeping; - // public float distanceResolution = 0.1f; - // public float directionResolution = 5; - private float3 _outputValue; public float3 outputValue { get { return this._outputValue; } diff --git a/ReceptorArray.cs b/ReceptorArray.cs new file mode 100644 index 0000000..fed0b3a --- /dev/null +++ b/ReceptorArray.cs @@ -0,0 +1,85 @@ +using System.Collections.Generic; +using UnityEngine; + +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) { + this.parent = parent; + this.nuclei = nuclei; + } + + 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 + thingReceivers.Add(thingId, receiver); + selectedReceiver = receiver; + } + } + //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(); + */ + } + +} \ No newline at end of file diff --git a/ReceptorArray.cs.meta b/ReceptorArray.cs.meta new file mode 100644 index 0000000..4a0e23a --- /dev/null +++ b/ReceptorArray.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 9e915c8563642f23891b20522b3589cf \ No newline at end of file