Nucleus+Neuroid->Neuron, improved Receptor support

This commit is contained in:
Pascal Serrarens 2026-01-13 16:33:16 +01:00
parent 5f6ec71c7b
commit 4f2f230335
15 changed files with 148 additions and 171 deletions

View File

@ -43,7 +43,7 @@
<UnityVersion>6000.3.2f1</UnityVersion>
</PropertyGroup>
<ItemGroup>
<Analyzer Include="/home/pascal/.vscode/extensions/visualstudiotoolsforunity.vstuc-1.2.0/Analyzers/Microsoft.Unity.Analyzers.dll" />
<Analyzer Include="/home/pascal/.vscode/extensions/visualstudiotoolsforunity.vstuc-1.2.1/Analyzers/Microsoft.Unity.Analyzers.dll" />
<Analyzer Include="/home/pascal/Unity/Hub/Editor/6000.3.2f1/Editor/Data/Tools/BuildPipeline/Unity.SourceGenerators/Unity.SourceGenerators.dll" />
<Analyzer Include="/home/pascal/Unity/Hub/Editor/6000.3.2f1/Editor/Data/Tools/BuildPipeline/Unity.SourceGenerators/Unity.Properties.SourceGenerator.dll" />
<Analyzer Include="/home/pascal/Unity/Hub/Editor/6000.3.2f1/Editor/Data/Tools/BuildPipeline/Unity.SourceGenerators/Unity.UIToolkit.SourceGenerator.dll" />

View File

@ -43,7 +43,7 @@
<UnityVersion>6000.3.2f1</UnityVersion>
</PropertyGroup>
<ItemGroup>
<Analyzer Include="/home/pascal/.vscode/extensions/visualstudiotoolsforunity.vstuc-1.2.0/Analyzers/Microsoft.Unity.Analyzers.dll" />
<Analyzer Include="/home/pascal/.vscode/extensions/visualstudiotoolsforunity.vstuc-1.2.1/Analyzers/Microsoft.Unity.Analyzers.dll" />
<Analyzer Include="/home/pascal/Unity/Hub/Editor/6000.3.2f1/Editor/Data/Tools/BuildPipeline/Unity.SourceGenerators/Unity.SourceGenerators.dll" />
<Analyzer Include="/home/pascal/Unity/Hub/Editor/6000.3.2f1/Editor/Data/Tools/BuildPipeline/Unity.SourceGenerators/Unity.Properties.SourceGenerator.dll" />
<Analyzer Include="/home/pascal/Unity/Hub/Editor/6000.3.2f1/Editor/Data/Tools/BuildPipeline/Unity.SourceGenerators/Unity.UIToolkit.SourceGenerator.dll" />
@ -67,6 +67,7 @@
<Compile Include="Assets/NanoBrain/LinearAlgebra/src/Matrix.cs" />
<Compile Include="Assets/NanoBrain/LinearAlgebra/src/Decomposition.cs" />
<Compile Include="Assets/NanoBrain/LinearAlgebra/src/Quat32.cs" />
<Compile Include="Assets/NanoBrain/Neuron.cs" />
<Compile Include="Assets/NanoBrain/VisualEditor/NanoBrainComponent.cs" />
<Compile Include="Assets/NanoBrain/LinearAlgebra/src/Vector2Int.cs" />
<Compile Include="Assets/Scenes/Boids/Scripts/SwarmControl.cs" />
@ -84,7 +85,6 @@
<Compile Include="Assets/NanoBrain/LinearAlgebra/src/Angle.cs" />
<Compile Include="Assets/NanoBrain/NucleusArray.cs" />
<Compile Include="Assets/NanoBrain/LinearAlgebra/src/Float.cs" />
<Compile Include="Assets/NanoBrain/Nucleus.cs" />
</ItemGroup>
<ItemGroup>
<None Include="Assets/NanoBrain/VisualEditor/Resources/GraphStyles.uss" />

View File

