cc9a845 Fix sleeping for product combinator e4ba7f8 Better cross-cluster monitoring 4f8a6ab Improved (but not fixed) cross-cluster monitoring b12616b Fix neuron output visualisation 96439cc Visualize all outputs d583e67 WIP cluster references/instance 04bab92 Fix links to multiple cluster neurons & cleanup e17a249 Cross-cluster editor links 0ab2d21 Migrating and cleaning up b6630ad First steps to using instanceCount for clusters 8801fa2 Cluster reimport fixes befb69d full graph with collapsed clusters 1a1919f Fix expansion of clsuter arrays c708f4d Improved clusterarray support c2e4e1b Fix Cluster array extension 02047a4 Adde full graph scrollbar 471ed36 Completed full graph integration 830e3e7 Added full graph view mode 249e888 Improve full graph view 308a6a1 The Entities are battling 75d9d1c Cleanup c8f0f0c Fix aging of neurons e2e169c small fixes 619ced6 Removed the use of Receptors 19f9296 Simplifications bc0a796 Integrated clusterarray in cluster e40dd23 Fixed clusterViewer for clusterarrays b0f4b41 Status quo adding clusterArrays 1fc75a8 Added ClusterArray 0023920 Cover seeking(-ish) behaviour 1c7b8e7 Added Tanh Activation a99d40c BrainViewer added db43655 Pew pew! 18ef4cd Merge commit '89017475984bbbf1899fb38846c5bb0e7775dedd' into NanoBrain git-subtree-dir: NanoBrain git-subtree-split: cc9a845b643ffb4a9abe4f7da787ac5c5b14dae8
789 lines
33 KiB
C#
789 lines
33 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]
|
|
public Cluster[] siblingClusters;
|
|
// This serialization should be enough
|
|
public int instanceCount = 1;
|
|
public Dictionary<int, Cluster> thingClusters = new();
|
|
|
|
[SerializeReference]
|
|
public List<Nucleus> clusterNuclei = 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?.clusterNuclei.Add(this);
|
|
ClonePrefab();
|
|
_ = this.inputs;
|
|
this.sortedNuclei = TopologicalSort(this.clusterNuclei);
|
|
}
|
|
|
|
/// <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;
|
|
this.clusterPrefab = parent;
|
|
|
|
if (this.clusterPrefab != null)
|
|
this.clusterPrefab.nuclei.Add(this);
|
|
|
|
ClonePrefab();
|
|
_ = this.inputs;
|
|
this.sortedNuclei = TopologicalSort(this.clusterNuclei);
|
|
}
|
|
|
|
/// <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.nuclei.ToArray();
|
|
|
|
// first clone the nuclei without their connections
|
|
foreach (Nucleus nucleus in prefabNuclei) {
|
|
nucleus.ShallowCloneTo(this);
|
|
}
|
|
Nucleus[] clonedNuclei = this.clusterNuclei.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 is not null && synapseNeuron.clusterPrefab != this.clusterPrefab) {
|
|
// Neuron is in another cluster, find the cloned cluster first
|
|
Cluster prefabCluster = synapseNeuron.parent;
|
|
int clusterIx = GetNucleusIndex(prefabNuclei, prefabCluster);
|
|
if (clusterIx < 0)
|
|
// Could not find the cluster in the prefab
|
|
continue;
|
|
if (clonedNuclei[clusterIx] is not Cluster clonedCluster)
|
|
// Could not find the cloned cluster
|
|
continue;
|
|
|
|
// Now find the neuron in that cloned cluster
|
|
int neuronIx = GetNucleusIndex(prefabCluster.prefab.nuclei, prefabSynapse.neuron);
|
|
if (neuronIx < 0)
|
|
// Could not find the neuron in the prefab cluster
|
|
continue;
|
|
if (clonedCluster.clusterNuclei[neuronIx] is not Neuron clonedSender)
|
|
// Could not find the neuron in the cloned cluster
|
|
continue;
|
|
|
|
clonedSender.AddReceiver(clonedNeuron, prefabSynapse.weight);
|
|
}
|
|
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);
|
|
}
|
|
}
|
|
|
|
// // 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);
|
|
// }
|
|
}
|
|
/*
|
|
// Copy the siblings for clusters
|
|
for (int nucleusIx = 0; nucleusIx < prefabNuclei.Length; nucleusIx++) {
|
|
Nucleus prefabNucleus = prefabNuclei[nucleusIx];
|
|
if (prefabNucleus is not Cluster prefabCluster)
|
|
continue;
|
|
|
|
if (prefabCluster.siblingClusters == null || prefabCluster.siblingClusters.Length == 0)
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// <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.clusterNuclei) {
|
|
if (nucleus is Neuron output) {
|
|
foreach (Nucleus receiver in output.receivers) {
|
|
int ix = GetNucleusIndex(this.clusterNuclei, output);
|
|
Debug.Log($"{output.name} -> {receiver.name}: {ix}");
|
|
if (ix < 0)
|
|
continue;
|
|
|
|
if (clone.clusterNuclei[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,
|
|
clusterPrefab = this.clusterPrefab,
|
|
};
|
|
// Somehow siblingClusters should be cloned too. Believe I do this in ClonePrefab right now.
|
|
|
|
return clone;
|
|
}
|
|
|
|
private static void RestoreAllExternalReceivers(Cluster clonedCluster, ClusterPrefab prefabParent, Cluster clonedParent) {
|
|
int clonedClusterIx = GetNucleusIndex(clonedParent.clusterNuclei, clonedCluster);
|
|
if (prefabParent.nuclei[clonedClusterIx] is not Cluster sourceCluster)
|
|
return;
|
|
|
|
for (int nucleusIx = 0; nucleusIx < sourceCluster.clusterNuclei.Count; nucleusIx++) {
|
|
Nucleus sourceNucleus = sourceCluster.clusterNuclei[nucleusIx];
|
|
if (sourceNucleus is not Neuron sourceNeuron)
|
|
continue;
|
|
|
|
if (clonedCluster.clusterNuclei[nucleusIx] is not Neuron clonedNeuron)
|
|
continue;
|
|
|
|
// copy the receivers (and thus synapses) from the source to the clone
|
|
foreach (Nucleus receiver in sourceNeuron.receivers) {
|
|
int ix = GetNucleusIndex(prefabParent.nuclei, receiver);
|
|
if (ix < 0 || ix >= clonedParent.clusterNuclei.Count)
|
|
continue;
|
|
|
|
Nucleus clonedReceiver = clonedParent.clusterNuclei[ix];
|
|
|
|
// Find the synapse for the weight
|
|
float weight = 1;
|
|
foreach (Synapse synapse in receiver.synapses) {
|
|
// Find the weight for this synapse
|
|
if (synapse.neuron == sourceNucleus) {
|
|
weight = synapse.weight;
|
|
break;
|
|
}
|
|
}
|
|
|
|
clonedNeuron.AddReceiver(clonedReceiver, weight);
|
|
// Debug.Log($"external: {clonedReceiver.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 (nucleus == nucleiElement)
|
|
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();
|
|
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;
|
|
}
|
|
}
|
|
|
|
// Otherwise find longest unused cluster
|
|
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 Dictionary<string, Nucleus> nucleiDict = new();
|
|
|
|
public List<Nucleus> _inputs = null;
|
|
public virtual List<Nucleus> inputs {
|
|
get {
|
|
if (this._inputs == null) {
|
|
this._inputs = new();
|
|
foreach (Nucleus nucleus in this.clusterNuclei) {
|
|
// inputs have no synapses
|
|
if (nucleus.synapses.Count == 0)
|
|
this._inputs.Add(nucleus);
|
|
}
|
|
ComputeOrders();
|
|
}
|
|
return this._inputs;
|
|
}
|
|
}
|
|
|
|
public Dictionary<Nucleus, List<Nucleus>> computeOrders = new();
|
|
private void ComputeOrders() {
|
|
foreach (Nucleus input in this._inputs)
|
|
computeOrders[input] = TopologicalSort2(input);
|
|
}
|
|
|
|
private List<Nucleus> TopologicalSort2(Nucleus startNode) {
|
|
Dictionary<Nucleus, int> inDegree = new();
|
|
HashSet<Nucleus> visited = new();
|
|
|
|
// Initialize in-degrees and mark all nodes as unvisited
|
|
foreach (Nucleus node in this.clusterNuclei)
|
|
inDegree[node] = 0;
|
|
|
|
// Calculate in-degrees for all nodes reachable from the start node
|
|
Queue<Nucleus> queue = new Queue<Nucleus>();
|
|
queue.Enqueue(startNode);
|
|
visited.Add(startNode);
|
|
|
|
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();
|
|
|
|
// if (current is Neuron neuron) {
|
|
foreach (Nucleus receiver in receivers) {
|
|
if (!visited.Contains(receiver)) {
|
|
visited.Add(receiver);
|
|
queue.Enqueue(receiver);
|
|
}
|
|
inDegree[receiver]++;
|
|
}
|
|
// }
|
|
}
|
|
|
|
// Perform topological sort on all reachable nodes
|
|
queue.Clear();
|
|
foreach (Nucleus node in visited) {
|
|
if (inDegree[node] == 0)
|
|
queue.Enqueue(node);
|
|
}
|
|
|
|
List<Nucleus> sortedOrder = new List<Nucleus>();
|
|
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();
|
|
|
|
//if (current is Neuron neuron) {
|
|
|
|
foreach (Nucleus receiver in receivers) {
|
|
if (visited.Contains(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.clusterNuclei.Count > 0)
|
|
return this.clusterNuclei[0] as Neuron;
|
|
return null;
|
|
}
|
|
}
|
|
protected List<Neuron> _outputs = null;
|
|
public List<Neuron> outputs {
|
|
get {
|
|
if (this._outputs == null) {
|
|
this._outputs = new();
|
|
foreach (Nucleus nucleus in this.clusterNuclei) {
|
|
if (nucleus is Neuron neuron && neuron.receivers.Count == 0)
|
|
this._outputs.Add(neuron);
|
|
}
|
|
}
|
|
return this._outputs;
|
|
}
|
|
}
|
|
|
|
public bool TryGetNucleus(string nucleusName, out Nucleus foundNucleus) {
|
|
foreach (Nucleus receptor in this.clusterNuclei) {
|
|
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.clusterNuclei) {
|
|
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.clusterNuclei) {
|
|
if (nucleus is Cluster) { //IReceptor receptor) {
|
|
if (nucleus.name == nucleusName | nucleus.name == nucleusName0)
|
|
return nucleus;
|
|
}
|
|
else if (nucleus.name == nucleusName)
|
|
return nucleus;
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
#region Receivers
|
|
|
|
public virtual List<Nucleus> CollectReceivers() {
|
|
List<Nucleus> receivers = new();
|
|
foreach (Nucleus outputNucleus in this.clusterNuclei) {
|
|
if (outputNucleus is not Neuron output)
|
|
continue;
|
|
|
|
foreach (Nucleus receiver in output.receivers) {
|
|
// Only add receivers outside this cluster
|
|
if (receiver.clusterPrefab != this.prefab)
|
|
receivers.Add(receiver);
|
|
}
|
|
}
|
|
return receivers;
|
|
}
|
|
|
|
public List<(Neuron, Nucleus)> CollectConnections() {
|
|
List<(Neuron, Nucleus)> connections = new();
|
|
|
|
foreach (Nucleus outputNucleus in this.clusterNuclei) {
|
|
if (outputNucleus is not Neuron output)
|
|
continue;
|
|
|
|
foreach (Nucleus receiver in output.receivers) {
|
|
// Only add receivers outside this cluster
|
|
if (receiver.clusterPrefab != this.prefab)
|
|
connections.Add((output, receiver));
|
|
}
|
|
}
|
|
return connections;
|
|
}
|
|
|
|
public void MoveReceivers(Cluster newCluster) {
|
|
Debug.Log($"Move receivers for {this.name} to {newCluster.name}");
|
|
foreach (Nucleus outputNucleus in this.clusterNuclei) {
|
|
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.clusterPrefab != 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}");
|
|
Synapse synapse = receiver.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}[{nucleus.GetHashCode()}]"); // = {neuron.outputValue}");
|
|
}
|
|
}
|
|
|
|
// 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.clusterNuclei)
|
|
nucleus.UpdateNuclei();
|
|
}
|
|
|
|
#endregion Update
|
|
|
|
}
|
|
|
|
} |