NanoBrain-unitypackage/ReceptorArray.cs

261 lines
9.6 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Unity.Mathematics;
using static Unity.Mathematics.math;
[Serializable]
public class ReceptorInstance : Nucleus {
public ReceptorInstance(Cluster parent, string name) {
this.parent = parent;
this.name = name;
// We explicitly do not add this to the parent, as it is serialized in the ReceptorArray
}
public ReceptorInstance(ClusterPrefab prefab, string name) {
this.cluster = prefab;
this.name = name;
// We explicitly do not add this to the prefab, as it is serialized in the ReceptorArray
}
public override Nucleus ShallowCloneTo(Cluster parent) {
ReceptorInstance clone = new(parent, name + " +1") {
receptor = this.receptor
};
return clone;
}
public override Nucleus Clone(ClusterPrefab prefab) {
ReceptorInstance clone = new(prefab, name) {
receptor = this.receptor
};
return clone;
}
[SerializeReference]
public ReceptorArray receptor;
public override void UpdateStateIsolated() {
}
}
[Serializable]
public class ReceptorArray : Nucleus {
public ReceptorArray(Cluster parent, string name) {
this.parent = parent;
this.name = name;
this._instances = new ReceptorInstance[1];
this._instances[0] = new ReceptorInstance(parent, this.name + "[0]") {
receptor = this
};
this.parent?.nuclei.Add(this);
}
public ReceptorArray(ClusterPrefab prefab, string name) {
this.cluster = prefab;
this.name = name;
this._instances = new ReceptorInstance[1];
this._instances[0] = new ReceptorInstance(prefab, this.name + "[0]") {
receptor = this
};
if (this.cluster != null)
this.cluster.nuclei.Add(this);
else
Debug.LogError("No prefab when adding receptor to prefab");
}
public override Nucleus ShallowCloneTo(Cluster parent) {
ReceptorArray clone = new(parent, name) {
_instances = new ReceptorInstance[this.instances.Length]
};
for (int ix = 0; ix < this.instances.Length; ix++) {
clone._instances[ix] = new ReceptorInstance(parent, $"{this.name} [{ix}]") {
receptor = clone
};
}
return clone;
}
public override Nucleus Clone(ClusterPrefab prefab) {
ReceptorArray clone = new(prefab, this.name) {
_instances = new ReceptorInstance[this.instances.Length]
};
for (int ix = 0; ix < this.instances.Length; ix++) {
clone._instances[ix] = new ReceptorInstance(prefab, this.name) {
receptor = this
};
}
foreach (Synapse synapse in this.synapses) {
Synapse clonedSynapse = clone.AddSynapse(synapse.nucleus);
clonedSynapse.weight = synapse.weight;
}
foreach (Nucleus receiver in this.receivers) {
clone.AddReceiver(receiver);
}
return clone;
}
[SerializeReference]
private ReceptorInstance[] _instances = new ReceptorInstance[0];
public ReceptorInstance[] instances {
get {
return _instances;
}
}
public void AddReceptor(ClusterPrefab prefab) {
if (this._instances.Length == 0) {
Debug.LogError("Empty receptor array, cannot add");
return;
}
int newLength = this._instances.Length + 1;
ReceptorInstance[] newArray = new ReceptorInstance[newLength];
for (int i = 0; i < this._instances.Length; i++)
newArray[i] = this._instances[i];
ReceptorInstance newReceptor = (ReceptorInstance)this._instances[0].Clone(prefab);
newReceptor.name = $"{this.name} [{this._instances.Length}]";
newArray[newLength - 1] = newReceptor;
this._instances = newArray;
}
public void RemoveReceptor() {
int newLength = this._instances.Length - 1;
if (newLength == 0) {
Debug.LogWarning("Receptor array cannot be empty");
return;
}
ReceptorInstance[] newPerceptei = new ReceptorInstance[newLength];
for (int i = 0; i < newLength; i++)
newPerceptei[i] = this._instances[i];
// Delete the last perception
if (this._instances[newLength] is Nucleus nucleus)
Neuron.Delete(nucleus); //this._nuclei[newLength]);
this._instances = newPerceptei;
}
private Dictionary<int, Nucleus> thingReceivers = new();
// public override void ProcessStimulus(int thingId, Vector3 inputValue, string thingName = null) {
// ProcessStimulus(inputValue, thingId, thingName);
// }
public override void ProcessStimulus(Vector3 inputValue, int thingId = 0, string thingName = null) {
CleanupReceivers();
if (!thingReceivers.TryGetValue(thingId, out Nucleus selectedReceiver)) {
//Debug.Log($" no receiver found for {thingId}");
// No existing nucleus for this thing
selectedReceiver = SelectReceptor(thingId, inputValue);
}
if (selectedReceiver == null)
return;
if (thingName != null) {
string baseName = selectedReceiver.name;
int colonPos = selectedReceiver.name.IndexOf(":");
if (colonPos > 0)
baseName = selectedReceiver.name[..colonPos];
selectedReceiver.name = baseName + ": " + thingName;
}
//if (selectedReceiver is Neuron selectedNucleus) {
selectedReceiver.stale = 0;
selectedReceiver.outputValue = inputValue;
this.parent.UpdateFromNucleus(this);
//selectedNucleus.ProcessStimulus(inputValue);
//}
}
private void CleanupReceivers() {
// Remove a thing-receiver connection when the nucleus is inactive
List<int> receiversToRemove = new();
thingReceivers ??= new();
foreach (KeyValuePair<int, Nucleus> item in thingReceivers) {
if (item.Value.isSleeping)
receiversToRemove.Add(item.Key);
}
foreach (int thingId in receiversToRemove) {
Nucleus selectedReceiver = thingReceivers[thingId];
// Debug.Log($" removed receiver for {thingId}");
thingReceivers.Remove(thingId);
int colonPos = selectedReceiver.name.IndexOf(":");
if (colonPos > 0)
selectedReceiver.name = selectedReceiver.name[..colonPos];
}
}
private Nucleus SelectReceptor(int thingId, float3 inputValue) {
// No existing nucleus for this thing
float inputMagnitude = length(inputValue);
Nucleus selectedReceiver = null;
float selectedMagnitude = 0;
this._instances ??= new ReceptorInstance[0];
foreach (Nucleus receiver in this._instances) {
if (thingReceivers.ContainsValue(receiver) == false) {
// We found an unusued receiver
// Debug.Log($"{thingId} -> [{receiver.name}]");
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);
Debug.Log($"{thingId} -> [{selectedReceiver.name}]");
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;
}
public override void UpdateStateIsolated() {
float3 sum = this.bias;
// Receptors do not have inputs, so we ignore the synapses
foreach (Nucleus nucleus in this._instances)
sum += nucleus.outputValue;
this.outputValue = sum / _instances.Length;
this.stale = 0;
UpdateNuclei();
}
public override void UpdateNuclei() {
foreach (Nucleus nucleus in this.instances) {
nucleus.stale++;
if (nucleus.stale > staleValueForSleep && lengthsq(nucleus.outputValue) > 0) {
nucleus.outputValue = Vector3.zero;
//this.UpdateStateIsolated();
this.parent.UpdateFromNucleus(this);
}
}
}
}