Cluster no longer has output

This commit is contained in:
Pascal Serrarens 2026-03-04 14:20:23 +01:00
parent 172d7ca8e8
commit 1dfe65eefa
8 changed files with 325 additions and 217 deletions

View File

@ -104,7 +104,7 @@ public class Cluster : Nucleus {
foreach (Nucleus prefabArrayNucleus in prefabReceptor.nucleiArray) { foreach (Nucleus prefabArrayNucleus in prefabReceptor.nucleiArray) {
int arrayNucleusIx = GetNucleusIndex(prefabNuclei, prefabArrayNucleus); int arrayNucleusIx = GetNucleusIndex(prefabNuclei, prefabArrayNucleus);
if (arrayNucleusIx >= 0) { if (arrayNucleusIx >= 0) {
Nucleus clonedArrayNucleus = clonedNuclei[arrayNucleusIx]; Neuron clonedArrayNucleus = clonedNuclei[arrayNucleusIx] as Neuron;
clonedArray.nuclei[arrayIx] = clonedArrayNucleus; clonedArray.nuclei[arrayIx] = clonedArrayNucleus;
} }
else { else {
@ -473,12 +473,12 @@ public class Cluster : Nucleus {
Debug.Log($"Update from {startNucleus.name}"); Debug.Log($"Update from {startNucleus.name}");
foreach (Nucleus nucleus in computeOrder) { foreach (Nucleus nucleus in computeOrder) {
nucleus.UpdateStateIsolated(); nucleus.UpdateStateIsolated();
if (startNucleus.trace) if (startNucleus.trace && nucleus is Neuron neuron)
Debug.Log($" {nucleus.name}[{nucleus.GetHashCode()}] = {nucleus.outputValue}"); Debug.Log($" {nucleus.name}[{nucleus.GetHashCode()}] = {neuron.outputValue}");
} }
this.outputValue = this.defaultOutput.outputValue; // this.outputValue = this.defaultOutput.outputValue;
this.stale = 0; // this.stale = 0;
// continue in parent // continue in parent
this.parent?.UpdateFromNucleus(this); this.parent?.UpdateFromNucleus(this);
@ -491,17 +491,19 @@ public class Cluster : Nucleus {
//Applying the weight factors //Applying the weight factors
foreach (Synapse synapse in this.synapses) { foreach (Synapse synapse in this.synapses) {
if (lengthsq(synapse.nucleus.outputValue) > 0) { if (synapse.nucleus is Neuron neuron) {
sum += synapse.weight * synapse.nucleus.outputValue; if (lengthsq(neuron.outputValue) > 0) {
this.stale = 0; sum += synapse.weight * neuron.outputValue;
//this.stale = 0;
}
} }
} }
foreach (Nucleus nucleus in this.sortedNuclei) foreach (Nucleus nucleus in this.sortedNuclei)
nucleus.UpdateStateIsolated(); nucleus.UpdateStateIsolated();
this.outputValue = this.defaultOutput.outputValue; // this.outputValue = this.defaultOutput.outputValue;
this.stale = 0; // this.stale = 0;
UpdateNuclei(); UpdateNuclei();
} }

View File

@ -100,6 +100,8 @@ public class ClusterReceptor : Cluster, IReceptor {
set { _array.nuclei = value; } set { _array.nuclei = value; }
} }
//public ClusterReceptor[] nucleiArray;
public void AddReceptorElement(ClusterPrefab prefab) { public void AddReceptorElement(ClusterPrefab prefab) {
IReceptorHelpers.AddReceptorElement(this, prefab); IReceptorHelpers.AddReceptorElement(this, prefab);
} }
@ -129,30 +131,31 @@ public class ClusterReceptor : Cluster, IReceptor {
} }
public override void UpdateNuclei() { public override void UpdateNuclei() {
this.stale++; // this.stale++;
if (this.stale > staleValueForSleep && lengthsq(this.bias) > 0) { // if (this.stale > staleValueForSleep && lengthsq(this.bias) > 0) {
this.bias = new float3(0, 0, 0); // this.bias = new float3(0, 0, 0);
this.parent.UpdateFromNucleus(this); // this.parent.UpdateFromNucleus(this);
} // }
foreach (Nucleus nucleus in this.clusterNuclei) foreach (Nucleus nucleus in this.clusterNuclei)
nucleus.UpdateNuclei(); nucleus.UpdateNuclei();
} }
public override void ProcessStimulus(Vector3 inputValue, int thingId = 0, string thingName = null) { public override void ProcessStimulus(Vector3 inputValue, int thingId = 0, string thingName = null) {
this._array ??= new NucleusArray(this.parent); //this._array ??= new NucleusArray(this.parent);
this._array.ProcessStimulus(thingId, inputValue, thingName); //this._array.ProcessStimulus(thingId, inputValue, thingName);
Debug.LogError("Process Stimulus was called on clusterreceptor without a neuron specified");
} }
private Dictionary<int, ClusterReceptor> thingReceivers = new(); private readonly Dictionary<int, ClusterReceptor> thingReceivers = new();
public virtual void ProcessStimulus(Neuron input, Vector3 inputValue, int thingId = 0, string thingName = null) { public virtual void ProcessStimulus(Neuron input, Vector3 inputValue, int thingId = 0, string thingName = null) {
CleanupReceivers(); CleanupReceivers();
inputValue = input.Activator(inputValue); //inputValue = input.Activator(inputValue);
if (!thingReceivers.TryGetValue(thingId, out ClusterReceptor selectedReceiver)) if (!thingReceivers.TryGetValue(thingId, out ClusterReceptor selectedReceiver))
selectedReceiver = FindReceiver(thingId, inputValue); selectedReceiver = FindReceiver2(thingId, inputValue);
if (selectedReceiver == null) if (selectedReceiver == null)
return; return;
@ -172,18 +175,61 @@ public class ClusterReceptor : Cluster, IReceptor {
selectedNeuron.ProcessStimulusDirect(inputValue); selectedNeuron.ProcessStimulusDirect(inputValue);
} }
private ClusterReceptor FindReceiver(int thingId, float3 inputValue) { // private ClusterReceptor FindReceiver(int thingId, float3 inputValue) {
// // No existing nucleus for this thing
// float inputMagnitude = length(inputValue);
// ClusterReceptor selectedReceiver = null;
// float selectedMagnitude = 0;
// foreach (ClusterReceptor receiver in nucleiArray.Cast<ClusterReceptor>()) {
// if (thingReceivers.ContainsValue(receiver) == false) {
// // We found an unusued receiver
// 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);
// 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);
// }
// }
// }
// 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;
// }
private ClusterReceptor FindReceiver2(int thingId, float3 inputValue) {
// No existing nucleus for this thing // No existing nucleus for this thing
float inputMagnitude = length(inputValue); //float inputMagnitude = length(inputValue);
ClusterReceptor selectedReceiver = null; ClusterReceptor selectedReceiver = null;
float selectedMagnitude = 0; float selectedMagnitude = 0;
foreach (ClusterReceptor receiver in nucleiArray.Cast<ClusterReceptor>()) { foreach (ClusterReceptor receiver in this.nucleiArray.Cast<ClusterReceptor>()) {
if (thingReceivers.ContainsValue(receiver) == false) { if (thingReceivers.ContainsValue(receiver) == false) {
// We found an unusued receiver // We found an unusued receiver
thingReceivers.Add(thingId, receiver); thingReceivers.Add(thingId, receiver);
return receiver; return receiver;
} }
else if (receiver.isSleeping) { else if (receiver.defaultOutput.isSleeping) {
// A sleeping receiver is not active and can therefore always be used // A sleeping receiver is not active and can therefore always be used
thingReceivers.Add(thingId, receiver); thingReceivers.Add(thingId, receiver);
return receiver; return receiver;
@ -191,15 +237,15 @@ public class ClusterReceptor : Cluster, IReceptor {
else if (selectedReceiver == null) { else if (selectedReceiver == null) {
// If we haven't found a receiver yet, just start by taking the first // If we haven't found a receiver yet, just start by taking the first
selectedReceiver = receiver; selectedReceiver = receiver;
selectedMagnitude = length(selectedReceiver.outputValue); selectedMagnitude = length(selectedReceiver.defaultOutput.outputValue);
} }
// Look for the receiver with the lowest magnitude // Look for the receiver with the lowest output magnitude
else { else {
float magnitude = length(receiver.outputValue); float magnitude = length(receiver.defaultOutput.outputValue);
if (magnitude < inputMagnitude && length(receiver.outputValue) < selectedMagnitude) { if (length(receiver.defaultOutput.outputValue) < selectedMagnitude) {
selectedReceiver = receiver; selectedReceiver = receiver;
selectedMagnitude = length(selectedReceiver.outputValue); selectedMagnitude = length(selectedReceiver.defaultOutput.outputValue);
} }
} }
} }
@ -220,7 +266,7 @@ public class ClusterReceptor : Cluster, IReceptor {
// Remove a thing-receiver connection when the nucleus is inactive // Remove a thing-receiver connection when the nucleus is inactive
List<int> receiversToRemove = new(); List<int> receiversToRemove = new();
foreach (KeyValuePair<int, ClusterReceptor> item in thingReceivers) { foreach (KeyValuePair<int, ClusterReceptor> item in thingReceivers) {
if (item.Value != null && item.Value.isSleeping) if (item.Value != null && item.Value.defaultOutput.isSleeping)
receiversToRemove.Add(item.Key); receiversToRemove.Add(item.Key);
} }
foreach (int thingId in receiversToRemove) { foreach (int thingId in receiversToRemove) {

View File

@ -262,10 +262,12 @@ public class ClusterInspector : Editor {
if (this.currentNucleus is IReceptor receptor1) { if (this.currentNucleus is IReceptor receptor1) {
float maxValue = 0; float maxValue = 0;
foreach (Nucleus nucleus in receptor1.nucleiArray) { foreach (Nucleus nucleus in receptor1.nucleiArray) {
float value = length(nucleus.outputValue); if (nucleus is Neuron neuron) {
float value = length(neuron.outputValue);
if (value > maxValue) if (value > maxValue)
maxValue = value; maxValue = value;
} }
}
float spacing = 400f / receptor1.nucleiArray.Count(); float spacing = 400f / receptor1.nucleiArray.Count();
float margin = 10 + spacing / 2; float margin = 10 + spacing / 2;
@ -309,7 +311,13 @@ public class ClusterInspector : Editor {
Handles.color = Color.white; Handles.color = Color.white;
// The selected nucleus highlight ring // The selected nucleus highlight ring
Handles.DrawSolidDisc(position, Vector3.forward, size + 2); Handles.DrawSolidDisc(position, Vector3.forward, size + 2);
DrawNucleus(this.currentNucleus, position, length(this.currentNucleus.outputValue), 20); float maxValue = 1;
if (this.currentNucleus is Neuron neuron)
maxValue = length(neuron.outputValue);
else if (this.currentNucleus is Cluster cluster)
maxValue = length(cluster.defaultOutput.outputValue);
DrawNucleus(this.currentNucleus, position, maxValue, 20);
} }
} }
@ -317,7 +325,12 @@ public class ClusterInspector : Editor {
Handles.color = Color.white; Handles.color = Color.white;
// The selected nucleus highlight ring // The selected nucleus highlight ring
Handles.DrawSolidDisc(position, Vector3.forward, size + 2); Handles.DrawSolidDisc(position, Vector3.forward, size + 2);
DrawNucleus(this.currentNucleus, position, length(this.currentNucleus.outputValue), 20); float maxValue = 1;
if (this.currentNucleus is Neuron neuron)
maxValue = length(neuron.outputValue);
else if (this.currentNucleus is Cluster cluster)
maxValue = length(cluster.defaultOutput.outputValue);
DrawNucleus(this.currentNucleus, position, maxValue, 20);
} }
} }
@ -388,10 +401,12 @@ public class ClusterInspector : Editor {
continue; continue;
drawnArrays.Add(clusterReceptor.nucleiArray); drawnArrays.Add(clusterReceptor.nucleiArray);
} }
float value = length(synapse.nucleus.outputValue) * synapse.weight; if (synapse.nucleus is Neuron synapseNeuron) {
float value = length(synapseNeuron.outputValue) * synapse.weight;
// Debug.Log($"{synapse.nucleus.name}: {value} {length(synapse.nucleus.outputValue)} {synapse.weight}"); // Debug.Log($"{synapse.nucleus.name}: {value} {length(synapse.nucleus.outputValue)} {synapse.weight}");
if (value > maxValue) if (value > maxValue)
maxValue = value; maxValue = value;
}
neuronCount++; neuronCount++;
} }
@ -419,7 +434,9 @@ public class ClusterInspector : Editor {
if (Application.isPlaying) { if (Application.isPlaying) {
if (maxValue == 0 || !float.IsFinite(maxValue)) if (maxValue == 0 || !float.IsFinite(maxValue))
maxValue = 1; maxValue = 1;
float brightness = length(synapse.nucleus.outputValue * synapse.weight) / maxValue; float brightness = 0;
if (synapse.nucleus is Neuron synapseNeuron)
brightness = length(synapseNeuron.outputValue * synapse.weight) / maxValue;
color = new Color(brightness, brightness, brightness, 1f); color = new Color(brightness, brightness, brightness, 1f);
} }
if (synapse.nucleus.parent != null && synapse.nucleus.parent != this.currentNucleus.parent) { if (synapse.nucleus.parent != null && synapse.nucleus.parent != this.currentNucleus.parent) {
@ -439,7 +456,9 @@ public class ClusterInspector : Editor {
private void DrawNucleus(Nucleus nucleus, Vector3 position, float maxValue, float size) { private void DrawNucleus(Nucleus nucleus, Vector3 position, float maxValue, float size) {
Color color; Color color;
if (Application.isPlaying) { if (Application.isPlaying) {
float brightness = length(nucleus.outputValue) / maxValue; float brightness = 0;
if (nucleus is Neuron neuron)
brightness = length(neuron.outputValue) / maxValue;
color = new Color(brightness, brightness, brightness, 1f); color = new Color(brightness, brightness, brightness, 1f);
} }
else else
@ -529,9 +548,13 @@ public class ClusterInspector : Editor {
private void HandleMouseHover(Nucleus nucleus, Rect rect) { private void HandleMouseHover(Nucleus nucleus, Rect rect) {
GUIContent tooltip; GUIContent tooltip;
if (nucleus is Neuron neuron) {
tooltip = new( tooltip = new(
$"{nucleus.name}" + $"{nucleus.name}" +
$"\nValue: {length(nucleus.outputValue)}"); $"\nValue: {length(neuron.outputValue)}");
}
else
tooltip = new($"{nucleus.name}");
Vector2 mousePosition = Event.current.mousePosition; Vector2 mousePosition = Event.current.mousePosition;
@ -609,8 +632,12 @@ public class ClusterInspector : Editor {
} }
if (Application.isPlaying) { if (Application.isPlaying) {
GUIContent nameLabel = new("Output", this.currentNucleus.outputValue.ToString()); if (currentNucleus is Neuron currentNeuron1) {
EditorGUILayout.FloatField(nameLabel, length(this.currentNucleus.outputValue)); GUIContent nameLabel = new("Output", currentNeuron1.outputValue.ToString());
EditorGUILayout.FloatField(nameLabel, length(currentNeuron1.outputValue));
}
else
EditorGUILayout.LabelField(" ");
} }
else else
EditorGUILayout.LabelField(" "); EditorGUILayout.LabelField(" ");
@ -686,9 +713,11 @@ public class ClusterInspector : Editor {
EditorGUILayout.Space(); EditorGUILayout.Space();
if (Application.isPlaying) { if (Application.isPlaying) {
Vector3 value = synapse.nucleus.outputValue * synapse.weight; if (synapse.nucleus is Neuron synapseNeuron) {
GUIContent synapseValueLabel = new(synapse.nucleus.name, synapse.nucleus.outputValue.ToString()); Vector3 value = synapseNeuron.outputValue * synapse.weight;
EditorGUILayout.FloatField(synapseValueLabel, length(synapse.nucleus.outputValue)); GUIContent synapseValueLabel = new(synapse.nucleus.name, synapseNeuron.outputValue.ToString());
EditorGUILayout.FloatField(synapseValueLabel, length(synapseNeuron.outputValue));
}
} }
else { else {
EditorGUILayout.BeginHorizontal(); EditorGUILayout.BeginHorizontal();
@ -784,8 +813,8 @@ public class ClusterInspector : Editor {
EditorGUILayout.Space(); EditorGUILayout.Space();
breakOnWake = EditorGUILayout.Toggle("Break on wake", breakOnWake); breakOnWake = EditorGUILayout.Toggle("Break on wake", breakOnWake);
if (breakOnWake) { if (breakOnWake && this.currentNucleus is Neuron currentNeuron) {
if (this.currentNucleus.isSleeping == false) if (currentNeuron.isSleeping == false)
Debug.Break(); Debug.Break();
} }
trace = EditorGUILayout.Toggle("Trace", trace); trace = EditorGUILayout.Toggle("Trace", trace);
@ -802,18 +831,22 @@ public class ClusterInspector : Editor {
if (this.gameObject != null) { if (this.gameObject != null) {
if (this.currentNucleus is IReceptor receptor) { if (this.currentNucleus is IReceptor receptor) {
foreach (Nucleus nucleus in receptor.nucleiArray) { foreach (Nucleus nucleus in receptor.nucleiArray) {
Vector3 worldVector = this.gameObject.transform.TransformVector(nucleus.outputValue); if (nucleus is Neuron neuron) {
Vector3 worldVector = this.gameObject.transform.TransformVector(neuron.outputValue);
Handles.color = Color.yellow; Handles.color = Color.yellow;
Handles.DrawLine(this.gameObject.transform.position, this.gameObject.transform.position + worldVector); Handles.DrawLine(this.gameObject.transform.position, this.gameObject.transform.position + worldVector);
} }
} }
}
else { else {
Vector3 worldVector = this.gameObject.transform.TransformVector(this.currentNucleus.outputValue); if (this.currentNucleus is Neuron currentNeuron) {
Vector3 worldVector = this.gameObject.transform.TransformVector(currentNeuron.outputValue);
Handles.color = Color.yellow; Handles.color = Color.yellow;
Handles.DrawLine(this.gameObject.transform.position, this.gameObject.transform.position + worldVector); Handles.DrawLine(this.gameObject.transform.position, this.gameObject.transform.position + worldVector);
} }
} }
} }
}
#region Synapses #region Synapses
@ -825,15 +858,15 @@ public class ClusterInspector : Editor {
case Nucleus.Type.MemoryCell: case Nucleus.Type.MemoryCell:
AddMemoryCellInput(nucleus); AddMemoryCellInput(nucleus);
break; break;
case Nucleus.Type.Selector: // case Nucleus.Type.Selector:
AddSelectorInput(nucleus); // AddSelectorInput(nucleus);
break; // break;
case Nucleus.Type.Cluster: case Nucleus.Type.Cluster:
AddClusterInput(nucleus); AddClusterInput(nucleus);
break; break;
case Nucleus.Type.Pulsar: // case Nucleus.Type.Pulsar:
AddPulsarInput(nucleus); // AddPulsarInput(nucleus);
break; // break;
case Nucleus.Type.Receptor: case Nucleus.Type.Receptor:
AddReceptorInput(nucleus); AddReceptorInput(nucleus);
break; break;
@ -855,19 +888,19 @@ public class ClusterInspector : Editor {
BuildLayers(); BuildLayers();
} }
protected void AddSelectorInput(Nucleus nucleus) { // protected void AddSelectorInput(Nucleus nucleus) {
Selector newSelector = new(this.prefab, "New Selector"); // Selector newSelector = new(this.prefab, "New Selector");
newSelector.AddReceiver(nucleus); // newSelector.AddReceiver(nucleus);
this.currentNucleus = newSelector; // this.currentNucleus = newSelector;
BuildLayers(); // BuildLayers();
} // }
protected void AddPulsarInput(Nucleus nucleus) { // protected void AddPulsarInput(Nucleus nucleus) {
Pulsar newPulsar = new(this.prefab, "New Pulsar"); // Pulsar newPulsar = new(this.prefab, "New Pulsar");
newPulsar.AddReceiver(nucleus); // newPulsar.AddReceiver(nucleus);
this.currentNucleus = newPulsar; // this.currentNucleus = newPulsar;
BuildLayers(); // BuildLayers();
} // }
protected virtual void AddMemoryCellInput(Nucleus nucleus) { protected virtual void AddMemoryCellInput(Nucleus nucleus) {
MemoryCell newMemory = new(this.prefab, "New memory cell"); MemoryCell newMemory = new(this.prefab, "New memory cell");

View File

@ -117,6 +117,23 @@ public class Neuron : Nucleus {
#endregion Serialization #endregion Serialization
protected float3 _outputValue;
public virtual float3 outputValue {
get { return _outputValue; }
set {
_outputValue = value;
if (this.isFiring)
WhenFiring?.Invoke();
}
}
public bool isFiring => length(_outputValue) > 0.5f;
public Action WhenFiring;
public virtual bool isSleeping => lengthsq(this.outputValue) == 0;
[NonSerialized]
public int stale = 1000;
public readonly int staleValueForSleep = 20;
// this clone the nucleus without the synapses and receivers // this clone the nucleus without the synapses and receivers
public override Nucleus ShallowCloneTo(Cluster newParent) { public override Nucleus ShallowCloneTo(Cluster newParent) {
Neuron clone = new(newParent, this.name); Neuron clone = new(newParent, this.name);
@ -164,7 +181,8 @@ public class Neuron : Nucleus {
if (receiver != null && receiver.synapses != null) if (receiver != null && receiver.synapses != null)
receiver.synapses.RemoveAll(s => s.nucleus == nucleus); receiver.synapses.RemoveAll(s => s.nucleus == nucleus);
} }
} else if (nucleus is Cluster cluster) { }
else if (nucleus is Cluster cluster) {
// remove all receivers for this cluster // remove all receivers for this cluster
foreach (Neuron output in cluster.outputs) { foreach (Neuron output in cluster.outputs) {
foreach (Nucleus receiver in output.receivers) { foreach (Nucleus receiver in output.receivers) {
@ -197,15 +215,19 @@ public class Neuron : Nucleus {
public float3 CombinatorSum() { public float3 CombinatorSum() {
float3 sum = this.bias; float3 sum = this.bias;
foreach (Synapse synapse in this.synapses) foreach (Synapse synapse in this.synapses) {
sum += synapse.weight * synapse.nucleus.outputValue; if (synapse.nucleus is Neuron neuron)
sum += synapse.weight * neuron.outputValue;
}
return sum; return sum;
} }
public float3 CombinatorProduct() { public float3 CombinatorProduct() {
float3 product = this.bias; float3 product = this.bias;
foreach (Synapse synapse in this.synapses) foreach (Synapse synapse in this.synapses) {
product *= synapse.weight * synapse.nucleus.outputValue; if (synapse.nucleus is Neuron neuron)
product *= synapse.weight * neuron.outputValue;
}
return product; return product;
} }
@ -215,7 +237,8 @@ public class Neuron : Nucleus {
//Applying the weight factors //Applying the weight factors
foreach (Synapse synapse in this.synapses) { foreach (Synapse synapse in this.synapses) {
float3 input = synapse.weight * synapse.nucleus.outputValue; if (synapse.nucleus is Neuron neuron) {
float3 input = synapse.weight * neuron.outputValue;
float inputLength = length(input); float inputLength = length(input);
if (inputLength > maxLength) { if (inputLength > maxLength) {
@ -223,6 +246,7 @@ public class Neuron : Nucleus {
maxLength = inputLength; maxLength = inputLength;
} }
} }
}
return max; return max;
} }
@ -314,7 +338,8 @@ public class Neuron : Nucleus {
public override void ProcessStimulus(Vector3 inputValue, int thingId = 0, string thingName = null) { public override void ProcessStimulus(Vector3 inputValue, int thingId = 0, string thingName = null) {
if (this.parent is ClusterReceptor clusterReceptor) { if (this.parent is ClusterReceptor clusterReceptor) {
clusterReceptor.ProcessStimulus(this, inputValue, thingId, thingName); clusterReceptor.ProcessStimulus(this, inputValue, thingId, thingName);
} else }
else
ProcessStimulusDirect(inputValue, thingId, thingName); ProcessStimulusDirect(inputValue, thingId, thingName);
// this.stale = 0; // this.stale = 0;
// this.bias = inputValue; // this.bias = inputValue;

View File

@ -13,22 +13,22 @@ public abstract class Nucleus {
[SerializeReference] [SerializeReference]
public Cluster parent; public Cluster parent;
protected float3 _outputValue; // protected float3 _outputValue;
public virtual float3 outputValue { // public virtual float3 outputValue {
get { return _outputValue; } // get { return _outputValue; }
set { // set {
_outputValue = value; // _outputValue = value;
if (this.isFiring) // if (this.isFiring)
WhenFiring?.Invoke(); // WhenFiring?.Invoke();
} // }
} // }
public bool isFiring => length(_outputValue) > 0.5f; // public bool isFiring => length(_outputValue) > 0.5f;
public Action WhenFiring; // public Action WhenFiring;
public virtual bool isSleeping => lengthsq(this.outputValue) == 0; // public virtual bool isSleeping => lengthsq(this.outputValue) == 0;
[NonSerialized] // [NonSerialized]
public int stale = 1000; // public int stale = 1000;
public readonly int staleValueForSleep = 20; // public readonly int staleValueForSleep = 20;
public bool trace = false; public bool trace = false;
public abstract Nucleus ShallowCloneTo(Cluster parent); public abstract Nucleus ShallowCloneTo(Cluster parent);
@ -81,7 +81,7 @@ public abstract class Nucleus {
} }
public virtual void SetBias(Vector3 inputValue) { public virtual void SetBias(Vector3 inputValue) {
this.stale = 0; //this.stale = 0;
this.bias = inputValue; this.bias = inputValue;
this.parent.UpdateFromNucleus(this); this.parent.UpdateFromNucleus(this);
} }

View File

@ -69,9 +69,11 @@ public class NucleusArray {
private Nucleus FindReceiver(int thingId, float3 inputValue) { private Nucleus FindReceiver(int thingId, float3 inputValue) {
// No existing nucleus for this thing // No existing nucleus for this thing
float inputMagnitude = length(inputValue); float inputMagnitude = length(inputValue);
Nucleus selectedReceiver = null; Neuron selectedReceiver = null;
float selectedMagnitude = 0; float selectedMagnitude = 0;
foreach (Nucleus receiver in this._nuclei) { foreach (Nucleus nucleusReceiver in this._nuclei) {
if (nucleusReceiver is not Neuron receiver)
continue;
if (thingReceivers.ContainsValue(receiver) == false) { if (thingReceivers.ContainsValue(receiver) == false) {
// We found an unusued receiver // We found an unusued receiver
thingReceivers.Add(thingId, receiver); thingReceivers.Add(thingId, receiver);
@ -138,7 +140,7 @@ public class NucleusArray {
// Remove a thing-receiver connection when the nucleus is inactive // Remove a thing-receiver connection when the nucleus is inactive
List<int> receiversToRemove = new(); List<int> receiversToRemove = new();
foreach (KeyValuePair<int, Nucleus> item in thingReceivers) { foreach (KeyValuePair<int, Nucleus> item in thingReceivers) {
if (item.Value != null && item.Value.isSleeping) if (item.Value != null && item.Value is Neuron neuron && neuron.isSleeping)
receiversToRemove.Add(item.Key); receiversToRemove.Add(item.Key);
} }
foreach (int thingId in receiversToRemove) { foreach (int thingId in receiversToRemove) {

View File

@ -1,54 +1,54 @@
using System; // using System;
using Unity.Mathematics; // using Unity.Mathematics;
/// <summary> // /// <summary>
/// The Pulsar represents a type of neuron that operates based on // /// The Pulsar represents a type of neuron that operates based on
/// the product of its weighted inputs rather than the traditional summation. // /// the product of its weighted inputs rather than the traditional summation.
/// Drawing inspiration from the concept of pulsars in astrophysics // /// Drawing inspiration from the concept of pulsars in astrophysics
/// —highly magnetized rotating neutron stars that emit beams of radiation— // /// —highly magnetized rotating neutron stars that emit beams of radiation—
/// the Pulsar could symbolize dynamic, focused output based on the interaction of multiple factors. // /// the Pulsar could symbolize dynamic, focused output based on the interaction of multiple factors.
/// </summary> // /// </summary>
/// Multiplicative Functionality: // /// Multiplicative Functionality:
/// Instead of summing inputs, the Pulsar takes the weighted product of its inputs. // /// 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. // /// This means that all inputs must be active (non-zero) for the neuron to "pulse" or activate.
/// Output Behavior: // /// Output Behavior:
/// The output could amplify or diminish depending on the magnitude of the inputs. // /// The output could amplify or diminish depending on the magnitude of the inputs.
/// The product would be sensitive to small values, // /// The product would be sensitive to small values,
/// which means that even a small input could significantly lower the overall output if multiplied. // /// which means that even a small input could significantly lower the overall output if multiplied.
/// Activation Mechanism: // /// Activation Mechanism:
/// The activation function can further refine the output from the product result. // /// 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. // /// For instance, a certain threshold could be used to determine if a pulse occurs.
/// Modeling Complex Interactions: // /// Modeling Complex Interactions:
/// The Pulsar could be particularly beneficial for modeling situations where interactions multiply rather than add. // /// 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), // /// 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. // /// biology (e.g., interaction of hormones), and machine learning where multiplicative relationships exist.
[Serializable] // [Serializable]
public class Pulsar : Neuron { // public class Pulsar : Neuron {
public Pulsar(Cluster parent, string name) : base(parent, name) { // public Pulsar(Cluster parent, string name) : base(parent, name) {
// To prevent mistakes, bias one (instead of zero for standard neurons) // // To prevent mistakes, bias one (instead of zero for standard neurons)
this.bias = new float3(1, 1, 1); // this.bias = new float3(1, 1, 1);
} // }
public Pulsar(ClusterPrefab parent, string name) : base(parent, name) { // public Pulsar(ClusterPrefab parent, string name) : base(parent, name) {
// To prevent mistakes, bias one (instead of zero for standard neurons) // // To prevent mistakes, bias one (instead of zero for standard neurons)
this.bias = new float3(1, 1, 1); // this.bias = new float3(1, 1, 1);
} // }
public override Nucleus ShallowCloneTo(Cluster newParent) { // public override Nucleus ShallowCloneTo(Cluster newParent) {
Pulsar clone = new(newParent, this.name); // Pulsar clone = new(newParent, this.name);
CloneFields(clone); // CloneFields(clone);
return clone; // return clone;
} // }
public override void UpdateStateIsolated() { // public override void UpdateStateIsolated() {
float3 product = this.bias; // float3 product = this.bias;
//Applying the weight factors // //Applying the weight factors
foreach (Synapse synapse in this.synapses) { // foreach (Synapse synapse in this.synapses) {
float3 input = synapse.weight * synapse.nucleus.outputValue; // float3 input = synapse.weight * synapse.nucleus.outputValue;
product *= input; // product *= input;
} // }
// Activation function // // Activation function
this.outputValue = Activator(product); // this.outputValue = Activator(product);
} // }
} // }

View File

@ -1,62 +1,62 @@
using System; // using System;
using Unity.Mathematics; // using Unity.Mathematics;
using static Unity.Mathematics.math; // using static Unity.Mathematics.math;
[Serializable] // [Serializable]
public class Selector : Neuron { // public class Selector : Neuron {
public Selector(Cluster parent, string name) : base(parent, name) { } // public Selector(Cluster parent, string name) : base(parent, name) { }
public Selector(ClusterPrefab parent, string name) : base(parent, name) {} // public Selector(ClusterPrefab parent, string name) : base(parent, name) {}
public override Nucleus ShallowCloneTo(Cluster newParent) { // public override Nucleus ShallowCloneTo(Cluster newParent) {
Selector clone = new(newParent, this.name) { // Selector clone = new(newParent, this.name) {
// array = this.array, // // array = this.array,
curve = this.curve, // curve = this.curve,
curvePreset = this.curvePreset, // curvePreset = this.curvePreset,
curveMax = this.curveMax, // curveMax = this.curveMax,
}; // };
return clone; // return clone;
} // }
public override void UpdateStateIsolated() { //float3 bias) { // public override void UpdateStateIsolated() { //float3 bias) {
float3 max = this.bias; // float3 max = this.bias;
float maxSqrLength = lengthsq(max); // float maxSqrLength = lengthsq(max);
//Applying the weight factors // //Applying the weight factors
foreach (Synapse synapse in this.synapses) { // foreach (Synapse synapse in this.synapses) {
float3 input = synapse.weight * synapse.nucleus.outputValue; // float3 input = synapse.weight * synapse.nucleus.outputValue;
float inputSqrlength = lengthsq(input); // float inputSqrlength = lengthsq(input);
if (inputSqrlength > maxSqrLength) { // if (inputSqrlength > maxSqrLength) {
max = input; // max = input;
maxSqrLength = inputSqrlength; // maxSqrLength = inputSqrlength;
} // }
} // }
// Activation function // // Activation function
float3 result; // float3 result;
switch (this.curvePreset) { // switch (this.curvePreset) {
case CurvePresets.Linear: // case CurvePresets.Linear:
result = max; // result = max;
break; // break;
case CurvePresets.Sqrt: // case CurvePresets.Sqrt:
result = normalize(max) * System.MathF.Sqrt(length(max)); // result = normalize(max) * System.MathF.Sqrt(length(max));
break; // break;
case CurvePresets.Power: // case CurvePresets.Power:
result = normalize(max) * System.MathF.Pow(length(max), 2); // result = normalize(max) * System.MathF.Pow(length(max), 2);
break; // break;
case CurvePresets.Reciprocal: { // case CurvePresets.Reciprocal: {
float magnitude = length(max); // float magnitude = length(max);
if (magnitude > 0) // if (magnitude > 0)
result = normalize(max) * (1 / magnitude); // result = normalize(max) * (1 / magnitude);
else // else
result = float3(0, 0, 0); // result = float3(0, 0, 0);
break; // break;
} // }
default: // default:
float activatedValue = this.curve.Evaluate(length(max)); // float activatedValue = this.curve.Evaluate(length(max));
result = normalize(max) * activatedValue; // result = normalize(max) * activatedValue;
break; // break;
} // }
this.outputValue = result; // this.outputValue = result;
} // }
} // }