NanoBrain-unitypackage/ClusterReceptor.cs

211 lines
7.7 KiB
C#

using System;
using System.Collections.Generic;
using UnityEngine;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using System.Linq;
[Serializable]
public class ClusterReceptor : Cluster, IReceptor {
public ClusterReceptor(ClusterPrefab prefab, Cluster parent, string name) : base(prefab, parent) {
this.name = name;
this.array = new NucleusArray(this);
if (this.name.IndexOf(":") < 0)
this.name += ": 0";
}
public ClusterReceptor(ClusterPrefab prefab, ClusterPrefab parent, string name) : base(prefab, parent) {
this.name = name;
this.array = new NucleusArray(this);
}
public string GetName() {
return this.name;
}
public override Nucleus ShallowCloneTo(Cluster parent) {
ClusterReceptor clone = new(this.prefab, parent, this.name) {
clusterPrefab = this.clusterPrefab,
//array = this.array
};
//CloneFields(clone);
// This cloned the prefab with the clusternuclei,
// but did not clone the receivers outside the cluster
RestoreExternalReceivers(clone, this.clusterPrefab, parent);
return clone;
}
public override Nucleus Clone(ClusterPrefab parent) {
ClusterReceptor clone = new(prefab, parent, this.name) {
array = this._array
};
//CloneFields(clone);
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);
// }
this._outputs = null; // Make sure the output are regenerated
foreach (Neuron output in this.outputs) {
int ix = GetNucleusIndex(this.clusterNuclei, output);
if (ix < 0 || clone.clusterNuclei[ix] is not Neuron clonedOutput)
continue;
foreach (Nucleus receiver in output.receivers)
clonedOutput.AddReceiver(receiver);
}
return clone;
}
public override List<Nucleus> CollectReceivers() {
List<Nucleus> receivers = new();
foreach (Nucleus element in this.nucleiArray) {
if (element is not Cluster clusterElement)
continue;
foreach (Nucleus outputNucleus in clusterElement.clusterNuclei) {
if (outputNucleus is not Neuron output)
continue;
// this should be clusterElement.outputs,
// but outputs is not updated when correctly and may contain old data...
foreach (Nucleus receiver in output.receivers) {
// Only add receivers outside clusterElement cluster
if (receiver.clusterPrefab != clusterElement.prefab &&
receivers.Contains(receiver) == false)
receivers.Add(receiver);
}
}
}
return receivers;
}
[SerializeReference]
private NucleusArray _array;
public NucleusArray array {
//get { return _array; }
set { _array = value; }
}
//[SerializeReference]
//private Nucleus[] _nucleusArray;
public Nucleus[] nucleiArray {
get { return _array.nuclei; }
set { _array.nuclei = value; }
}
public void AddReceptorElement(ClusterPrefab prefab) {
IReceptorHelpers.AddReceptorElement(this, prefab);
}
public void RemoveReceptorElement() {
IReceptorHelpers.RemoveReceptorElement(this);
}
public void AddArrayReceiver(Nucleus receiverToAdd, float weight = 1) {
IReceptorHelpers.AddArrayReceiver(this, receiverToAdd, weight);
}
public override void UpdateStateIsolated() {
float3 sum = this.bias;
foreach (Nucleus nucleus in this.sortedNuclei)
nucleus.UpdateStateIsolated();
this.outputValue = this.defaultOutput.outputValue;
this.stale = 0;
UpdateNuclei();
}
public override void UpdateNuclei() {
this.stale++;
if (this.stale > staleValueForSleep && lengthsq(this.bias) > 0) {
this.bias = new float3(0, 0, 0);
this.parent.UpdateFromNucleus(this);
}
foreach (Nucleus nucleus in this.clusterNuclei)
nucleus.UpdateNuclei();
}
public override void ProcessStimulus(Vector3 inputValue, int thingId = 0, string thingName = null) {
this._array ??= new NucleusArray(this.parent);
this._array.ProcessStimulus(thingId, inputValue, thingName);
}
private Dictionary<int, ClusterReceptor> thingReceivers = new();
public virtual void ProcessStimulus(Neuron input, Vector3 inputValue, int thingId = 0, string thingName = null) {
inputValue = input.Activator(inputValue);
if (!thingReceivers.TryGetValue(thingId, out ClusterReceptor selectedReceiver))
selectedReceiver = FindReceiver(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;
}
int inputIx = this.GetNucleusIndex(this.clusterNuclei, input);
if (inputIx < 0)
return;
if (selectedReceiver.clusterNuclei[inputIx] is Neuron selectedNeuron)
selectedNeuron.ProcessStimulusDirect(inputValue);
}
private ClusterReceptor FindReceiver(int thingId, float3 inputValue) {
// No existing nucleus for this thing
float inputMagnitude = length(inputValue);
ClusterReceptor selectedReceiver = null;
float selectedMagnitude = 0;
foreach (ClusterReceptor receiver in nucleiArray.Cast<ClusterReceptor>()) {
if (thingReceivers.ContainsValue(receiver) == false) {
// We found an unusued receiver
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);
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;
}
}