diff --git a/.editorconfig b/.editorconfig
index def86c3..64e4e80 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -18,3 +18,6 @@ csharp_new_line_between_query_expression_clauses = true
# Limit the number of characters in a line
max_line_length = 100 # This setting does not enforce it; it's a guideline.
+
+[*.{cs,vb}]
+dotnet_diagnostic.IDE1006.severity = none
\ No newline at end of file
diff --git a/Assembly-CSharp.csproj b/Assembly-CSharp.csproj
index a3f3720..97530fd 100644
--- a/Assembly-CSharp.csproj
+++ b/Assembly-CSharp.csproj
@@ -52,6 +52,7 @@
+
diff --git a/Assets/NanoBrain/Editor/NeuroidWindow.cs b/Assets/NanoBrain/Editor/NeuroidWindow.cs
index 0548e29..3560973 100644
--- a/Assets/NanoBrain/Editor/NeuroidWindow.cs
+++ b/Assets/NanoBrain/Editor/NeuroidWindow.cs
@@ -43,8 +43,9 @@ public class GraphEditorWindow : EditorWindow {
}
// If this neuroid is not visited while its output neuroid is visited
- if (!neuronVisited.Contains(neuroid) && (neuroid.outputNeuroid == null ||
- (neuronVisited.Contains(neuroid.outputNeuroid) && neuroid.outputNeuroid.layerIx == layerIx - 1))) {
+ // Note: this does not yet work for multiple outputs yet (see the use of First())
+ if (!neuronVisited.Contains(neuroid) && (neuroid.outputNeuroids.Count == 0 ||
+ (neuronVisited.Contains(neuroid.outputNeuroids.First()) && neuroid.outputNeuroids.First().layerIx == layerIx - 1))) {
// Add it to the next layer
currentLayer.neuroids.Add(neuroid);
neuroid.layerIx = layerIx;
@@ -54,7 +55,7 @@ public class GraphEditorWindow : EditorWindow {
Vector2Int neuroidPosition = new(layerIx, neuroidIx);
neuroidPositions[neuroid] = neuroidPosition;
neuroidIx++;
- Debug.Log($"Layer {layerIx} neuron {neuroidIx} id {neuroid.id} {neuroid.name}");
+ Debug.Log($"Layer {layerIx} neuron {neuroidIx} name {neuroid.name}");
}
}
@@ -110,9 +111,9 @@ public class GraphEditorWindow : EditorWindow {
Vector3 parentPos = new(100 + layerNeuroidPos.x * 100, margin + layerNeuroidPos.y * spacing, 0.1f);
int i = 0;
- float inputSpacing = 200f / layerNeuroid.synapses.Count;
+ float inputSpacing = 200f / layerNeuroid.newSynapses.Count;
float inputMargin = 100 + inputSpacing / 2;
- foreach (Synapse synapse in layerNeuroid.synapses.Values) {
+ foreach (Synapse synapse in layerNeuroid.newSynapses.Values) {
if (synapse.neuroid != null) {
if (this.neuroidPositions.ContainsKey(synapse.neuroid)) {
@@ -144,7 +145,7 @@ public class GraphEditorWindow : EditorWindow {
// Draw the tooltip
GUIContent tooltip = new(
$"{neuroid.name}" +
- $"\nsynapse count {neuroid.synapses.Count}" +
+ $"\nsynapse count {neuroid.newSynapses.Count}" +
$"\nValue: {neuroid.outputValue}" +
$"\nStale: {neuroid.stale}");
diff --git a/Assets/NanoBrain/Neuroid.cs b/Assets/NanoBrain/Neuroid.cs
index a098aaa..a5d69d3 100644
--- a/Assets/NanoBrain/Neuroid.cs
+++ b/Assets/NanoBrain/Neuroid.cs
@@ -29,17 +29,16 @@ public class NeuroidNetwork {
}
public class Neuroid {
- public int id;
+ //public int id;
public string name;
public int layerIx;
public int stale = 0;
- public readonly Dictionary synapses = new();
+ public readonly Dictionary newSynapses = new();
public Vector3 outputValue;
- public Neuroid outputNeuroid;
- public int outputNeurix;
+ public HashSet outputNeuroids = new();
public enum Mode {
Sum,
@@ -52,69 +51,95 @@ public class Neuroid {
public Neuroid(NeuroidNetwork net) {
this.net = net;
- this.net.neuroids.Add(this);
+ if (this.net != null)
+ this.net.neuroids.Add(this);
}
- public void SetOutputTo(Neuroid neuroid) {
- this.outputNeuroid = neuroid;
- // neuroid.inputNeuroids.Add(this);
- this.outputNeurix = this.id;
+ public void AddSynapse(Neuroid input) {
+ input.AddReceiver(this);
+ this.newSynapses[input] = new(input, Vector3.zero, 1.0f);
+ }
+
+ public void AddReceiver(Neuroid receiver) {
+ this.outputNeuroids.Add(receiver);
}
public void ResetWeights() {
- foreach (Synapse synapse in synapses.Values)
+ foreach (Synapse synapse in this.newSynapses.Values)
synapse.weight = 1.0f;
}
public void SetWeight(Neuroid input, float weight) {
- if (synapses.ContainsKey(input.id))
- synapses[input.id] = new(input, synapses[input.id].value, weight);
- else
- synapses[input.id] = new(input, Vector3.zero, weight);
+ if (this.newSynapses.ContainsKey(input)) {
+ this.newSynapses[input].weight = weight;
+ }
+ else {
+ this.newSynapses[input] = new(input, Vector3.zero, weight);
+ }
}
public void GetInputFrom(Neuroid input, float weight = 1.0f) {
- input.id = this.synapses.Count;
- input.SetOutputTo(this);
- synapses[input.id] = new(input, Vector3.zero, weight);
+ input.AddReceiver(this);
+ this.newSynapses[input] = new(input, Vector3.zero, weight);
}
- public void SetInput(int inputId, Vector3 value) {
- if (synapses.ContainsKey(inputId))
- synapses[inputId].value = value;
+ public void SetInput(Neuroid input, Vector3 value) {
+ if (this.newSynapses.ContainsKey(input)) {
+ Synapse synapse = this.newSynapses[input];
+ synapse.value = value;
+ }
else
- synapses[inputId] = new(null, value, 1.0f);
+ this.newSynapses[input] = new(null, value, 1.0f);
UpdateState();
}
- public void SetInput(int inputIx, Vector3 value, float weight) {
- if (synapses.ContainsKey(inputIx)) {
- Synapse synapse = synapses[inputIx];
+
+ public void SetInput(Neuroid input, Vector3 value, float weight) {
+ if (this.newSynapses.ContainsKey(input)) {
+ Synapse synapse = this.newSynapses[input];
synapse.value = value;
synapse.weight = weight;
}
else
- synapses[inputIx] = new(null, value, weight);
+ this.newSynapses[input] = new(null, value, weight);
UpdateState();
}
- void UpdateState() {
+ public readonly Dictionary fakeNeuroids = new();
+ public void SetInput(int thingId, Vector3 value, float weight, NeuroidNetwork net) {
+ if (fakeNeuroids.ContainsKey(thingId)) {
+ Neuroid fakeInput = fakeNeuroids[thingId];
+ Synapse synapse = this.newSynapses[fakeInput];
+ synapse.value = value;
+ synapse.weight = weight;
+ }
+ else {
+ fakeNeuroids[thingId] = new(net);
+ this.newSynapses[fakeNeuroids[thingId]] = new (null, value, weight);
+ }
+ UpdateState();
+ }
+
+
+ protected virtual void UpdateState() {
Vector3 sum = Vector3.zero;
- foreach (Synapse synapse in synapses.Values)
+ foreach (Synapse synapse in this.newSynapses.Values)
sum += synapse.value * synapse.weight;
this.outputValue = Activation(sum);
- this.outputNeuroid?.SetInput(this.outputNeurix, this.outputValue);
+ foreach (Neuroid neuroid in outputNeuroids) {
+ neuroid?.SetInput(this, this.outputValue);
+ }
this.stale = 0;
}
Vector3 Activation(Vector3 sum) {
- if (synapses.Count == 0 && mode == Mode.Average)
- Debug.LogWarning($"{this.id} {this.name} has zero synapses for average");
+ if (this.newSynapses.Count == 0 && mode == Mode.Average)
+ Debug.LogWarning($"{this.name} has zero synapses for average");
if (float.IsNaN(sum.magnitude))
- Debug.LogWarning($"{this.id} {this.name} sum is nan");
+ Debug.LogWarning($"{this.name} sum is nan");
return mode switch {
Mode.Sum => sum,
- Mode.Average => sum / synapses.Count,
+ Mode.Average => sum / this.newSynapses.Count,
_ => sum,
};
//return sum; //(sum.magnitude > 0.5f) ? sum : Vector3.zero;
diff --git a/Assets/NanoBrain/Perception.cs b/Assets/NanoBrain/Perception.cs
new file mode 100644
index 0000000..bb822fe
--- /dev/null
+++ b/Assets/NanoBrain/Perception.cs
@@ -0,0 +1,83 @@
+using System.Collections.Generic;
+using UnityEngine;
+
+public class Perception {
+ public SensoryNeuroid[] sensoryNeuroids = new SensoryNeuroid[7];
+ //public Neuroid[] velocitySensors = new Neuroid[7];
+ public NeuroidNetwork neuroidNet { get; protected set; }
+
+ public HashSet receivers { get; protected set; }
+
+ public Perception(NeuroidNetwork neuroidNet) {
+ this.neuroidNet = neuroidNet;
+ this.receivers = new();
+ }
+
+ // public void SendOutputTo(Neuroid receiver) {
+ // foreach (SensoryNeuroid neuroid in sensoryNeuroids) {
+ // if (neuroid != null) {
+ // neuroid.AddReceiver(receiver);
+ // receiver.newSynapses[neuroid] = new (neuroid, Vector3.zero, 1.0f);
+ // }
+ // }
+ // }
+
+ public void SendPositions(Neuroid receiver) {
+ receivers.Add(receiver);
+ foreach (SensoryNeuroid neuroid in sensoryNeuroids) {
+ if (neuroid != null) {
+ neuroid.AddReceiver(receiver);
+ receiver.newSynapses[neuroid] = new (neuroid, Vector3.zero, 1.0f);
+ }
+ }
+ }
+ public void SendVelocities(Neuroid receiver) {
+ receivers.Add(receiver);
+ foreach (SensoryNeuroid neuroid in sensoryNeuroids) {
+ if (neuroid != null && neuroid.velocityNeuroid != null) {
+ neuroid.velocityNeuroid.AddReceiver(receiver);
+ receiver.newSynapses[neuroid] = new (neuroid, Vector3.zero, 1.0f);
+ }
+ }
+ }
+
+ public void ProcessStimulus(int thingId, Vector3 localPosition) {
+ int availableIx = -1;
+ SensoryNeuroid leastInterestingNeuroid = null;
+ for (int i = 0; i < sensoryNeuroids.Length; i++) {
+ if (sensoryNeuroids[i] == null || sensoryNeuroids[i].IsStale())
+ availableIx = i;
+ else if (sensoryNeuroids[i].receptor.thingId == thingId) {
+ sensoryNeuroids[i].receptor.position = localPosition;
+ return;
+ }
+ if (sensoryNeuroids[i] != null) {
+ if (leastInterestingNeuroid == null || leastInterestingNeuroid.receptor.position.magnitude > sensoryNeuroids[i].receptor.position.magnitude)
+ leastInterestingNeuroid = sensoryNeuroids[i];
+ }
+ }
+ if (availableIx != -1) {
+ if (sensoryNeuroids[availableIx] != null) {
+ // Debug.Log($"revived receptor {availableIx} for {thingId}");
+ sensoryNeuroids[availableIx].receptor.thingId = thingId;
+ sensoryNeuroids[availableIx].receptor.position = localPosition;
+ }
+ else {
+ // Debug.Log($"new receptor for {thingId}");
+ SensoryNeuroid neuroid = new(neuroidNet, thingId);
+ foreach (Neuroid receiver in receivers)
+ receiver.GetInputFrom(neuroid);
+
+ sensoryNeuroids[availableIx] = neuroid;
+ neuroid.receptor.position = localPosition;
+ }
+ }
+ else if (leastInterestingNeuroid != null) {
+ //Debug.Log($"replaced receptor {leastInterestingNeuroid.thingId} for {thingId}");
+ leastInterestingNeuroid.receptor.thingId = thingId;
+ leastInterestingNeuroid.receptor.position = localPosition;
+ }
+
+ //Debug.LogWarning($"No available receptor for {id}");
+ }
+}
\ No newline at end of file
diff --git a/Assets/NanoBrain/Perception.cs.meta b/Assets/NanoBrain/Perception.cs.meta
new file mode 100644
index 0000000..adfa3db
--- /dev/null
+++ b/Assets/NanoBrain/Perception.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 37d94d399d30e6eb996236adabad87ee
\ No newline at end of file
diff --git a/Assets/NanoBrain/SensoryNeuroid.cs b/Assets/NanoBrain/SensoryNeuroid.cs
index 7651d30..cf6bc38 100644
--- a/Assets/NanoBrain/SensoryNeuroid.cs
+++ b/Assets/NanoBrain/SensoryNeuroid.cs
@@ -1,32 +1,70 @@
+using System.Linq;
using UnityEngine;
-public class Receptor {
- public SensoryNeuroid neuroid;
- public void SetValue(Vector3 value) {
- if (neuroid != null) {
- neuroid.SetInput(neuroid.id, value);
- }
+public class SensoryNeuroid : Neuroid {
+ // A neuroid which has no neurons as input
+ // But receives value from a receptor
+ public Receptor receptor;
+ public VelocityNeuroid velocityNeuroid;
+
+ public SensoryNeuroid(NeuroidNetwork net, int thingId) : base(net) {
+ this.name = "sensory neuroid";
+ this.receptor = new Receptor {
+ neuroid = this,
+ thingId = thingId
+ };
+ this.velocityNeuroid = new(net);
+ // The velocity neuroid received position data from this
+ this.AddReceiver(velocityNeuroid);
}
- public Vector3 GetValue() {
- if (neuroid != null)
- return neuroid.synapses[neuroid.id].value;
- else
- return Vector3.zero;
+
+}
+
+public class Receptor {
+
+ public SensoryNeuroid neuroid;
+
+ public int thingId;
+ ///
+ /// Local position of the thing
+ ///
+ public virtual Vector3 position {
+ get {
+ if (neuroid != null)
+ return neuroid.newSynapses[neuroid].value;
+ else
+ return Vector3.zero;
+ }
+ set {
+ if (neuroid != null)
+ neuroid.SetInput(neuroid, value);
+ }
}
}
+public class VelocityNeuroid : Neuroid {
+ // Would be best if this was received through a synapse via a loop....
+ private Vector3 lastPosition = Vector3.zero;
+ private float lastValueTime = 0;
-public class SensoryNeuroid : Neuroid {
- public Receptor receptor;
- public int thingId;
-
- public SensoryNeuroid(NeuroidNetwork net, int id) : base(net) {
- this.name = "sensory neuroid";
- // this.id = id;
- this.thingId = id;
- this.receptor = new Receptor {
- neuroid = this
- };
+ public VelocityNeuroid(NeuroidNetwork net) : base(net) {
}
+ protected override void UpdateState() {
+ // Assuming only one synapse for now....
+ Vector3 currentPosition = this.newSynapses.First().Value.value;
+ float currentValueTime = Time.time;
+
+ float deltaTime = currentValueTime - lastValueTime;
+ Vector3 translation = currentPosition - lastPosition;
+ Vector3 velocity = translation / deltaTime;
+
+ // No activation function...
+ this.outputValue = velocity;
+ foreach (Neuroid receiver in outputNeuroids)
+ receiver?.SetInput(this, this.outputValue);
+ this.stale = 0;
+
+ this.lastValueTime = Time.time;
+ }
}
\ No newline at end of file
diff --git a/Assets/Scenes/Boids/Boids.unity b/Assets/Scenes/Boids/Boids.unity
index 5b7c526..970b54d 100644
--- a/Assets/Scenes/Boids/Boids.unity
+++ b/Assets/Scenes/Boids/Boids.unity
@@ -378,7 +378,7 @@ MonoBehaviour:
separationForce: 5
separationDistance: 0.3
bodyForce: 20
- perceptionDistance: 2
+ perceptionDistance: 1
boundaryForce: 5
spaceSize: {x: 10, y: 10, z: 10}
boundaryWidth: {x: 1, y: 1, z: 1}
@@ -394,7 +394,7 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: ec888ca5333d45a438f9f417fa5ce135, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::SwarmSpawn
- count: 1000
+ count: 5
boidPrefab: {fileID: 8702527964058765413, guid: f9c706268554ce449a8773675b2864b8, type: 3}
spawnAreaSize: {x: 0.5, y: 0.5, z: 0.5}
minDelay: 0.05
diff --git a/Assets/Scenes/Boids/Scripts/Boid.cs b/Assets/Scenes/Boids/Scripts/Boid.cs
index 9b7e00d..d0cd02e 100644
--- a/Assets/Scenes/Boids/Scripts/Boid.cs
+++ b/Assets/Scenes/Boids/Scripts/Boid.cs
@@ -19,7 +19,8 @@ public class Boid : MonoBehaviour {
readonly Collider[] results = new Collider[10];
- public SensoryNeuroid[] neighbourSensor = new SensoryNeuroid[6];
+ //public SensoryNeuroid[] neighbourSensor = new SensoryNeuroid[6];
+ public Perception perception;
public NeuroidNetwork neuroidNet = new();
public Neuroid bodyVector;
@@ -40,11 +41,14 @@ public class Boid : MonoBehaviour {
bounds = new(sc.transform.position, sc.spaceSize - 2 * sc.boundaryWidth);
+ perception = new Perception(neuroidNet);
//neighbourSensor = new(neuroidNet) { name = "Neighbour", id = 879 };
cohesion = new(neuroidNet) { name = "Cohesion", mode = Neuroid.Mode.Sum };
+ perception.SendPositions(cohesion);
//cohesion.GetInputFrom(neighbourSensor);
alignment = new(neuroidNet) { name = "Alignment", mode = Neuroid.Mode.Average };
+ //perception.SendVelocities(alignment);
separation = new(neuroidNet) { name = "Separation", mode = Neuroid.Mode.Sum };
target = new(neuroidNet) { name = "Target", mode = Neuroid.Mode.Sum };
boundary = new(neuroidNet) { name = "Boundary", mode = Neuroid.Mode.Sum };
@@ -78,15 +82,15 @@ public class Boid : MonoBehaviour {
Vector3 localPosition = neighbour.transform.position - this.transform.position;
Vector3 relativeVelocity = neighbour.velocity - this.velocity;
- int id = neighbour.GetInstanceID();
- ProcessStimulus(id, localPosition);
+ int thingId = neighbour.GetInstanceID();
+ perception.ProcessStimulus(thingId, localPosition);
Vector3 separationForce = -localPosition / localPosition.sqrMagnitude;
// which is equivalent to -(localPosition.normalized / localPosition.magnitude)
- separation.SetInput(id, separationForce, sc.separationDistance);
- //cohesion.SetInput(id, localPosition, sc.cohesionForce);
- alignment.SetInput(id, relativeVelocity, sc.alignmentForce);
+ separation.SetInput(thingId, separationForce, sc.separationDistance, neuroidNet);
+ //cohesion.SetInput(thingId, localPosition, sc.cohesionForce);
+ alignment.SetInput(thingId, relativeVelocity, sc.alignmentForce, neuroidNet);
neighbourCount++;
}
@@ -108,7 +112,7 @@ public class Boid : MonoBehaviour {
Vector3 direction = (sc.transform.position - this.transform.position).normalized;
outside = direction * magnitude;
- boundary.SetInput(id, outside, sc.boundaryForce);
+ boundary.SetInput(id, outside, sc.boundaryForce, neuroidNet);
// Debug.Log($"boundary {this.transform.position} {outside} force = {outside * sc.boundaryForce}");
}
@@ -128,7 +132,7 @@ public class Boid : MonoBehaviour {
//Debug.Log($"neighbours: {neighbourCount} synapses: {cohesion.synapses.Count}");
neuroidNet.Update();
}
-
+/*
Receptor GetReceptor(Neuroid perceptionNeuroid, int id) {
int availableIx = -1;
for (int i = 0; i < neighbourSensor.Length; i++) {
@@ -157,7 +161,7 @@ public class Boid : MonoBehaviour {
}
-
+/*
void ProcessStimulus(int thingId, Vector3 value) {
int availableIx = -1;
SensoryNeuroid leastInterestingNeuroid = null;
@@ -168,8 +172,6 @@ public class Boid : MonoBehaviour {
neighbourSensor[i].receptor.SetValue(value);
return;
}
- // if (leastInterestingIx == -1 || neighbourSensor[leastInterestingIx].receptor.GetValue().magnitude > neighbourSensor[i].receptor.GetValue().magnitude)
- // leastInterestingIx = i;
if (neighbourSensor[i] != null) {
if (leastInterestingNeuroid == null || leastInterestingNeuroid.receptor.GetValue().magnitude > neighbourSensor[i].receptor.GetValue().magnitude)
leastInterestingNeuroid = neighbourSensor[i];
@@ -197,7 +199,7 @@ public class Boid : MonoBehaviour {
//Debug.LogWarning($"No available receptor for {id}");
}
-
+*/
void OnDrawGizmosSelected() {
Gizmos.DrawWireSphere(transform.position, sc.perceptionDistance);
}