From 6f67def8e6305c6ed3b622e018754c14412e03ec Mon Sep 17 00:00:00 2001 From: Pascal Serrarens Date: Tue, 10 Feb 2026 11:47:49 +0100 Subject: [PATCH] Pulsar neuron and firing actions --- Editor/ClusterInspector.cs | 53 ++++++++++++++++++++++------ Neuron.cs | 72 ++++++++++++++++++++++++-------------- Nucleus.cs | 18 ++++++---- Pulsar.cs | 54 ++++++++++++++++++++++++++++ Pulsar.cs.meta | 2 ++ 5 files changed, 155 insertions(+), 44 deletions(-) create mode 100644 Pulsar.cs create mode 100644 Pulsar.cs.meta diff --git a/Editor/ClusterInspector.cs b/Editor/ClusterInspector.cs index 5f81101..c675e7b 100644 --- a/Editor/ClusterInspector.cs +++ b/Editor/ClusterInspector.cs @@ -678,24 +678,46 @@ public class ClusterInspector : Editor { } } + protected virtual void AddInput(Nucleus.Type selectedType, Nucleus nucleus) { + switch (selectedType) { + case Nucleus.Type.Neuron: + AddNeuronInput(nucleus); + break; + case Nucleus.Type.MemoryCell: + AddMemoryCellInput(nucleus); + break; + case Nucleus.Type.Selector: + AddSelectorInput(nucleus); + break; + case Nucleus.Type.Cluster: + AddClusterInput(nucleus); + break; + case Nucleus.Type.Pulsar: + AddPulsarInput(nucleus); + break; + default: + break; + } + } + protected virtual void AddInput(int selectedInputType, Nucleus nucleus) { switch (selectedInputType) { case 0: // Neuron - AddInputNeuron(nucleus); + AddNeuronInput(nucleus); break; case 1: // MemoryCell - AddInputMemoryCell(nucleus); + AddMemoryCellInput(nucleus); break; case 2: // Selector AddSelectorInput(nucleus); break; case 3: // Cluster - AddCluster(nucleus); + AddClusterInput(nucleus); break; } } - protected virtual void AddInputNeuron(Nucleus nucleus) { + protected virtual void AddNeuronInput(Nucleus nucleus) { //Neuron newNeuroid = new(this.cluster, "New neuron"); Neuron newNeuroid = new(this.prefab, "New neuron"); newNeuroid.AddReceiver(nucleus); @@ -725,14 +747,21 @@ public class ClusterInspector : Editor { BuildLayers(); } - protected virtual void AddInputMemoryCell(Nucleus nucleus) { + protected void AddPulsarInput(Nucleus nucleus) { + Pulsar newPulsar = new(this.prefab, "New Pulsar"); + newPulsar.AddReceiver(nucleus); + this.currentNucleus = newPulsar; + BuildLayers(); + } + + protected virtual void AddMemoryCellInput(Nucleus nucleus) { MemoryCell newMemory = new(this.prefab, "New memory cell"); newMemory.AddReceiver(nucleus); this.currentNucleus = newMemory; BuildLayers(); } - protected virtual void AddCluster(Nucleus nucleus) { + protected virtual void AddClusterInput(Nucleus nucleus) { ClusterPickerWindow.ShowPicker(brain => OnClusterPicked(nucleus, brain), "Select Cluster"); } @@ -777,11 +806,13 @@ public class ClusterInspector : Editor { if (cluster == null) return; - string[] options = { "Neuron", "MemoryCell", "Selector", "Cluster" }; - int selectedInputType = -1; - selectedInputType = EditorGUILayout.Popup("Add", selectedInputType, options); - if (selectedInputType >= 0) - AddInput(selectedInputType, this.currentNucleus); + //string[] options = { "Neuron", "MemoryCell", "Selector", "Cluster" }; + //int selectedInputType = -1; + //selectedInputType = EditorGUILayout.Popup("Add", selectedInputType, options); + //if (selectedInputType >= 0) + Nucleus.Type selectedType = (Nucleus.Type)EditorGUILayout.EnumPopup("Add", Nucleus.Type.None); + if (selectedType != Nucleus.Type.None) + AddInput(selectedType, this.currentNucleus); } protected virtual void DisconnectNucleus(Neuron nucleus) { diff --git a/Neuron.cs b/Neuron.cs index c6c8221..bc6bccd 100644 --- a/Neuron.cs +++ b/Neuron.cs @@ -129,29 +129,33 @@ public class Neuron : Nucleus { // this clone the nucleus without the synapses and receivers public override Nucleus ShallowCloneTo(Cluster newParent) { - Neuron clone = new(newParent, this.name) { - array = null, - bias = this.bias, - curve = this.curve, - curvePreset = this.curvePreset, - curveMax = this.curveMax, - average = this.average - }; + Neuron clone = new(newParent, this.name); + // { + // array = null, + // bias = this.bias, + // curve = this.curve, + // curvePreset = this.curvePreset, + // curveMax = this.curveMax, + // average = this.average + // }; + CloneFields(clone); return clone; } public override Nucleus Clone(ClusterPrefab prefab) { - Neuron clone = new(prefab, this.name) { - //Neuron clone = new(this.parent, this.name) { - array = this.array, - curve = this.curve, - curvePreset = this.curvePreset, - curveMax = this.curveMax, - average = this.average - }; + Neuron clone = new(prefab, this.name); + // { + // //Neuron clone = new(this.parent, this.name) { + // array = this.array, + // curve = this.curve, + // curvePreset = this.curvePreset, + // curveMax = this.curveMax, + // average = this.average + // }; // if (clone.cluster != null) // clone.cluster.nuclei.Add(clone); + CloneFields(clone); foreach (Synapse synapse in this.synapses) { Synapse clonedSynapse = clone.AddSynapse(synapse.nucleus); clonedSynapse.weight = synapse.weight; @@ -162,6 +166,15 @@ public class Neuron : Nucleus { return clone; } + protected virtual void CloneFields(Neuron clone) { + clone.array = null; + clone.bias = this.bias; + clone.curve = this.curve; + clone.curvePreset = this.curvePreset; + clone.curveMax = this.curveMax; + clone.average = this.average; + } + public static void Delete(Nucleus nucleus) { foreach (Synapse synapse in nucleus.synapses) { if (synapse.nucleus is Neuron synapse_nucleus) { @@ -205,32 +218,37 @@ public class Neuron : Nucleus { sum /= n; // Activation function + float3 result = Activation(sum); + if (this.stale > staleValueForSleep) + this.outputValue = new float3(0, 0, 0); + else + this.outputValue = result; + } + + protected float3 Activation(float3 input) { float3 result = Vector3.zero; switch (this.curvePreset) { case CurvePresets.Linear: - result = sum; + result = input; break; case CurvePresets.Sqrt: - result = normalize(sum) * System.MathF.Sqrt(length(sum)); + result = normalize(input) * System.MathF.Sqrt(length(input)); break; case CurvePresets.Power: - result = normalize(sum) * System.MathF.Pow(length(sum), 2); + result = normalize(input) * System.MathF.Pow(length(input), 2); break; case CurvePresets.Reciprocal: { - float magnitude = length(sum); + float magnitude = length(input); if (magnitude > 0) - result = normalize(sum) * (1 / magnitude); + result = normalize(input) * (1 / magnitude); break; } default: - float activatedValue = this.curve.Evaluate(length(sum)); - result = normalize(sum) * activatedValue; + float activatedValue = this.curve.Evaluate(length(input)); + result = normalize(input) * activatedValue; break; } - if (this.stale > staleValueForSleep) - this.outputValue = new float3(0,0,0); - else - this.outputValue = result; + return result; } public virtual void ProcessStimulus(Vector3 inputValue, string thingName = null) { diff --git a/Nucleus.cs b/Nucleus.cs index fe5eae4..a72b5dc 100644 --- a/Nucleus.cs +++ b/Nucleus.cs @@ -6,12 +6,6 @@ using static Unity.Mathematics.math; [Serializable] public abstract class Nucleus { - // [SerializeField] - // protected string _name; - // public virtual string name { - // get => _name; - // set => _name = value; - // } public string name; //[Obsolete] @@ -26,9 +20,12 @@ public abstract class Nucleus { //this.stale = 0; // this._isSleeping = false; _outputValue = value; + if (this.isFiring) + WhenFiring?.Invoke(); } } public bool isFiring => length(_outputValue) > 0.5f; + public Action WhenFiring; public bool isSleeping => lengthsq(this.outputValue) == 0; [NonSerialized] @@ -38,6 +35,15 @@ public abstract class Nucleus { public abstract Nucleus ShallowCloneTo(Cluster parent); public abstract Nucleus Clone(ClusterPrefab prefab); + public enum Type { + None, + Neuron, + MemoryCell, + Selector, + Cluster, + Pulsar + } + #region Synapses public Vector3 bias = Vector3.zero; diff --git a/Pulsar.cs b/Pulsar.cs new file mode 100644 index 0000000..889406c --- /dev/null +++ b/Pulsar.cs @@ -0,0 +1,54 @@ +using System; +using Unity.Mathematics; + +/// +/// The Pulsar represents a type of neuron that operates based on +/// the product of its weighted inputs rather than the traditional summation. +/// Drawing inspiration from the concept of pulsars in astrophysics +/// —highly magnetized rotating neutron stars that emit beams of radiation— +/// the Pulsar could symbolize dynamic, focused output based on the interaction of multiple factors. +/// +/// Multiplicative Functionality: +/// Instead of summing inputs, the Pulsar takes the weighted product of its inputs. +/// This means that all inputs must be active (non-zero) for the neuron to "pulse" or activate. +/// Output Behavior: +/// The output could amplify or diminish depending on the magnitude of the inputs. +/// The product would be sensitive to small values, +/// which means that even a small input could significantly lower the overall output if multiplied. +/// Activation Mechanism: +/// The activation function can further refine the output from the product result. +/// For instance, a certain threshold could be used to determine if a pulse occurs. +/// Modeling Complex Interactions: +/// The Pulsar could be particularly beneficial for modeling situations where interactions multiply rather than add. +/// This is useful in fields such as economics (e.g., compound growth models), +/// biology (e.g., interaction of hormones), and machine learning where multiplicative relationships exist. +[Serializable] +public class Pulsar : Neuron { + public Pulsar(Cluster parent, string name) : base(parent, name) { + // To prevent mistakes, bias one (instead of zero for standard neurons) + this.bias = new float3(1, 1, 1); + } + public Pulsar(ClusterPrefab parent, string name) : base(parent, name) { + // To prevent mistakes, bias one (instead of zero for standard neurons) + this.bias = new float3(1, 1, 1); + } + + public override Nucleus ShallowCloneTo(Cluster newParent) { + Pulsar clone = new(newParent, this.name); + CloneFields(clone); + return clone; + } + + public override void UpdateStateIsolated(float3 _bias) { + float3 product = this.bias; + + //Applying the weight factors + foreach (Synapse synapse in this.synapses) { + float3 input = synapse.weight * synapse.nucleus.outputValue; + product *= input; + } + + // Activation function + this.outputValue = Activation(product); + } +} \ No newline at end of file diff --git a/Pulsar.cs.meta b/Pulsar.cs.meta new file mode 100644 index 0000000..e73f7db --- /dev/null +++ b/Pulsar.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 46bd155173053a01585411c3e07f85d4 \ No newline at end of file