934 lines
39 KiB
C#

using System;
using System.Collections.Generic;
using UnityEngine;
#if UNITY_MATHEMATICS
using Unity.Mathematics;
using static Unity.Mathematics.math;
#endif
namespace NanoBrain {
/// <summary>
/// A Cluster combines a collection of Nuclei to implement reusable behaviour
/// </summary>
/// A Cluster is an instantiation of a ClusterPrefab.
/// Clusters can be nested inside other clusters.
[Serializable]
public class Cluster : Nucleus {
// It may be that clusters will not be nuclei anymore in the future....
public ClusterPrefab prefab;
/// <summary>
/// The base name of the cluster. I don't think this is actively used at this moment
/// </summary>
public string baseName {
get {
int colonPositon = this.name.IndexOf(':');
if (colonPositon < 0)
return this.name;
return this.name[..colonPositon];
}
}
// This should not be serialized
//[SerializeReference]
[NonSerialized]
public Cluster[] siblingClusters;
// This serialization should be enough
[SerializeField]
public int instanceCount = 1;
public Dictionary<int, Cluster> thingClusters = new();
[SerializeReference]
public List<Nucleus> nuclei = new();
// the nuclei sorted using topological sorting
// to ensure that the cluster is computer in the right order
public List<Nucleus> sortedNuclei;
#region Init
/// <summary>
/// Instantiate a new copy of a ClusterPrefab in the given parent
/// </summary>
/// <param name="prefab">The prefab to use</param>
/// <param name="parent">The cluster in which this new cluster will be placed</param>
public Cluster(ClusterPrefab prefab, Cluster parent) {
this.prefab = prefab;
this.name = prefab.name;
this.parent = parent;
this.parent?.nuclei.Add(this);
ClonePrefab();
_ = this.inputs;
this.sortedNuclei = TopologicalSort(this.nuclei);
}
/// <summary>
/// Add a new cluster to a ClusterPrefab
/// </summary>
/// <param name="prefab">The prefab to copy</param>
/// <param name="parent">The prefab in which the new copy is placed</param>
public Cluster(ClusterPrefab prefab, ClusterPrefab parent = null) {
this.prefab = prefab;
this.name = prefab.name;
if (parent != null)
this.parent = parent.cluster;
// if (this.parent.prefab != null)
// this.parent.prefab.cluster.nuclei.Add(this);
ClonePrefab();
_ = this.inputs;
this.sortedNuclei = TopologicalSort(this.nuclei);
}
/// <summary>
/// Clone a prefab.
/// </summary>
/// Strange that this does not take any parameters or return values.
/// Where which the clone be found???
private void ClonePrefab() {
Nucleus[] prefabNuclei = this.prefab.cluster.nuclei.ToArray();
// first clone the nuclei without their connections
foreach (Nucleus nucleus in prefabNuclei) {
nucleus.ShallowCloneTo(this);
}
Nucleus[] clonedNuclei = this.nuclei.ToArray();
// Now clone the connections
for (int nucleusIx = 0; nucleusIx < prefabNuclei.Length; nucleusIx++) {
Nucleus prefabNucleus = prefabNuclei[nucleusIx];
if (prefabNucleus is not Neuron prefabNeuron)
continue;
Nucleus clonedNucleus = clonedNuclei[nucleusIx];
if (clonedNucleus == null || clonedNucleus is not Neuron clonedNeuron)
continue;
foreach (Synapse prefabSynapse in prefabNeuron.synapses) {
Neuron synapseNeuron = prefabSynapse.neuron;
if (synapseNeuron.parent.prefab != null && synapseNeuron.parent.prefab != this.prefab) {
// Neuron is in another cluster, find the cloned cluster first
ClusterPrefab prefabCluster = synapseNeuron.parent.prefab;
Cluster clonedCluster = this.nuclei.Find(n => n.name == prefabCluster.name) as Cluster;
if (clonedCluster == null)
continue;
// Now find the neuron in that cloned cluster
int neuronIx = GetNucleusIndex(prefabCluster.cluster.nuclei, prefabSynapse.neuron.name);
if (neuronIx < 0)
// Could not find the neuron in the prefab cluster
continue;
if (clonedCluster.nuclei[neuronIx] is not Neuron clonedSender)
// Could not find the neuron in the cloned cluster
continue;
clonedSender.AddReceiver(clonedNeuron, prefabSynapse.weight);
//Debug.Log($"Add synapse {clonedCluster.name}.{clonedSender.name} -> {clonedNeuron.name} [{clonedSender.receivers.Count}]");
}
else {
int ix = GetNucleusIndex(prefabNuclei, prefabSynapse.neuron);
if (ix < 0)
continue;
if (clonedNuclei[ix] is not Neuron clonedSender)
continue;
// Copy the receivers which will also create the synapse
clonedSender.AddReceiver(clonedNeuron, prefabSynapse.weight);
// Debug.Log($"Add synapse {clonedSender.name} -> {clonedNeuron.name}");
}
}
// // Copy the receivers, which will also create the synapses
// foreach (Nucleus receiver in prefabNeuron.receivers.ToArray()) {
// int ix = GetNucleusIndex(prefabNuclei, receiver);
// if (ix < 0)
// continue;
// if (clonedNuclei[ix] is not Nucleus clonedReceiver)
// continue;
// // Find the synapse for the weight
// float weight = 1;
// foreach (Synapse synapse in receiver.synapses) {
// // Find the weight for this synapse
// if (synapse.neuron == prefabNucleus) {
// weight = synapse.weight;
// break;
// }
// }
// clonedNeuron.AddReceiver(clonedReceiver, weight);
// }
}
if (Application.isPlaying) {
// Only create cluster siblings at runtime
foreach (Nucleus clonedNucleus in clonedNuclei) {
if (clonedNucleus is not Cluster clonedCluster)
continue;
List<Cluster> siblings = new() {
clonedCluster
};
for (int instanceIx = 1; instanceIx < clonedCluster.instanceCount; instanceIx++) {
// Create another sibling
Debug.Log($"create {clonedCluster.prefab.name} sibling");
Cluster sibling = new(clonedCluster.prefab, this) {
name = $"{clonedCluster.baseName}: {instanceIx}",
parent = this.parent,
instanceCount = this.instanceCount,
};
siblings.Add(sibling);
CopyAllExternalReceivers(clonedCluster, sibling, clonedCluster.prefab, this);
}
Cluster[] siblingClusters = siblings.ToArray();
foreach (Cluster sibling in siblings)
sibling.siblingClusters = siblingClusters;
}
// Ensure that all neurons are computed to initialize bias
foreach (Nucleus clonedNucleus in clonedNuclei) {
clonedNucleus.UpdateStateIsolated();
}
}
/*
for (int nucleusIx = 0; nucleusIx < clonedNuclei.Length; nucleusIx++) {
Nucleus prefabNucleus = prefabNuclei[nucleusIx];
if (prefabNucleus is not Cluster prefabCluster)
continue;
if (prefabCluster.instanceCount <= 1)
continue;
Cluster clonedNucleus = clonedNuclei[nucleusIx] as Cluster;
if (prefabCluster == prefabCluster.siblingClusters[0]) {
// We clone the array only for the first entry
//NucleusArray clonedArray = new(prefabReceptor.nucleiArray.Length);
Cluster[] clonedArray = new Cluster[prefabCluster.siblingClusters.Length];
int arrayIx = 0;
foreach (Cluster prefabArrayNucleus in prefabCluster.siblingClusters) {
int arrayNucleusIx = GetNucleusIndex(prefabNuclei, prefabArrayNucleus);
if (arrayNucleusIx >= 0) {
Cluster clonedArrayNucleus = clonedNuclei[arrayNucleusIx] as Cluster;
clonedArray[arrayIx] = clonedArrayNucleus;
}
else {
Debug.LogError($" Could not find prefab nucleus {prefabNucleus.name} in the clones");
}
arrayIx++;
}
clonedNucleus.siblingClusters = clonedArray;
}
else {
// The others will refer to the array created for the first nucleus in the array
int firstNucleusIx = GetNucleusIndex(prefabNuclei, prefabCluster.siblingClusters[0]);
Cluster clonedFirstNucleus = clonedNuclei[firstNucleusIx] as Cluster;
clonedNucleus.siblingClusters = clonedFirstNucleus.siblingClusters;
}
}
}
/*
// Collect the subclusters
List<Cluster> subClusters = new();
foreach (Nucleus nucleus in prefabNuclei) {
foreach (Synapse synapse in nucleus.synapses) {
Nucleus synapseNucleus = synapse.neuron;
Cluster subCluster = synapseNucleus.parent;
if (subCluster is null ||
synapseNucleus.clusterPrefab == this.clusterPrefab) {
continue;
}
// if (synapseNucleus is not Cluster subCluster)
// continue;
if (subClusters.Contains(subCluster))
continue;
subClusters.Add(subCluster);
}
}
// Create the subcluster instances
foreach (Cluster subCluster in subClusters) {
for (int ix = 0; ix < subCluster.instanceCount; ix++) {
// create the new instance
Cluster clusterInstance = new(subCluster.prefab);
// connect it
foreach ((Neuron sender, Nucleus receiver) in subCluster.CollectConnections()) {
int receiverIx = GetNucleusIndex(prefabNuclei, receiver);
if (receiverIx < 0)
continue;
if (clonedNuclei[receiverIx] is not Nucleus clonedReceiver)
continue;
// Find the synapse for the weight
float weight = 1;
foreach (Synapse synapse in receiver.synapses) {
// Find the weight for this synapse
if (synapse.neuron == sender) {
weight = synapse.weight;
break;
}
}
if (clusterInstance.GetNucleus(sender.name) is not Neuron clonedSender)
continue;
clonedSender.AddReceiver(clonedReceiver, weight);
}
}
}
*/
// foreach (Nucleus nucleus in this.clusterNuclei) {
// if (nucleus is Cluster clonedSubCluster)
// RestoreAllExternalReceivers(clonedSubCluster, this.prefab, this);
// }
}
private void CloneSynapses(Neuron prefabNeuron, Neuron clonedNeuron) {
foreach (Synapse prefabSynapse in prefabNeuron.synapses) {
Neuron synapseNeuron = prefabSynapse.neuron;
if (synapseNeuron.parent.prefab != null && synapseNeuron.parent.prefab != this.prefab) {
// Neuron is in another cluster, find the cloned cluster first
ClusterPrefab prefabCluster = synapseNeuron.parent.prefab;
Cluster clonedCluster = this.nuclei.Find(n => n.name == prefabCluster.name) as Cluster;
if (clonedCluster == null)
continue;
// Now find the neuron in that cloned cluster
int neuronIx = GetNucleusIndex(prefabCluster.cluster.nuclei, prefabSynapse.neuron.name);
if (neuronIx < 0)
// Could not find the neuron in the prefab cluster
continue;
if (clonedCluster.nuclei[neuronIx] is not Neuron clonedSender)
// Could not find the neuron in the cloned cluster
continue;
clonedSender.AddReceiver(clonedNeuron, prefabSynapse.weight);
//Debug.Log($"Add synapse {clonedCluster.name}.{clonedSender.name} -> {clonedNeuron.name} [{clonedSender.receivers.Count}]");
}
else {
Neuron clonedSender = this.nuclei.Find(n => n.name == prefabSynapse.neuron.name) as Neuron;
// Copy the receivers which will also create the synapse
clonedSender.AddReceiver(clonedNeuron, prefabSynapse.weight);
// Debug.Log($"Add synapse {clonedSender.name} -> {clonedNeuron.name}");
}
}
}
/// <summary>
/// Sort the nuclei in a correct evaluation order
/// </summary>
/// <param name="nodes"></param>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
private List<Nucleus> TopologicalSort(List<Nucleus> nodes) {
Dictionary<Nucleus, int> inDegree = new();
foreach (Nucleus node in nodes)
inDegree[node] = 0; // Initialize in-degree to zero
// Calculate in-degrees
foreach (Nucleus node in nodes) {
if (node is Cluster cluster) {
foreach (Nucleus receiver in cluster.CollectReceivers())
inDegree[receiver]++;
}
else if (node is Neuron neuron) {
foreach (Nucleus receiver in neuron.receivers)
inDegree[receiver]++;
}
}
Queue<Nucleus> queue = new();
foreach (Nucleus node in nodes) {
if (inDegree[node] == 0) // Nodes with no dependencies
queue.Enqueue(node);
}
// The queue basically stores all input nuclei?
List<Nucleus> sortedOrder = new();
while (queue.Count > 0) {
Nucleus current = queue.Dequeue();
sortedOrder.Add(current); // Process the node
if (current is Neuron neuron) {
foreach (Nucleus receiver in neuron.receivers) {
inDegree[receiver]--;
if (inDegree[receiver] == 0) // If all dependencies resolved
queue.Enqueue(receiver);
}
}
else if (current is Cluster cluster) {
foreach (Nucleus receiver in cluster.CollectReceivers()) {
inDegree[receiver]--;
if (inDegree[receiver] == 0) // If all dependencies resolved
queue.Enqueue(receiver);
}
}
}
// Check for cycles in the graph
if (sortedOrder.Count != nodes.Count)
throw new InvalidOperationException("Graph is not a DAG; a cycle exists.");
return sortedOrder;
}
public override Nucleus Clone(ClusterPrefab parent) {
Cluster clone = new(this.prefab, parent);
// foreach (Synapse synapse in this.synapses) {
// Synapse clonedSynapse = clone.AddSynapse(synapse.neuron);
// clonedSynapse.weight = synapse.weight;
// }
foreach (Nucleus nucleus in this.nuclei) {
if (nucleus is Neuron output) {
foreach (Nucleus receiver in output.receivers) {
int ix = GetNucleusIndex(this.nuclei, output);
Debug.Log($"{output.name} -> {receiver.name}: {ix}");
if (ix < 0)
continue;
if (clone.nuclei[ix] is not Neuron clonedOutput)
continue;
clonedOutput.AddReceiver(receiver);
}
}
}
return clone;
}
public override Nucleus ShallowCloneTo(Cluster parent) {
// Clusters should not be cloned, but instantiated from the prefab....
Cluster clone = new(this.prefab, parent) {
name = this.name,
parent = this.parent,
instanceCount = this.instanceCount,
};
// Somehow siblingClusters should be cloned too. Believe I do this in ClonePrefab right now.
return clone;
}
private static void CopyAllExternalReceivers(Cluster sourceCluster, Cluster sibling, ClusterPrefab prefabParent, Cluster clonedParent) {
for (int nucleusIx = 0; nucleusIx < sourceCluster.nuclei.Count; nucleusIx++) {
Nucleus sourceNucleus = sourceCluster.nuclei[nucleusIx];
if (sourceNucleus is not Neuron sourceNeuron)
continue;
if (sibling.nuclei[nucleusIx] is not Neuron clonedNeuron)
continue;
// copy the receivers (and thus synapses) from the source to the sibling
foreach (Nucleus receiver in sourceNeuron.receivers) {
if (receiver is not Neuron receiverNeuron)
continue;
int ix = GetNucleusIndex(clonedParent.nuclei, receiver);
if (ix < 0 || ix >= clonedParent.nuclei.Count)
continue;
// Find the synapse for the weight
float weight = 1;
foreach (Synapse synapse in receiverNeuron.synapses) {
// Find the weight for this synapse
if (synapse.neuron == sourceNucleus) {
weight = synapse.weight;
break;
}
}
clonedNeuron.AddReceiver(receiver, weight);
Debug.Log($"external: {receiver.name} receives from {clonedNeuron.name} {clonedNeuron.GetHashCode()}");
}
}
}
protected int GetNucleusIndex(Nucleus[] nuclei, Nucleus nucleus) {
for (int i = 0; i < nuclei.Length; i++) {
if (nucleus == nuclei[i])
return i;
}
return -1;
}
public static int GetNucleusIndex(List<Nucleus> nuclei, Nucleus nucleus) {
int i = 0;
foreach (Nucleus nucleiElement in nuclei) {
//for (int i = 0; i < nuclei.Length; i++) {
if (nucleiElement == nucleus)
return i;
i++;
}
return -1;
}
public static int GetNucleusIndex(List<Nucleus> nuclei, string nucleusName) {
int i = 0;
foreach (Nucleus nucleiElement in nuclei) {
//for (int i = 0; i < nuclei.Length; i++) {
if (nucleiElement.name == nucleusName)
return i;
i++;
}
return -1;
}
#endregion Init
#region Cluster Array
public void AddInstance() {
this.instanceCount++;
}
public void AddInstance(ClusterPrefab prefab) {
// Ensure siblingClusters exists
if (this.siblingClusters == null || this.siblingClusters.Length == 0)
this.siblingClusters = new Cluster[1] { this };
// Prepare the new array
int newLength = this.siblingClusters.Length + 1;
Cluster[] newSiblings = new Cluster[newLength];
for (int i = 0; i < newSiblings.Length - 1; i++)
newSiblings[i] = this.siblingClusters[i];
Cluster newCluster = this.Clone(prefab) as Cluster;
string baseName = this.name;
int colonPos = baseName.IndexOf(":");
if (colonPos > 0)
baseName = baseName[..colonPos];
newCluster.name = $"{baseName}: {newLength - 1}";
newSiblings[newLength - 1] = newCluster;
// All siblingClusters need to user this array!
foreach (Cluster sibling in newSiblings)
sibling.siblingClusters = newSiblings;
}
public void RemoveInstance() {
if (instanceCount > 1)
instanceCount--;
else {
if (this.siblingClusters == null || this.siblingClusters.Length <= 1)
return;
// Prepare the new array
int newLength = this.siblingClusters.Length - 1;
Cluster[] newClusters = new Cluster[newLength];
for (int i = 0; i < newLength; i++)
newClusters[i] = this.siblingClusters[i];
Neuron.Delete(this.siblingClusters[^1]);
this.siblingClusters = newClusters;
}
}
public virtual Cluster GetThingCluster() {
Cluster selectedCluster = SelectCluster();
return selectedCluster;
}
public virtual Cluster GetThingCluster(int thingId, string thingName = null) {
if (thingClusters.TryGetValue(thingId, out Cluster cluster))
return cluster;
Cluster selectedCluster = SelectCluster();
selectedCluster.name = baseName + ": " + thingName;
thingClusters[thingId] = selectedCluster;
return selectedCluster;
}
private Cluster SelectCluster() {
if (this.siblingClusters == null)
return this;
// Find a sleeping cluster
// foreach (Cluster cluster in this.siblingClusters) {
// if (cluster.defaultOutput.isSleeping) {
// RemoveThingCluster(cluster);
// return cluster;
// }
// }
// Find longest unused cluster
// Note this uses the default output...
Cluster unusedCluster = this.siblingClusters[0];
for (int ix = 1; ix < this.siblingClusters.Length; ix++) {
if (this.siblingClusters[ix].defaultOutput.lastUpdate < unusedCluster.defaultOutput.lastUpdate)
unusedCluster = this.siblingClusters[ix];
}
RemoveThingCluster(unusedCluster);
return unusedCluster;
}
private void RemoveThingCluster(Cluster cluster) {
List<int> keysToRemove = new();
foreach (KeyValuePair<int, Cluster> kvp in thingClusters) {
if (kvp.Value == cluster)
keysToRemove.Add(kvp.Key);
}
foreach (int thingId in keysToRemove)
thingClusters.Remove(thingId);
}
public bool SameSiblingsAs(Cluster[] otherSiblingClusters) {
if (this.siblingClusters == null)
return false;
for (int ix = 0; ix < this.siblingClusters.Length; ix++) {
if (this.siblingClusters[ix] != otherSiblingClusters[ix])
return false;
}
return true;
}
public void AddArrayReceiver(Nucleus receiverToAdd, float weight = 1) {
this.defaultOutput.AddReceiver(receiverToAdd, weight);
// foreach (Cluster cluster in this.siblingClusters) {
// cluster.defaultOutput.AddReceiver(receiverToAdd, weight);
// }
}
#endregion ClusterArray
public List<Nucleus> _inputs = null;
public virtual List<Nucleus> inputs {
get {
if (this._inputs == null) {
this._inputs = new();
foreach (Nucleus nucleus in this.nuclei) {
if (nucleus is not Neuron neuron)
continue;
// inputs have no synapses
if (neuron.synapses.Count == 0)
this._inputs.Add(nucleus);
}
RefreshComputeOrders();
}
return this._inputs;
}
}
private Dictionary<Nucleus, List<Nucleus>> _computeOrders;
public Dictionary<Nucleus, List<Nucleus>> computeOrders {
get {
if (_computeOrders == null || _computeOrders.Count == 0) {
_computeOrders = new();
foreach (Nucleus nucleus in this.nuclei)
_computeOrders[nucleus] = TopologicalSort2(nucleus);
}
return _computeOrders;
}
}
public void RefreshComputeOrders() {
this._computeOrders = null;
}
private List<Nucleus> TopologicalSort2(Nucleus startNode) {
Dictionary<Nucleus, int> inDegree = new();
//HashSet<Nucleus> visited = new();
// Calculate in-degrees for all nodes reachable from the start node
Queue<Nucleus> queue = new();
queue.Enqueue(startNode);
//visited.Add(startNode);
inDegree[startNode] = 0;
while (queue.Count > 0) {
Nucleus current = queue.Dequeue();
List<Nucleus> receivers = null;
if (current is Neuron neuron)
receivers = neuron.receivers;
else if (current is Cluster cluster)
receivers = cluster.CollectReceivers();
foreach (Nucleus receiver in receivers) {
if (!inDegree.ContainsKey(receiver)) {
//visited.Add(receiver);
inDegree[receiver] = 0;
queue.Enqueue(receiver);
}
inDegree[receiver]++;
}
}
// Perform topological sort on all reachable nodes
queue.Clear();
foreach (Nucleus node in inDegree.Keys) {
if (inDegree[node] == 0)
queue.Enqueue(node);
}
List<Nucleus> sortedOrder = new();
while (queue.Count > 0) {
Nucleus current = queue.Dequeue();
sortedOrder.Add(current); // Process the node
List<Nucleus> receivers = null;
if (current is Neuron neuron)
receivers = neuron.receivers;
else if (current is Cluster cluster)
receivers = cluster.CollectReceivers();
foreach (Nucleus receiver in receivers) {
if (inDegree.ContainsKey(receiver)) {
inDegree[receiver]--;
if (inDegree[receiver] == 0) // If all dependencies resolved
queue.Enqueue(receiver);
}
}
}
// Check for cycles in the graph
// if (sortedOrder.Count != visited.Count)
// throw new InvalidOperationException("Graph is not a DAG; a cycle exists.");
return sortedOrder;
}
public virtual Neuron defaultOutput {//=> this.nuclei[0] as Nucleus;
get {
if (this.nuclei.Count > 0)
return this.nuclei[0] as Neuron;
return null;
}
}
protected List<Neuron> _outputs = null;
public List<Neuron> outputs {
get {
if (this._outputs == null || this._outputs.Count == 0) {
this._outputs = new();
foreach (Nucleus nucleus in this.nuclei) {
if (nucleus is Neuron neuron && neuron.receivers.Count == 0)
this._outputs.Add(neuron);
}
}
return this._outputs;
}
}
public void RefreshOutputs() {
this._outputs = null;
}
public bool TryGetNucleus(string nucleusName, out Nucleus foundNucleus) {
foreach (Nucleus receptor in this.nuclei) {
if (receptor is Nucleus nucleus)
if (nucleus.name == nucleusName) {
foundNucleus = nucleus;
return true;
}
}
foundNucleus = null;
return false;
}
public Nucleus GetNucleus(string nucleusName) {
int dotPosition = nucleusName.IndexOf('.');
if (dotPosition >= 0) {
string clusterName = nucleusName[..dotPosition];
string clusterName0 = clusterName + ": 0";
foreach (Nucleus nucleus in this.nuclei) {
if (nucleus is Cluster cluster) {
if (cluster.name == clusterName || cluster.name == clusterName0) {
string subNucleusName = nucleusName[(dotPosition + 1)..];
return cluster.GetNucleus(subNucleusName);
}
}
}
return null;
}
else {
string nucleusName0 = nucleusName + ": 0";
foreach (Nucleus nucleus in this.nuclei) {
if (nucleus is Cluster) { //IReceptor receptor) {
if (nucleus.name == nucleusName | nucleus.name == nucleusName0)
return nucleus;
}
else if (nucleus.name == nucleusName)
return nucleus;
}
return null;
}
}
public Neuron GetNeuron(string neuronName) {
foreach (Nucleus nucleus in this.nuclei) {
if (nucleus is Neuron neuron && neuron.name == neuronName)
return neuron;
}
return null;
}
public bool DeleteNucleus(Nucleus nucleus) {
if (this.nuclei.Contains(nucleus) == false) {
// Try to find the nucleus by name
if (TryGetNucleus(nucleus.name, out nucleus) == false)
return false;
}
Neuron.Delete(nucleus);
//int nucleusIx = this.nuclei.IndexOf(nucleus);
this.nuclei.Remove(nucleus);
//this.prefab.cluster.nuclei.RemoveAt(nucleusIx);
RefreshOutputs();
return true;
}
#region Receivers
public virtual List<Nucleus> CollectReceivers(bool removeDuplicates = false) {
List<Nucleus> receivers = new();
foreach (Nucleus outputNucleus in this.nuclei) {
if (outputNucleus is not Neuron output)
continue;
// Debug.Log($"output {this.name} {outputNucleus.name}");
foreach (Nucleus receiver in output.receivers) {
// Debug.Log($"output {receiver.name}");
// Only add receivers outside this cluster
if (receiver.parent.prefab != this.prefab) {
if (removeDuplicates == false || receivers.Contains(receiver) == false)
// Debug.Log($" YES");
receivers.Add(receiver);
}
}
}
return receivers;
}
public List<(Neuron, Nucleus)> CollectConnections() {
List<(Neuron, Nucleus)> connections = new();
foreach (Nucleus outputNucleus in this.nuclei) {
if (outputNucleus is not Neuron output)
continue;
foreach (Nucleus receiver in output.receivers) {
// Only add receivers outside this cluster
if (receiver.parent.prefab != this.prefab)
connections.Add((output, receiver));
}
}
return connections;
}
public List<Synapse> CollectSynapsesTo(Cluster otherCluster) {
List<Synapse> collectedSynapses = new();
foreach (Nucleus nucleus in this.nuclei) {
if (nucleus is not Neuron neuron)
continue;
foreach (Synapse synapse in neuron.synapses) {
if (synapse.neuron.parent == otherCluster)
collectedSynapses.Add(synapse);
}
}
return collectedSynapses;
}
public void MoveReceivers(Cluster newCluster) {
Debug.Log($"Move receivers for {this.name} to {newCluster.name}");
foreach (Nucleus outputNucleus in this.nuclei) {
if (outputNucleus is not Neuron output)
continue;
// Find the existing output in the new cluster
if (newCluster.GetNucleus(output.name) is not Neuron newOutput) {
Debug.LogWarning($"Could not find output {this.name}.{output.name} in {newCluster.name}");
continue;
}
Debug.Log($"Check {this.name}.{output.name} receivers");
Nucleus[] receivers = output.receivers.ToArray();
foreach (Nucleus receiver in receivers) {
if (receiver.parent.prefab != this.prefab) {
// Replace synapse with new synapse
// to the new cluster
Debug.Log($"move {receiver.name} from {this.name}.{output.name} to {newCluster.name}.{newOutput.name}");
if (receiver is not Neuron receiverNeuron)
continue;
Synapse synapse = receiverNeuron.GetSynapse(output);
newOutput.AddReceiver(receiver, synapse.weight);
output.RemoveReceiver(receiver);
}
}
}
}
#endregion Receivers
#region Update
public void UpdateFromNucleus(Nucleus startNucleus) {
// no bias+synapse input state calculation for now...
if (this.computeOrders.ContainsKey(startNucleus) == false) {
Debug.LogError($"{this.name} compute orders does not contain an order for {startNucleus.name}");
return;
}
List<Nucleus> computeOrder = this.computeOrders[startNucleus];
//if (startNucleus.trace)
// Debug.Log($"Update from {startNucleus.name}");
foreach (Nucleus nucleus in computeOrder) {
if (nucleus is not Cluster) {
nucleus.UpdateStateIsolated();
//if (startNucleus.trace && nucleus is Neuron neuron)
// Debug.Log($" {nucleus.name}");
if (nucleus is Neuron neuron) {
foreach (Nucleus receiver in neuron.receivers) {
if (receiver.parent != this) {
Debug.Log($" External: {receiver.parent.name}.{receiver.name}");
receiver.parent.UpdateFromNucleus(receiver);
}
}
}
}
}
// continue in parent
//this.parent?.UpdateFromNucleus(this);
UpdateNuclei();
}
public override void UpdateStateIsolated() {
throw new Exception("Cluster should not be updated!");
}
public override void UpdateNuclei() {
foreach (Nucleus nucleus in this.nuclei)
nucleus.UpdateNuclei();
}
#endregion Update
public void Refresh() {
// This should not be needed, but somehow somewhere the parent is changed...
foreach (Nucleus nucleus in this.nuclei) {
if (nucleus is not Neuron neuron)
continue;
neuron.parent = this;
}
RefreshOutputs();
RefreshComputeOrders();
}
}
}