Add memory cell

This commit is contained in:
Pascal Serrarens 2026-01-26 20:59:17 +01:00
parent f4559efe40
commit 8d1a4c3b72
7 changed files with 177 additions and 12 deletions

View File

@ -76,6 +76,7 @@
<Compile Include="Assets/NanoBrain/ClusterInstance.cs" />
<Compile Include="Assets/NanoBrain/LinearAlgebra/src/Spherical.cs" />
<Compile Include="Assets/NanoBrain/LinearAlgebra/test/Vector3FloatTest.cs" />
<Compile Include="Assets/NanoBrain/MemoryCell.cs" />
<Compile Include="Assets/NanoBrain/LinearAlgebra/test/QuaternionTest.cs" />
<Compile Include="Assets/Scenes/Boids/Scripts/Boid.cs" />
<Compile Include="Assets/NanoBrain/LinearAlgebra/src/SwingTwist.cs" />

View File

@ -0,0 +1,65 @@
using System;
using UnityEngine;
using Unity.Mathematics;
using static Unity.Mathematics.math;
[Serializable]
public class MemoryCell : Neuron {
public MemoryCell(Cluster cluster, string name) : base(cluster, name) {}
#region Parameters
// Returns the memorized value weighted by time
// return lastValue * (current time - last time)
[SerializeField]
public bool deltaValue = false;
#endregion Parameters
#region State
private float3 _memorizedValue;
private float _memorizedTime;
public override void UpdateState() {
// A memorycell does not have an activation function
float3 result = new(0, 0, 0);
int n = 0;
//Applying the weight factgors
foreach (Synapse synapse in this.synapses) {
if (synapse.nucleus == this) {
float deltaTime = Time.time - this.lastTime;
synapse.weight = deltaTime;
}
result += synapse.weight * synapse.nucleus.outputValue;
if (lengthsq(synapse.nucleus.outputValue) != 0)
n++;
}
if (this.average)
result /= n;
UpdateResult(result);
}
public override void UpdateResult(Vector3 result) {
// output value is the previous value
if (this.deltaValue) {
float deltaTime = Time.time - this._memorizedTime;
this._outputValue = this._memorizedValue * deltaTime;
}
else
this._outputValue = this._memorizedValue;
// Store the result for the next time
this._memorizedValue = result;
this._memorizedTime = Time.time;
foreach (INucleus receiver in this.receivers)
receiver.UpdateState();
}
#endregion State
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 29633aa3fe5cd9dcc8d886051f45d4d8

View File

@ -54,8 +54,13 @@ public class Neuron : INucleus {
}
public AnimationCurve curve;
public float curveMax = 1.0f;
#region Parameters
public bool average = false;
#endregion Parameters
public AnimationCurve GenerateCurve() {
switch (this.curvePreset) {
case CurvePresets.Linear:
@ -132,8 +137,8 @@ public class Neuron : INucleus {
#endregion Activation
private float3 _outputValue;
public float3 outputValue {
protected float3 _outputValue;
public virtual float3 outputValue {
get { return _outputValue; }
set {
this.stale = 0;
@ -147,6 +152,7 @@ public class Neuron : INucleus {
private bool _isSleeping = false;
public bool isSleeping => _isSleeping;
public float lastTime { get; private set; }
public void UpdateNuclei() {
this.stale++;
@ -238,11 +244,16 @@ public class Neuron : INucleus {
//Applying the weight factgors
foreach (Synapse synapse in this.synapses) {
if (synapse.nucleus == this) {
float deltaTime = Time.time - this.lastTime;
synapse.weight = deltaTime;
}
sum += synapse.weight * synapse.nucleus.outputValue;
// Perhaps synapses should be removed when the output value goes to 0....
if (lengthsq(synapse.nucleus.outputValue) != 0)
n++;
}
if (average)
if (this.average)
sum /= n;
// Activation function
@ -267,7 +278,8 @@ public class Neuron : INucleus {
}
UpdateResult(result);
}
public void UpdateResult(Vector3 result) {
public virtual void UpdateResult(Vector3 result) {
// float d = Vector3.Distance(result, this.outputValue);
// if (d < 0.5f) {
// //Debug.Log($"insignificant update: {d}");
@ -275,6 +287,7 @@ public class Neuron : INucleus {
// }
this.outputValue = result;
this.lastTime = Time.time;
foreach (INucleus receiver in this.receivers)
receiver.UpdateState();

View File

@ -0,0 +1,61 @@
%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: Velocity
m_EditorClassIdentifier: Assembly-CSharp::Cluster
asset: {fileID: 0}
nuclei:
- rid: 2243601362379866169
references:
version: 2
RefIds:
- rid: 2243601362379866169
type: {class: Neuron, ns: , asm: Assembly-CSharp}
data:
_name: Output
_synapses: []
_receivers: []
_array:
rid: 2243601362379866170
_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: 2243601362379866170
type: {class: NucleusArray, ns: , asm: Assembly-CSharp}
data:
_nuclei:
- rid: 2243601362379866169
name: Output

View File

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

View File

@ -488,14 +488,20 @@ public class ClusterInspector : Editor {
this.currentNucleus.name = EditorGUILayout.TextField(this.currentNucleus.name);
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 = (Neuron.CurvePresets)EditorGUILayout.EnumPopup(neuroid.curvePreset, GUILayout.Width(100));
EditorGUILayout.EndHorizontal();
if (this.currentNucleus is MemoryCell memory) {
// should use serializedProperty
memory.deltaValue = EditorGUILayout.Toggle("DeltaValue", memory.deltaValue);
}
else {
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 = (Neuron.CurvePresets)EditorGUILayout.EnumPopup(neuroid.curvePreset, GUILayout.Width(100));
EditorGUILayout.EndHorizontal();
}
if (neuroid.array == null || neuroid.array.nuclei == null || neuroid.array.nuclei.Count() == 0)
neuroid.array = new NucleusArray(neuroid);
@ -544,6 +550,8 @@ public class ClusterInspector : Editor {
ConnectNucleus(this.cluster, this.currentNucleus);
if (GUILayout.Button("Add Input Neuron"))
AddInputNeuron(this.currentNucleus);
if (GUILayout.Button("Add Input MemoryCell"))
AddInputMemoryCell(this.currentNucleus);
if (GUILayout.Button("Add Input Cluster"))
AddCluster(this.currentNucleus);
@ -598,6 +606,13 @@ public class ClusterInspector : Editor {
BuildLayers();
}
protected virtual void AddInputMemoryCell(INucleus nucleus) {
MemoryCell newMemory = new(this.cluster.cluster, "New memory cell");
newMemory.AddReceiver(nucleus);
this.currentNucleus = newMemory;
BuildLayers();
}
protected virtual void AddCluster(INucleus nucleus) {
BrainPickerWindow.ShowPicker(brain => OnClusterPicked(nucleus, brain), "Select Cluster");
}