diff --git a/Assets/NanoBrain/Editor/NeuroidWindow.cs b/Assets/NanoBrain/Editor/NeuroidWindow.cs index 3560973..b0510df 100644 --- a/Assets/NanoBrain/Editor/NeuroidWindow.cs +++ b/Assets/NanoBrain/Editor/NeuroidWindow.cs @@ -37,15 +37,19 @@ public class GraphEditorWindow : EditorWindow { int neuroidIx = 0; foreach (Neuroid neuroid in neuroids) { + // Skip neurons we already processed + if (neuronVisited.Contains(neuroid)) + continue; + if (neuroid.IsStale()) { neuronVisited.Add(neuroid); continue; } - // If this neuroid is not visited while its output neuroid is visited + // If the output neuroid is visited // 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))) { + if (neuroid.outputNeuroids.Count == 0 // make sure the root neuroids are processed directly + || (neuronVisited.Contains(neuroid.outputNeuroids.First()) && neuroid.outputNeuroids.First().layerIx == layerIx - 1)) { // Add it to the next layer currentLayer.neuroids.Add(neuroid); neuroid.layerIx = layerIx; @@ -111,9 +115,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.newSynapses.Count; + float inputSpacing = 200f / layerNeuroid.synapses.Count; float inputMargin = 100 + inputSpacing / 2; - foreach (Synapse synapse in layerNeuroid.newSynapses.Values) { + foreach (Synapse synapse in layerNeuroid.synapses.Values) { if (synapse.neuroid != null) { if (this.neuroidPositions.ContainsKey(synapse.neuroid)) { @@ -145,7 +149,7 @@ public class GraphEditorWindow : EditorWindow { // Draw the tooltip GUIContent tooltip = new( $"{neuroid.name}" + - $"\nsynapse count {neuroid.newSynapses.Count}" + + $"\nsynapse count {neuroid.synapses.Count}" + $"\nValue: {neuroid.outputValue}" + $"\nStale: {neuroid.stale}"); diff --git a/Assets/NanoBrain/Neuroid.cs b/Assets/NanoBrain/Neuroid.cs index a5d69d3..09c5606 100644 --- a/Assets/NanoBrain/Neuroid.cs +++ b/Assets/NanoBrain/Neuroid.cs @@ -16,8 +16,8 @@ public class Synapse { public class NeuroidNetwork { public List neuroids = new(); - public Neuroid AddNeuron() { - Neuroid neuroid = new(this); + public Neuroid AddNeuron(string name) { + Neuroid neuroid = new(this, name); return neuroid; } @@ -29,35 +29,33 @@ public class NeuroidNetwork { } public class Neuroid { - //public int id; public string name; public int layerIx; public int stale = 0; - public readonly Dictionary newSynapses = new(); + public readonly Dictionary synapses = new(); public Vector3 outputValue; public HashSet outputNeuroids = new(); - public enum Mode { - Sum, - Average, - } - public Mode mode = Mode.Sum; - + public bool average = false; + //public bool quadratic = false; + public bool inverse = false; + public float exponent = 1.0f; public NeuroidNetwork net; - public Neuroid(NeuroidNetwork net) { + public Neuroid(NeuroidNetwork net, string name) { this.net = net; + this.name = name; if (this.net != null) this.net.neuroids.Add(this); } public void AddSynapse(Neuroid input) { input.AddReceiver(this); - this.newSynapses[input] = new(input, Vector3.zero, 1.0f); + this.synapses[input] = new(input, Vector3.zero, 1.0f); } public void AddReceiver(Neuroid receiver) { @@ -65,84 +63,77 @@ public class Neuroid { } public void ResetWeights() { - foreach (Synapse synapse in this.newSynapses.Values) + foreach (Synapse synapse in this.synapses.Values) synapse.weight = 1.0f; } public void SetWeight(Neuroid input, float weight) { - if (this.newSynapses.ContainsKey(input)) { - this.newSynapses[input].weight = weight; + if (this.synapses.ContainsKey(input)) { + this.synapses[input].weight = weight; } else { - this.newSynapses[input] = new(input, Vector3.zero, weight); + this.synapses[input] = new(input, Vector3.zero, weight); } } public void GetInputFrom(Neuroid input, float weight = 1.0f) { input.AddReceiver(this); - this.newSynapses[input] = new(input, Vector3.zero, weight); + this.synapses[input] = new(input, Vector3.zero, weight); } public void SetInput(Neuroid input, Vector3 value) { - if (this.newSynapses.ContainsKey(input)) { - Synapse synapse = this.newSynapses[input]; + if (this.synapses.ContainsKey(input)) { + Synapse synapse = this.synapses[input]; synapse.value = value; } else - this.newSynapses[input] = new(null, value, 1.0f); + this.synapses[input] = new(null, value, 1.0f); UpdateState(); } public void SetInput(Neuroid input, Vector3 value, float weight) { - if (this.newSynapses.ContainsKey(input)) { - Synapse synapse = this.newSynapses[input]; + if (this.synapses.ContainsKey(input)) { + Synapse synapse = this.synapses[input]; synapse.value = value; synapse.weight = weight; } else - this.newSynapses[input] = new(null, value, weight); - 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); - } + this.synapses[input] = new(null, value, weight); 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.synapses[fakeInput]; + // synapse.value = value; + // synapse.weight = weight; + // } + // else { + // fakeNeuroids[thingId] = new(net); + // this.synapses[fakeNeuroids[thingId]] = new(null, value, weight); + // } + // UpdateState(); + // } protected virtual void UpdateState() { - Vector3 sum = Vector3.zero; - foreach (Synapse synapse in this.newSynapses.Values) - sum += synapse.value * synapse.weight; - - this.outputValue = Activation(sum); - foreach (Neuroid neuroid in outputNeuroids) { - neuroid?.SetInput(this, this.outputValue); + Vector3 result = Vector3.zero; + foreach (Synapse synapse in this.synapses.Values) { + Vector3 direction = synapse.value.normalized; + float magnitude = synapse.value.magnitude; + magnitude = synapse.weight * Mathf.Pow(magnitude, exponent); + if (inverse) + magnitude = 1 / magnitude; + result += direction * magnitude; } - this.stale = 0; - } + if (average && this.synapses.Count > 0) + result /= this.synapses.Count; - Vector3 Activation(Vector3 sum) { - 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.name} sum is nan"); - return mode switch { - Mode.Sum => sum, - Mode.Average => sum / this.newSynapses.Count, - _ => sum, - }; - //return sum; //(sum.magnitude > 0.5f) ? sum : Vector3.zero; + this.outputValue = result; + foreach (Neuroid neuroid in outputNeuroids) + neuroid.SetInput(this, this.outputValue); + this.stale = 0; } public bool IsStale() { diff --git a/Assets/NanoBrain/Perception.cs b/Assets/NanoBrain/Perception.cs index bb822fe..99729c3 100644 --- a/Assets/NanoBrain/Perception.cs +++ b/Assets/NanoBrain/Perception.cs @@ -3,40 +3,33 @@ 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 HashSet positionReceivers { get; protected set; } + public HashSet velocityReceivers { get; protected set; } public Perception(NeuroidNetwork neuroidNet) { this.neuroidNet = neuroidNet; - this.receivers = new(); + this.positionReceivers = new(); + this.velocityReceivers = 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); + public void SendPositions(Neuroid receiver, float weight = 1.0f) { + positionReceivers.Add(receiver); foreach (SensoryNeuroid neuroid in sensoryNeuroids) { if (neuroid != null) { neuroid.AddReceiver(receiver); - receiver.newSynapses[neuroid] = new (neuroid, Vector3.zero, 1.0f); + receiver.synapses[neuroid] = new (neuroid, Vector3.zero, weight); } } } public void SendVelocities(Neuroid receiver) { - receivers.Add(receiver); + velocityReceivers.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); + receiver.synapses[neuroid] = new (neuroid, Vector3.zero, 1.0f); } } } @@ -65,8 +58,10 @@ public class Perception { else { // Debug.Log($"new receptor for {thingId}"); SensoryNeuroid neuroid = new(neuroidNet, thingId); - foreach (Neuroid receiver in receivers) + foreach (Neuroid receiver in positionReceivers) receiver.GetInputFrom(neuroid); + foreach (Neuroid receiver in velocityReceivers) + receiver.GetInputFrom(neuroid.velocityNeuroid); sensoryNeuroids[availableIx] = neuroid; neuroid.receptor.position = localPosition; diff --git a/Assets/NanoBrain/SensoryNeuroid.cs b/Assets/NanoBrain/SensoryNeuroid.cs index cf6bc38..50558ec 100644 --- a/Assets/NanoBrain/SensoryNeuroid.cs +++ b/Assets/NanoBrain/SensoryNeuroid.cs @@ -7,8 +7,7 @@ public class SensoryNeuroid : Neuroid { public Receptor receptor; public VelocityNeuroid velocityNeuroid; - public SensoryNeuroid(NeuroidNetwork net, int thingId) : base(net) { - this.name = "sensory neuroid"; + public SensoryNeuroid(NeuroidNetwork net, int thingId) : base(net, "sensory neuroid") { this.receptor = new Receptor { neuroid = this, thingId = thingId @@ -31,7 +30,7 @@ public class Receptor { public virtual Vector3 position { get { if (neuroid != null) - return neuroid.newSynapses[neuroid].value; + return neuroid.synapses[neuroid].value; else return Vector3.zero; } @@ -47,12 +46,12 @@ public class VelocityNeuroid : Neuroid { private Vector3 lastPosition = Vector3.zero; private float lastValueTime = 0; - public VelocityNeuroid(NeuroidNetwork net) : base(net) { + public VelocityNeuroid(NeuroidNetwork net) : base(net, "Velocity") { } protected override void UpdateState() { // Assuming only one synapse for now.... - Vector3 currentPosition = this.newSynapses.First().Value.value; + Vector3 currentPosition = this.synapses.First().Value.value; float currentValueTime = Time.time; float deltaTime = currentValueTime - lastValueTime; diff --git a/Assets/Scenes/Boids/Boids.unity b/Assets/Scenes/Boids/Boids.unity index 970b54d..0f3df0d 100644 --- a/Assets/Scenes/Boids/Boids.unity +++ b/Assets/Scenes/Boids/Boids.unity @@ -377,7 +377,6 @@ MonoBehaviour: cohesionForce: 5 separationForce: 5 separationDistance: 0.3 - bodyForce: 20 perceptionDistance: 1 boundaryForce: 5 spaceSize: {x: 10, y: 10, z: 10} @@ -394,7 +393,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: ec888ca5333d45a438f9f417fa5ce135, type: 3} m_Name: m_EditorClassIdentifier: Assembly-CSharp::SwarmSpawn - count: 5 + count: 10 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 d0cd02e..1375e50 100644 --- a/Assets/Scenes/Boids/Scripts/Boid.cs +++ b/Assets/Scenes/Boids/Scripts/Boid.cs @@ -11,6 +11,8 @@ public class Boid : MonoBehaviour { public float separationDistance = 0.5f; public float bodyForce = 1; + public bool debug = false; + public SwarmControl sc; public Vector3 velocity = Vector3.zero; public Vector3 acceleration = Vector3.zero; @@ -27,7 +29,7 @@ public class Boid : MonoBehaviour { public Neuroid cohesion; public Neuroid alignment; public Neuroid separation; - public Neuroid target; + // public Neuroid target; public Neuroid boundary; public Neuroid totalForce; @@ -42,34 +44,28 @@ 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 }; + cohesion = new(neuroidNet, "Cohesion"); 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 }; + + alignment = new(neuroidNet, "Alignment") { average = true }; + perception.SendVelocities(alignment); + + separation = new(neuroidNet, "Separation") { inverse = true, exponent = 2 }; + perception.SendPositions(separation, sc.separationForce); + + boundary = new(neuroidNet, "Boundary"); - totalForce = new(neuroidNet) { name = "Total force", mode = Neuroid.Mode.Sum }; + totalForce = new(neuroidNet, "Total"); totalForce.GetInputFrom(alignment, sc.alignmentForce); totalForce.GetInputFrom(cohesion, sc.cohesionForce); - totalForce.GetInputFrom(separation, sc.separationForce); - totalForce.GetInputFrom(target, sc.bodyForce); + totalForce.GetInputFrom(separation, -sc.separationForce); totalForce.GetInputFrom(boundary, sc.boundaryForce); } void Update() { Physics.OverlapSphereNonAlloc(this.transform.position, sc.perceptionDistance, results); - neighbourCount = 0; - - cohesion.ResetWeights(); - alignment.ResetWeights(); - //separation.ResetWeights(); - foreach (Collider c in results) { if (c == null) continue; @@ -80,45 +76,40 @@ public class Boid : MonoBehaviour { continue; Vector3 localPosition = neighbour.transform.position - this.transform.position; - Vector3 relativeVelocity = neighbour.velocity - this.velocity; + if (debug) + Debug.Log($" distance {localPosition.magnitude}"); int thingId = neighbour.GetInstanceID(); perception.ProcessStimulus(thingId, localPosition); - - Vector3 separationForce = -localPosition / localPosition.sqrMagnitude; - // which is equivalent to -(localPosition.normalized / localPosition.magnitude) - - separation.SetInput(thingId, separationForce, sc.separationDistance, neuroidNet); - //cohesion.SetInput(thingId, localPosition, sc.cohesionForce); - alignment.SetInput(thingId, relativeVelocity, sc.alignmentForce, neuroidNet); - neighbourCount++; - } } - //Vector3 spaceLocalPosition = sc.transform.InverseTransformPoint(this.transform.position); - if (!bounds.Contains(this.transform.position)) { - Vector3 point = this.transform.position; - // Vector3 distanceOutside = Vector3.Max(bounds.min - this.transform.position, this.transform.position - bounds.max); - // // Ensure value is > 0 (but isn't this already) - // Vector3 outside = distanceOutside; // Vector3.Max(Vector3.zero, distanceOutside); + // if (!bounds.Contains(this.transform.position)) { + // Vector3 point = this.transform.position; + // // Vector3 distanceOutside = Vector3.Max(bounds.min - this.transform.position, this.transform.position - bounds.max); + // // // Ensure value is > 0 (but isn't this already) + // // Vector3 outside = distanceOutside; // Vector3.Max(Vector3.zero, distanceOutside); - Vector3 below = bounds.min - point; // positive where point < min - Vector3 above = point - bounds.max; // positive where point > max + // Vector3 below = bounds.min - point; // positive where point < min + // Vector3 above = point - bounds.max; // positive where point > max - // outside distances per axis (0 if inside on that axis) - Vector3 outside = Vector3.Max(Vector3.zero, Vector3.Max(below, above)); - float magnitude = outside.magnitude; - Vector3 direction = (sc.transform.position - this.transform.position).normalized; - outside = direction * magnitude; + // // outside distances per axis (0 if inside on that axis) + // Vector3 outside = Vector3.Max(Vector3.zero, Vector3.Max(below, above)); + // float magnitude = outside.magnitude; + // Vector3 direction = (sc.transform.position - this.transform.position).normalized; + // outside = direction * magnitude; - boundary.SetInput(id, outside, sc.boundaryForce, neuroidNet); - // Debug.Log($"boundary {this.transform.position} {outside} force = {outside * sc.boundaryForce}"); - } + // boundary.SetInput(id, outside, sc.boundaryForce, neuroidNet); + // // Debug.Log($"boundary {this.transform.position} {outside} force = {outside * sc.boundaryForce}"); + // } Vector3 totalForceVector = totalForce.outputValue; //Debug.DrawRay(this.transform.position, totalForceVector, Color.magenta); + if (this.debug) { + Debug.Log($"Cohesion {cohesion.outputValue.magnitude} separation {separation.outputValue.magnitude} alignment {alignment.outputValue.magnitude}"); + } + this.velocity = (1 - sc.inertia) * (totalForceVector * Time.deltaTime) + sc.inertia * velocity + (sc.speed * transform.forward); //this.velocity = Vector3.ClampMagnitude(this.velocity, sc.speed); @@ -132,74 +123,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++) { - if (neighbourSensor[i] == null || neighbourSensor[i].IsStale()) - availableIx = i; - else if (neighbourSensor[i].thingId == id) - return neighbourSensor[i].receptor; - } - if (availableIx != -1) { - if (neighbourSensor[availableIx] != null) { - Debug.Log($"revived receptor {availableIx} for {id}"); - neighbourSensor[availableIx].thingId = id; - return neighbourSensor[availableIx].receptor; - } - else { - Debug.Log($"new receptor for {id}"); - SensoryNeuroid neuroid = new(neuroidNet, id); - perceptionNeuroid.GetInputFrom(neuroid); - neighbourSensor[availableIx] = neuroid; - return neuroid.receptor; - } - } - //Debug.LogWarning($"No available receptor for {id}"); - return null; - } - - -/* - void ProcessStimulus(int thingId, Vector3 value) { - int availableIx = -1; - SensoryNeuroid leastInterestingNeuroid = null; - for (int i = 0; i < neighbourSensor.Length; i++) { - if (neighbourSensor[i] == null || neighbourSensor[i].IsStale()) - availableIx = i; - else if (neighbourSensor[i].thingId == thingId) { - neighbourSensor[i].receptor.SetValue(value); - return; - } - if (neighbourSensor[i] != null) { - if (leastInterestingNeuroid == null || leastInterestingNeuroid.receptor.GetValue().magnitude > neighbourSensor[i].receptor.GetValue().magnitude) - leastInterestingNeuroid = neighbourSensor[i]; - } - } - if (availableIx != -1) { - if (neighbourSensor[availableIx] != null) { - // Debug.Log($"revived receptor {availableIx} for {thingId}"); - neighbourSensor[availableIx].thingId = thingId; - neighbourSensor[availableIx].receptor.SetValue(value); - } - else { - // Debug.Log($"new receptor for {thingId}"); - SensoryNeuroid neuroid = new(neuroidNet, thingId); - cohesion.GetInputFrom(neuroid); - neighbourSensor[availableIx] = neuroid; - neuroid.receptor.SetValue(value); - } - } - else if (leastInterestingNeuroid != null) { - //Debug.Log($"replaced receptor {leastInterestingNeuroid.thingId} for {thingId}"); - leastInterestingNeuroid.thingId = thingId; - leastInterestingNeuroid.receptor.SetValue(value); - } - - //Debug.LogWarning($"No available receptor for {id}"); - } -*/ void OnDrawGizmosSelected() { Gizmos.DrawWireSphere(transform.position, sc.perceptionDistance); } diff --git a/Assets/Scenes/Boids/Scripts/SwarmControl.cs b/Assets/Scenes/Boids/Scripts/SwarmControl.cs index 7837162..fa502ab 100644 --- a/Assets/Scenes/Boids/Scripts/SwarmControl.cs +++ b/Assets/Scenes/Boids/Scripts/SwarmControl.cs @@ -10,7 +10,7 @@ public class SwarmControl : MonoBehaviour public float cohesionForce = 10.0f; public float separationForce = 5.0f; public float separationDistance = 0.5f; - public float bodyForce = 20; + // public float bodyForce = 20; public float perceptionDistance = 1.0f; public float boundaryForce = 2.0f;