@ -9,14 +9,22 @@ public class Cluster : ScriptableObject, INucleus {
public Cluster cluster => this;
[SerializeReference]
public List<INucleus> nuclei = new();
public List<IReceptor> nuclei = new();
public INucleus output => this.nuclei[0];
public INucleus output => this.nuclei[0] as INucleus;
//private readonly List<INucleus> _inputs = new();
public List<INucleus> inputs { // = compare receptors in NanoBrain
// for now all nuclei are inputs
get { return this.nuclei; }
public List<INucleus> _inputs = null;
public List<INucleus> inputs {
get {
if (this._inputs == null) {
this._inputs = new();
foreach (IReceptor receptor in this.nuclei) {
if (receptor is INucleus nucleus)
this._inputs.Add(nucleus);
}
}
return this._inputs;
}
}
// The synapses of all inputs
@ -35,9 +43,9 @@ public class Cluster : ScriptableObject, INucleus {
// This is an invariant and should be ensured before the nucleus is used
// because output requires it.
public void EnsureInitialization() {
nuclei ??= new List<INucleus>();
nuclei ??= new List<IReceptor>();
if (nuclei.Count == 0)
new Neuroid(this, "Output"); // Every cluster should have at least 1 neuroid
new Neuron(this, "Output"); // Every cluster should have at least 1 neuron
}
public void AddReceiver(INucleus receiver) {
@ -61,7 +69,7 @@ public class Cluster : ScriptableObject, INucleus {
HashSet<INucleus> visitedNuclei = new();
MarkNuclei(visitedNuclei, this.output);
//Debug.Log($"Garbage collection found {visitedNuclei.Count} Nuclei");
this.nuclei.RemoveAll(nucleus => visitedNuclei.Contains(nucleus) == false);
this.nuclei.RemoveAll(nucleus => nucleus is INucleus n && visitedNuclei.Contains(n) == false);
//this.perceptei.RemoveAll(perceptoid => visitedNuclei.Contains(perceptoid) == false);
}
@ -105,7 +113,7 @@ public class Cluster : ScriptableObject, INucleus {
}
public void UpdateNuclei() {
foreach (INucleus nucleus in nuclei)
foreach (IReceptor nucleus in this.nuclei)
nucleus.UpdateNuclei();
}

View File

@ -23,7 +23,6 @@ public interface INucleus : IReceptor {
public void UpdateState();
public void UpdateNuclei();
#endregion dynamic state
@ -47,6 +46,7 @@ public interface IReceptor {
// float3 to prepare for SIMD
public float3 outputValue { get; }
public void UpdateNuclei();
public bool isSleeping { get; }
#endregion dynamic

View File

@ -1,9 +1,10 @@
/*
using UnityEngine;
using Unity.Mathematics;
using static Unity.Mathematics.math;
[System.Serializable]
public class Neuroid : Nucleus {
public class Neuroid : Neuron {
public bool average = false;
@ -78,3 +79,4 @@ public class Neuroid : Nucleus {
}
*/

View File

@ -5,7 +5,7 @@ using Unity.Mathematics;
using static Unity.Mathematics.math;
[Serializable]
public class Nucleus : INucleus {
public class Neuron : INucleus {
[SerializeField]
protected string _name;
@ -49,6 +49,7 @@ public class Nucleus : INucleus {
}
public AnimationCurve curve;
public float curveMax = 1.0f;
public bool average = false;
public AnimationCurve GenerateCurve() {
switch (this.curvePreset) {
@ -70,7 +71,7 @@ public class Nucleus : INucleus {
}
}
public virtual void Deserialize(Nucleus nucleus) { }
public virtual void Deserialize(Neuron nucleus) { }
#endregion Serialization
@ -103,17 +104,27 @@ public class Nucleus : INucleus {
#endregion Runtime state
public Nucleus(string name) {
public Neuron(Cluster brain, string name) : this(name) {
this.cluster = brain;
if (this.cluster != null) {
this.cluster.nuclei.Add(this);
}
else
Debug.LogError("No neuroid network");
}
public Neuron(string name) {
this._name = name;
}
public virtual INucleus Clone() {
Nucleus clone = new(this.name) {
Neuron clone = new(this.name) {
cluster = this.cluster,
array = this.array,
curve = this.curve,
curvePreset = this.curvePreset,
curveMax = this.curveMax
curveMax = this.curveMax,
average = this.average
};
if (clone.cluster != null)
clone.cluster.nuclei.Add(clone);
@ -140,14 +151,14 @@ public class Nucleus : INucleus {
public static void Delete(INucleus nucleus) {
foreach (Synapse synapse in nucleus.synapses) {
if (synapse.nucleus is Nucleus synapse_nucleus) {
if (synapse.nucleus is Neuron synapse_nucleus) {
if (synapse_nucleus.receivers.Count > 1) {
// there is another nucleus feeding into this input nucleus
synapse_nucleus.receivers.RemoveAll(r => r == nucleus);
}
else {
// No other links, delete it.
Nucleus.Delete(synapse_nucleus);
Neuron.Delete(synapse_nucleus);
}
}
}
@ -168,8 +179,41 @@ public class Nucleus : INucleus {
return synapse;
}
public virtual void UpdateState() { }
public virtual void UpdateState() {
float3 sum = new(0, 0, 0);
int n = 0;
//Applying the weight factgors
foreach (Synapse synapse in this.synapses) {
sum = sum + (synapse.weight * synapse.nucleus.outputValue);
if (lengthsq(synapse.nucleus.outputValue) != 0)
n++;
}
if (average)
sum /= n;
// Activation function
Vector3 result;
switch (this.curvePreset) {
case CurvePresets.Linear:
result = sum;
break;
case CurvePresets.Sqrt:
result = normalize(sum) * System.MathF.Sqrt(length(sum));
break;
case CurvePresets.Power:
result = normalize(sum) * System.MathF.Pow(length(sum), 2);
break;
case CurvePresets.Reciprocal:
result = normalize(sum) * (1 / length(sum));
break;
default:
float activatedValue = this.curve.Evaluate(length(sum));
result = normalize(sum) * activatedValue;
break;
}
UpdateResult(result);
}
public void UpdateResult(Vector3 result) {
// float d = Vector3.Distance(result, this.outputValue);
// if (d < 0.5f) {

View File

@ -38,7 +38,7 @@ public class NucleusArray {
for (int i = 0; i < newLength; i++)
newPerceptei[i] = this.nuclei[i];
// Delete the last perception
Nucleus.Delete(this.nuclei[newLength]);
Neuron.Delete(this.nuclei[newLength]);
this.nuclei = newPerceptei;
}

View File

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using Unity.Mathematics;
@ -24,15 +25,37 @@ public class Receptor : IReceptor {
receiverNucleus.synapses.RemoveAll(synapse => synapse.nucleus == this);
}
public bool isSleeping => false;
//public bool isSleeping => false;
private int stale = 1000;
public Vector3 localPosition;
private bool _isSleeping = false;
public bool isSleeping => _isSleeping;
public Vector3 localPosition {
set {
this.stale = 0;
this._isSleeping = false;
this._outputValue = value;
}
}
public float distanceResolution = 0.1f;
public float directionResolution = 5;
public float3 outputValue => this.localPosition;
//public float3 outputValue => this.localPosition;
private float3 _outputValue;
public float3 outputValue {
get { return this._outputValue; }
set {
this.stale = 0;
this._isSleeping = false;
this._outputValue = value;
}
}
public Receptor(Cluster cluster, INucleus nucleus) {
if (cluster != null)
cluster.nuclei.Add(this);
this.AddReceiver(nucleus);
}
@ -100,4 +123,11 @@ public class Receptor : IReceptor {
// selectedPerceptoid.name = selectedPerceptoid.baseName + " " + thingName;
// selectedPerceptoid.UpdateState();
}
public void UpdateNuclei() {
this.stale++;
this._isSleeping = this.stale > 2;
if (isSleeping)
this._outputValue = Vector3.zero;
}
}

View File

@ -228,7 +228,7 @@ public class ClusterInspector : Editor {
// This is used to 'scale' the output value colors of the nuclei
float maxValue = 0;
foreach (INucleus receiver in nucleus.receivers) {
if (receiver is Neuroid neuroid) {
if (receiver is Neuron neuroid) {
float value = length(neuroid.outputValue);
if (value > maxValue)
maxValue = value;
@ -263,29 +263,29 @@ public class ClusterInspector : Editor {
int neuronCount = 0;
List<NucleusArray> drawnArrays = new();
foreach (Synapse synapse in nucleus.synapses) {
if (synapse.nucleus is Neuroid neuroid) {
if (synapse.nucleus is Neuron neuroid) {
if (drawnArrays.Contains(neuroid.array))
continue;
drawnArrays.Add(neuroid.array);
neuronCount++;
float value = length(neuroid.outputValue);
if (value > maxValue)
maxValue = value;
}
float value = length(synapse.nucleus.outputValue);
if (value > maxValue)
maxValue = value;
neuronCount++;
}
// Determine the spacing of the nuclei in the layer
float spacing = 400f / neuronCount; //nodeCount;
float spacing = 400f / neuronCount;
float margin = 10 + spacing / 2;
int row = 0;
drawnArrays = new();
foreach (Synapse synapse in nucleus.synapses) {
if (synapse.nucleus is Neuroid neuroid) {
if (drawnArrays.Contains(neuroid.array))
if (synapse.nucleus is Neuron neuron) {
if (drawnArrays.Contains(neuron.array))
continue;
drawnArrays.Add(neuroid.array);
drawnArrays.Add(neuron.array);
}
Vector3 pos = new(250, margin + row * spacing, 0.0f);
Handles.color = Color.white;
@ -318,12 +318,12 @@ public class ClusterInspector : Editor {
normal = { textColor = Color.white },
fontStyle = FontStyle.Bold,
};
if (nucleus is Nucleus perceptoid) {
if (perceptoid.array == null || perceptoid.array.nuclei == null || perceptoid.array.nuclei.Length == 0)
perceptoid.array = new NucleusArray(perceptoid);
if (nucleus is Neuron neuron) {
if (neuron.array == null || neuron.array.nuclei == null || neuron.array.nuclei.Length == 0)
neuron.array = new NucleusArray(neuron);
if (perceptoid.array.nuclei.Length > 1) {
Handles.Label(labelPosition, perceptoid.array.nuclei.Length.ToString(), style);
if (neuron.array.nuclei.Length > 1) {
Handles.Label(labelPosition, neuron.array.nuclei.Length.ToString(), style);
}
}
@ -396,14 +396,14 @@ public class ClusterInspector : Editor {
return;
this.currentNucleus.name = EditorGUILayout.TextField(this.currentNucleus.name);
if (this.currentNucleus is Nucleus neuroid) {
if (this.currentNucleus is Neuron neuroid) {
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("Activation Curve", GUILayout.Width(150));
if (neuroid.curveMax > 0)
EditorGUILayout.CurveField(neuroid.curve, Color.cyan, new Rect(0, 0, 1, neuroid.curveMax));
else
EditorGUILayout.CurveField(neuroid.curve, Color.cyan, new Rect(0, neuroid.curveMax, 1, -neuroid.curveMax));
neuroid.curvePreset = (Neuroid.CurvePresets)EditorGUILayout.EnumPopup(neuroid.curvePreset, GUILayout.Width(100));
neuroid.curvePreset = (Neuron.CurvePresets)EditorGUILayout.EnumPopup(neuroid.curvePreset, GUILayout.Width(100));
EditorGUILayout.EndHorizontal();
if (neuroid.array == null || neuroid.array.nuclei == null || neuroid.array.nuclei.Length == 0)
@ -423,6 +423,7 @@ public class ClusterInspector : Editor {
EditorGUILayout.LabelField(" ");
if (this.currentNucleus.synapses.Count > 0) {
EditorGUILayout.LabelField("Synapses");
Synapse[] synapses = this.currentNucleus.synapses.ToArray();
foreach (Synapse synapse in synapses) {
if (synapse.nucleus != null) {
@ -471,7 +472,7 @@ public class ClusterInspector : Editor {
}
protected virtual void AddInputNeuron(INucleus nucleus) {
Neuroid newNeuroid = new(this.cluster.cluster, "New neuron");
Neuron newNeuroid = new(this.cluster.cluster, "New neuron");
newNeuroid.AddReceiver(nucleus);
this.currentNucleus = newNeuroid;
BuildLayers();
@ -488,7 +489,7 @@ public class ClusterInspector : Editor {
break;
}
}
Nucleus.Delete(nucleus);
Neuron.Delete(nucleus);
BuildLayers();
}
@ -522,12 +523,12 @@ public class ClusterInspector : Editor {
// Nucleus n = this.currentNucleus.brain.nuclei[selectedIndex - perceptei.Count()];
// n.AddReceiver(this.currentNucleus);
// }
INucleus n = cluster.nuclei[selectedIndex];
IReceptor n = cluster.nuclei[selectedIndex];
n.AddReceiver(this.currentNucleus);
}
}
protected virtual void DisconnectNucleus(Nucleus nucleus) {
protected virtual void DisconnectNucleus(Neuron nucleus) {
if (this.currentNucleus.cluster == null)
return;
string[] names = this.currentNucleus.synapses.Select(synapse => synapse.nucleus.name).ToArray();

View File

@ -1,60 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 60a957541c24c57e78018c202ebb1d9b, type: 3}
m_Name: New Cluster
m_EditorClassIdentifier: Assembly-CSharp::Cluster
nuclei:
- rid: 2243601034565648587
references:
version: 2
RefIds:
- rid: 2243601034565648587
type: {class: Neuroid, ns: , asm: Assembly-CSharp}
data:
_name: Output
_synapses: []
_receivers: []
_array:
rid: 2243601034565648588
_curvePreset: 0
curve:
serializedVersion: 2
m_Curve:
- serializedVersion: 3
time: 0
value: 0
inSlope: 0
outSlope: 1
tangentMode: 0
weightedMode: 0
inWeight: 0
outWeight: 0
- serializedVersion: 3
time: 1000
value: 1000
inSlope: 1
outSlope: 0
tangentMode: 0
weightedMode: 0
inWeight: 0
outWeight: 0
m_PreInfinity: 2
m_PostInfinity: 2
m_RotationOrder: 4
curveMax: 1
average: 0
- rid: 2243601034565648588
type: {class: NucleusArray, ns: , asm: Assembly-CSharp}
data:
nuclei:
- rid: 2243601034565648587
name: Output

View File

@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 83e4ef8976534236989bcb1a9342dbf8
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@ -13,62 +13,18 @@ MonoBehaviour:
m_Name: NewSwarm
m_EditorClassIdentifier: Assembly-CSharp::Cluster
nuclei:
- rid: 2243601034565648442
- rid: 2243601034565648443
- rid: 2243601062909444155
references:
version: 2
RefIds:
- rid: 2243601034565648442
type: {class: Neuroid, ns: , asm: Assembly-CSharp}
- rid: 2243601062909444155
type: {class: Neuron, ns: , asm: Assembly-CSharp}
data:
id: 322343360
_name: Output
_synapses:
- nucleus:
rid: 2243601034565648443
cluster: {fileID: 0}
weight: 1
curveMax: 1
_receivers: []
nucleusType:
_curvePreset: 0
curve:
serializedVersion: 2
m_Curve:
- serializedVersion: 3
time: 0
value: 0
inSlope: 0
outSlope: 1
tangentMode: 0
weightedMode: 0
inWeight: 0
outWeight: 0
- serializedVersion: 3
time: 1000
value: 1000
inSlope: 1
outSlope: 0
tangentMode: 0
weightedMode: 0
inWeight: 0
outWeight: 0
m_PreInfinity: 2
m_PostInfinity: 2
m_RotationOrder: 4
curveMax: 1
average: 0
inverse: 0
exponent: 1
- rid: 2243601034565648443
type: {class: Neuroid, ns: , asm: Assembly-CSharp}
data:
id: -1924138416
_name: Avoidance
_synapses: []
_receivers:
- rid: 2243601034565648442
nucleusType:
_receivers: []
_array:
rid: 2243601062909444156
_curvePreset: 0
curve:
serializedVersion: 2
@ -96,5 +52,9 @@ MonoBehaviour:
m_RotationOrder: 4
curveMax: 1
average: 0
inverse: 0
exponent: 1
- rid: 2243601062909444156
type: {class: NucleusArray, ns: , asm: Assembly-CSharp}
data:
nuclei:
- rid: 2243601062909444155
name: Output

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: eddc759ede59e66cd936ad6ae2c55c46
guid: 83e4ef8976534236989bcb1a9342dbf8
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000

View File

@ -179,4 +179,4 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 92f34a5e4027a1dc39efd8ce63cf6aba, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::NanoBrainComponent
defaultBrain: {fileID: 11400000, guid: eddc759ede59e66cd936ad6ae2c55c46, type: 2}
defaultBrain: {fileID: 11400000, guid: 83e4ef8976534236989bcb1a9342dbf8, type: 2}