using System;
using System.IO;
using System.Collections.Generic;
using UnityEngine;
#if UNITY_MATHEMATICS
using Unity.Mathematics;
using static Unity.Mathematics.math;
#endif
using NanoBrain.Unity;
namespace NanoBrain {
///
/// A Cluster combines a collection of Nuclei to implement reusable behaviour
///
/// 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....
///
/// The prefab used to create this cluster
///
/// Cluster should always be created from prefabs
public ClusterPrefab prefab;
public ClusterJson json;
public TextAsset jsonAsset;
//[HideInInspector]
///
/// The version number of this cluster instance
///
/// This is used to detect changes in the prefab which require this Cluster instance to be updated
public int version;
///
/// The base name of the cluster. I don't think this is actively used at this moment
///
public string baseName {
get {
int colonPositon = this.name.IndexOf(':');
if (colonPositon < 0)
return this.name;
return this.name[..colonPositon];
}
}
///
/// All cluster instance of a multi-cluster
///
/// A cluster is a multi-cluster when there is more than one instance.
/// The actual instances are only created at runtime.
/// The value instanceCount determines how many instances will be present at runtime.
//[NonSerialized]
[SerializeReference]
[HideInInspector]
public Cluster[] instances;
///
/// The number of cluster instances in a multi-cluster
///
/// A cluster is a multi-clsuter when there is more than one instance.
[SerializeField]
public uint instanceCount = 1;
///
/// The mapping from things to cluster instances
///
/// In a multi-cluster each instance can be used for a thing.
/// Cluster instance may also not (yet) be mapped to a thing.
[NonSerialized]
public Dictionary thingClusters = new();
///
/// All nuclei in this cluster
///
[SerializeReference]
[HideInInspector]
public List nuclei = new();
public class TimedAction {
public TimedAction(string neuronName, Action action, long timestamp) {
this.neuronName = neuronName;
this.timestamp = timestamp;
this.action = action;
}
public string neuronName;
public long timestamp;
public Action action;
public static void AddTo(List actions, string neuronName, Action action, long timestamp) {
int ix = -1;
int insertIx = actions.Count; // default: add at end
// Find existing item and the first item with timestamp > this.timestamp
for (int i = 0; i < actions.Count; i++) {
TimedAction item = actions[i];
if (ix < 0 && item.neuronName == neuronName) {
ix = i;
break;
}
}
if (ix >= 0) {
actions[ix].action = action;
actions[ix].timestamp = timestamp;
return;
}
// No existing item; compute insertion point (sorted by timestamp ascending)
for (int i = 0; i < actions.Count; i++) {
if (actions[i].timestamp > timestamp) {
insertIx = i;
break;
}
}
TimedAction newAction = new(neuronName, action, timestamp);
actions.Insert(insertIx, newAction);
}
}
public readonly List actions = new();
#region Init
public Cluster() {
this.name = "Empty Cluster";
}
///
/// Instantiate a new copy of a ClusterPrefab in the given parent
///
/// The prefab to use
/// The cluster in which this new cluster will be placed
public Cluster(ClusterPrefab prefab, Cluster parent, uint instanceCount = 1) {
this.prefab = prefab;
this.version = prefab.version;
this.name = prefab.name;
this.parent = parent;
this.parent?.nuclei.Add(this);
ClonePrefab();
if (instanceCount > 1) {
List siblings = new() { this };
for (int instanceIx = 1; instanceIx < instanceCount; instanceIx++) {
// Create another sibling
Cluster sibling = new(prefab, this) {
name = $"{this.baseName}: {instanceIx}",
parent = this.parent,
instanceCount = this.instanceCount,
};
siblings.Add(sibling);
CopyAllExternalReceivers(this, sibling, this);
}
Cluster[] siblingClusters = siblings.ToArray();
foreach (Cluster sibling in siblings) {
sibling.instanceCount = instanceCount;
sibling.instances = siblingClusters;
}
}
}
///
/// Add a new cluster to a ClusterPrefab
///
/// The prefab to copy
/// The prefab in which the new copy is placed
public Cluster(ClusterPrefab prefab, ClusterPrefab parent = null) {
this.prefab = prefab;
this.version = prefab.version;
this.name = prefab.name;
if (parent != null) {
TextAsset jsonFile = Resources.Load(parent.name);
string json = jsonFile.text;
ClusterData clusterData = JsonUtility.FromJson(json);
//ClusterData clusterData = Cluster.Import(jsonPath);
parent.cluster = new(clusterData);
this.parent = parent.cluster;
}
ClonePrefab();
}
public Cluster(ClusterJson json) {
ClusterData clusterData = JsonUtility.FromJson(json.jsonFile.text);
this.name = clusterData.name;
// First create all neurons
foreach (NeuronData neuronData in clusterData.neurons) {
Neuron newNeuron = new(this, neuronData.name);
}
// Now create the synapses between them
foreach (NeuronData neuronData in clusterData.neurons) {
Neuron receiver = this.GetNeuron(neuronData.name);
foreach (SynapseData synapseData in neuronData.synapses) {
Neuron sender = this.GetNeuron(synapseData.neuronName);
sender?.AddReceiver(receiver, synapseData.weight, synapseData.trainable);
}
}
}
public Cluster(ClusterData clusterData) {
this.name = clusterData.name;
// First create all neurons
foreach (NeuronData neuronData in clusterData.neurons) {
if (neuronData.type == Type.MemoryCell)
new MemoryCell(this, neuronData.name) {
bias = neuronData.bias,
combinator = neuronData.combinatorType,
activator = neuronData.activationType
};
else
new Neuron(this, neuronData.name) {
bias = neuronData.bias,
combinator = neuronData.combinatorType,
activator = neuronData.activationType
};
}
// Now create the synapses between them
foreach (NeuronData neuronData in clusterData.neurons) {
Neuron receiver = this.GetNeuron(neuronData.name);
foreach (SynapseData synapseData in neuronData.synapses) {
// If the synapse points to another cluster
if (synapseData.clusterName != this.name) {
// Add reference to external cluster
// Do we know the external cluster already?
ExternalClusterData externalClusterData = clusterData.GetCluster(synapseData.clusterName);
Cluster extCluster = this.GetCluster(synapseData.clusterName);
if (extCluster == null) {
// if not: create new external cluster
Debug.Log("New ext cluster");
ClusterPrefab extPrefab = Resources.Load(externalClusterData.prefabName) as ClusterPrefab;
extCluster = new(extPrefab, this, instanceCount) {
name = externalClusterData.name
};
}
extCluster.instanceCount = externalClusterData.instanceCount;
// Do we have the synapse already?
Neuron extNeuron = extCluster.GetNeuron(synapseData.neuronName);
Synapse synapse = receiver.GetSynapse(extNeuron); //extCluster, synapseData.neuronName);
if (synapse == null) {
Debug.Log("new receiver for external");
// If not: create new synapse
extNeuron.AddReceiver(receiver);
}
}
else {
Neuron sender = this.GetNeuron(synapseData.neuronName);
sender?.AddReceiver(receiver, synapseData.weight, synapseData.trainable);
}
}
}
}
///
/// Clone a prefab.
///
/// Strange that this does not take any parameters or return values.
/// Where which the clone be found???
private void ClonePrefab() {
//Debug.Log($"Clone Prefab {this.prefab.name} -> {this.name}");
if (this.prefab == null || this.prefab.cluster == null || this.prefab.cluster.nuclei == null)
return;
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
Cluster prefabCluster = synapseNeuron.parent;
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.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, prefabSynapse.trainable);
//Debug.Log($"Add synapse {clonedCluster.name}.{clonedSender.name} -> {clonedNeuron.name} [{clonedSender.receivers.Count}]");
}
else {
int ix = GetNucleusIndex(this.prefab.cluster.nuclei, 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, prefabSynapse.trainable);
// Debug.Log($"Add synapse {clonedSender.name} -> {clonedNeuron.name}");
}
}
}
foreach (Nucleus clonedNucleus in clonedNuclei) {
if (clonedNucleus is not Cluster clonedCluster)
continue;
List siblings = new() { clonedCluster };
for (int instanceIx = 1; instanceIx < clonedCluster.instanceCount; instanceIx++) {
// Create another sibling
Cluster sibling = new(clonedCluster.prefab, this) {
name = $"{clonedCluster.baseName}: {instanceIx}",
parent = this.parent,
instanceCount = this.instanceCount,
};
siblings.Add(sibling);
CopyAllExternalReceivers(clonedCluster, sibling, this);
}
Cluster[] siblingClusters = siblings.ToArray();
foreach (Cluster sibling in siblings)
sibling.instances = siblingClusters;
}
// Ensure that all neurons are computed to initialize bias
foreach (Nucleus clonedNucleus in clonedNuclei) {
if (clonedNucleus is not Cluster)
clonedNucleus.UpdateStateIsolated();
}
}
/// \copydoc NanoBrain::Nucleus::ShallowCloneTo
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,
};
return clone;
}
private static void CopyAllExternalReceivers(Cluster sourceCluster, Cluster sibling, 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()}");
}
}
}
///
/// Get the index of a nucleus in a list of nuclei
///
/// The list of nuclei to search
/// The nucleus to find
/// The index of the nucleus in the list or -1 when it has not been found
public static int GetNucleusIndex(List 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;
}
///
/// Get the index of a nucleus with the given name in a list of nuclei
///
/// The list of nuclei to search
/// The name of the nucleus to find
/// The index of the nucleus in the list or -1 when it has not been found
public static int GetNucleusIndex(List 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;
}
///
/// Initializes all trainable weights to random values
///
public void InitializeRandom() {
System.Random randomGenerator = new();
foreach (Nucleus nucleus in this.nuclei) {
if (nucleus is Neuron neuron) {
foreach (Synapse synapse in neuron.synapses) {
if (synapse.trainable) {
synapse.weight = (float)randomGenerator.NextDouble() * 2.0f - 1.0f;
// Debug.Log($"{neuron.name}-{synapse.neuron.name} weight = {synapse.weight}");
}
}
}
}
}
public void GaussianAdditiveMutation(float sigma) {
foreach (Nucleus nucleus in this.nuclei) {
if (nucleus is Neuron neuron) {
foreach (Synapse synapse in neuron.synapses) {
if (synapse.trainable) {
synapse.GaussianAdditiveMutation(sigma);
//synapse.weight = (float)randomGenerator.NextDouble() * 2.0f - 1.0f;
// Debug.Log($"{neuron.name}-{synapse.neuron.name} weight = {synapse.weight}");
}
}
}
}
}
#endregion Init
#region Cluster Array
///
/// Increase the number of instances in an multi-cluster
///
/// /remark Note this does not create the instances.
/// This is only intended to be used for prefabs.
public void AddInstance() {
this.instanceCount++;
}
///
/// Create an new instance in a multi-cluster
///
/// The prefab to use to create the new instance
/// /remark This does not change the instanceCount.
/// It should only be used at runtime.
public void AddInstance(ClusterPrefab prefab) {
// Ensure siblingClusters exists
if (this.instances == null || this.instances.Length == 0)
this.instances = new Cluster[1] { this };
// Prepare the new array
int newLength = this.instances.Length + 1;
Cluster[] newSiblings = new Cluster[newLength];
for (int i = 0; i < newSiblings.Length - 1; i++)
newSiblings[i] = this.instances[i];
//Cluster newCluster = this.Clone(prefab) as Cluster;
Cluster newCluster = new(prefab);
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.instances = newSiblings;
}
///
/// Decrease the number of instance in a multi-cluster
///
public void RemoveInstance() {
if (instanceCount > 1)
instanceCount--;
else {
// It is not clear to me why we update the siblingClusters when the
// instanceCount <= 1....
if (this.instances == null || this.instances.Length <= 1)
return;
// Prepare the new array
int newLength = this.instances.Length - 1;
Cluster[] newClusters = new Cluster[newLength];
for (int i = 0; i < newLength; i++)
newClusters[i] = this.instances[i];
Neuron.Delete(this.instances[^1]);
this.instances = newClusters;
}
}
///
/// Remove a mapping from a thing to a cluster such that it becomes available for new things
///
/// The multi-cluster instance which not no longer be mapped
private void RemoveThingCluster(Cluster cluster) {
int keyToRemove = -1;
foreach (KeyValuePair kvp in this.thingClusters) {
if (kvp.Value == cluster) {
keyToRemove = kvp.Key;
break;
}
}
if (keyToRemove >= 0)
this.thingClusters.Remove(keyToRemove);
}
public List GetAllInstances(Neuron nucleus) {
List allInstances = new();
int ix = -1;
//foreach (Nucleus myNucleus in this.nuclei) {
for (; ix < this.nuclei.Count; ix++) {
if (this.nuclei[ix] == nucleus)
break;
}
if (ix == this.nuclei.Count)
return allInstances;
foreach (Cluster instance in this.instances) {
List otherNuclei = instance.nuclei;
if (otherNuclei[ix] is Neuron otherNeuron)
allInstances.Add(otherNeuron);
}
return allInstances;
}
// public void Backpropagation(Func Observer, float target, float learningRate) {
// foreach (Nucleus nucleus in this.instances[0].nuclei) {
// if (nucleus is not Neuron neuron)
// continue;
// foreach (Synapse synapse in neuron.synapses) {
// List allSynapseNeurons = GetAllInstances(synapse.neuron);
// Vector3 dSSRdW = Vector3.zero;
// for (int clusterIx = 0; clusterIx < this.instances.Length; clusterIx++) {
// Cluster clusterInstance = this.instances[clusterIx];
// Neuron neuronInstance = allSynapseNeurons[clusterIx];
// // Simple case, without receivers...
// dSSRdW += (Vector3)(-2 * (Observer(clusterInstance) - target) * neuronInstance.activation);
// }
// synapse.weight += learningRate * dSSRdW.magnitude;
// }
// }
// }
#endregion ClusterArray
///
/// This gives the order in which nuclei should be computed when a nucleus is updated
///
[NonSerialized]
private Dictionary> _computeOrders;
///
/// This gives the order in which nuclei should be computed when a nucleus is updated
///
public Dictionary> computeOrders {
get {
if (_computeOrders == null || _computeOrders.Count == 0) {
_computeOrders = new();
foreach (Nucleus nucleus in this.nuclei)
_computeOrders[nucleus] = TopologicalSort(nucleus);
}
return _computeOrders;
}
}
///
/// Refresh the order in which neurons should be computed
///
public void RefreshComputeOrders() {
this._computeOrders = null;
}
private List TopologicalSort(Nucleus startNode) {
Dictionary inDegree = new();
//HashSet visited = new();
// Calculate in-degrees for all nodes reachable from the start node
Queue queue = new();
queue.Enqueue(startNode);
//visited.Add(startNode);
inDegree[startNode] = 0;
while (queue.Count > 0) {
Nucleus current = queue.Dequeue();
List 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 sortedOrder = new();
while (queue.Count > 0) {
Nucleus current = queue.Dequeue();
sortedOrder.Add(current); // Process the node
List 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;
}
///
/// The first nucleus in a cluster is the default output
///
public virtual Neuron defaultOutput {//=> this.nuclei[0] as Nucleus;
get {
if (this.nuclei != null && this.nuclei.Count > 0)
return this.nuclei[0] as Neuron;
return null;
}
}
///
/// The neurons without outgoing connections
///
/// These neurons can potentially be connected to neurons in other clusters
[NonSerialized]
protected List _outputs = null;
///
/// The neurons without outgoing connections
///
/// These neurons can potentially be connected to neurons in other clusters
public List outputs {
get {
if (this._outputs == null || this._outputs.Count == 0) {
this._outputs = new();
if (this.nuclei == null)
return this._outputs;
foreach (Nucleus nucleus in this.nuclei) {
if (nucleus is Neuron neuron && neuron.receivers.Count == 0)
this._outputs.Add(neuron);
}
}
return this._outputs;
}
}
///
/// Reset the list of outputs such that they will be re-determined
///
public void RefreshOutputs() {
this._outputs = null;
}
///
/// Try to find a nucleus in this cluster
///
/// The name of the nucleus to find
/// The found nucleus or null if it is not found
/// True when the nucleus is found, false otherwise
public bool TryGetNucleus(string nucleusName, out Nucleus foundNucleus) {
foreach (Nucleus receptor in this.nuclei) {
if (receptor is Nucleus nucleus)
if (nucleus.name == nucleusName) {
// if (nucleus is Cluster cluster)
// cluster.CheckInstances();
foundNucleus = nucleus;
return true;
}
}
foundNucleus = null;
return false;
}
///
/// Get a neuron in this cluster
///
/// The name of the neuron to find
/// The found neuron or null when it is not found
public Neuron GetNeuron(string neuronName) {
if (this.nuclei == null)
return null;
foreach (Nucleus nucleus in this.nuclei) {
if (nucleus is Neuron neuron && neuron.name == neuronName)
return neuron;
}
return null;
}
///
/// Get a subcluster in this cluster
///
/// The name of the cluster to find
/// The found cluster or null when it is not found
public Cluster GetCluster(string clusterName) {
int dotPosition = clusterName.IndexOf('.');
if (dotPosition >= 0) {
string clusterBaseName = clusterName[..dotPosition];
string clusterName0 = clusterBaseName + ": 0";
foreach (Nucleus nucleus in this.nuclei) {
if (nucleus is Cluster cluster) {
if (cluster.name == clusterBaseName || cluster.name == clusterName0) {
// cluster.CheckInstances();
string subNucleusName = clusterName[(dotPosition + 1)..];
return cluster.GetCluster(subNucleusName);
}
}
}
return null;
}
else {
string nucleusName0 = clusterName + ": 0";
foreach (Nucleus nucleus in this.nuclei) {
if (nucleus is Cluster cluster) {
if (nucleus.name == clusterName || nucleus.name == nucleusName0) {
// cluster.CheckInstances();
return cluster;
}
}
}
return null;
}
}
///
/// Get a neuron in an instance of a multi-cluster
///
/// The id of the thing mapped to the cluster instance
/// The name of the neuron to find
/// The name of the thing mapped to the cluster instance
/// The found neuron or null when it is not found
/// The cluster instance mapped to the thing will be neuron.parent if a neuron is found.
public Neuron GetNeuron(int thingId, string neuronName, string thingName = null) {
if (this.instances == null || this.instances.Length <= 1)
return this.GetNeuron(neuronName);
// See if we are already using a cluster for thingId
thingClusters ??= new();
if (thingClusters.TryGetValue(thingId, out Cluster cluster))
return cluster.GetNeuron(neuronName);
// Find the cluster with the lowest value neuron
Neuron lowestNeuron = null;
foreach (Cluster sibling in this.instances) {
Neuron neuron = sibling.GetNeuron(neuronName);
if (lowestNeuron == null || neuron.outputMagnitude < lowestNeuron.outputMagnitude)
lowestNeuron = neuron;
}
Cluster selectedCluster = lowestNeuron.parent;
RemoveThingCluster(selectedCluster);
selectedCluster.name = baseName + ": " + thingName;
thingClusters[thingId] = selectedCluster;
return lowestNeuron;
}
///
/// Delete a nucleus from this clsuter
///
/// The nucleus to delete
/// True if a nucleus was deleted, false if the nucleus could not be found
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;
}
public static bool EqualStructure(Cluster cluster1, Cluster cluster2) {
int n1 = cluster1.nuclei.Count;
int n2 = cluster2.nuclei.Count;
if (n1 != n2)
return false;
for (int i = 0; i < cluster1.nuclei.Count; i++) {
if (EqualStructure(cluster1.nuclei[i], cluster2.nuclei[i]) == false)
return false;
}
return true;
}
public bool CopyWeightsFrom(Cluster source) {
int thisNucleiCount = this.nuclei.Count;
int sourceNucleiCount = source.nuclei.Count;
if (thisNucleiCount != sourceNucleiCount)
return false;
for (int i = 0; i < this.nuclei.Count; i++) {
if (this.nuclei[i] is Neuron thisNeuron &&
source.nuclei[i] is Neuron sourceNeuron) {
if (thisNeuron.CopyWeightsFrom(sourceNeuron) == false)
return false;
}
}
return true;
}
public void ProcessWeightsFrom(Cluster source, Func processor) {
if (processor is null)
throw new ArgumentNullException(nameof(processor));
if (source is null)
throw new ArgumentNullException(nameof(source));
int thisNucleiCount = this.nuclei.Count;
int sourceNucleiCount = source.nuclei.Count;
if (thisNucleiCount != sourceNucleiCount)
throw new ArgumentException("Lists must have the same length.", nameof(source));
for (int i = 0; i < thisNucleiCount; i++) {
if (this.nuclei[i] is Neuron thisNeuron &&
source.nuclei[i] is Neuron sourceNeuron) {
thisNeuron.ProcessWeightsFrom(sourceNeuron, processor);
}
}
}
#region Receivers
///
/// Collect all receiving nuclei of signals from this cluster
///
/// Ensure that a receiver is only listed once in the result
/// The list of receivers
public virtual List CollectReceivers(bool removeDuplicates = false) {
List 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;
}
///
/// Collect all synapses of senders in another cluster of signals to this cluster
///
/// The other cluster with sending neurons
/// A list of synapses to the neurons in the other clusters
public List CollectSynapsesTo(Cluster otherCluster) {
List 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;
}
#endregion Receivers
#region Update
///
/// Update the state of the nucleus and all nuclei receiving from it
///
/// The nucleus to start updating
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 computeOrder = this.computeOrders[startNucleus];
foreach (Nucleus nucleus in computeOrder) {
if (nucleus is Cluster)
continue;
nucleus.UpdateStateIsolated();
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);
}
}
}
}
}
/// \copydoc NanoBrain::Nucleus::UpdateStateIsolated
public override void UpdateStateIsolated() {
throw new Exception("Cluster should not be updated!");
}
#endregion Update
///
/// Recalculate derived properties
///
/// This can be used to recalculate derived properties after the set of nuclei has been changed
public void Refresh() {
Cleanup();
// This should not be needed, but somehow somewhere the parent is changed...
foreach (Nucleus nucleus in this.nuclei) {
nucleus.parent = this;
}
RefreshOutputs();
RefreshComputeOrders();
}
public void Cleanup() {
foreach (Nucleus nucleus in this.nuclei.ToArray()) {
if (nucleus is not Cluster cluster)
continue;
List receivers = cluster.CollectReceivers();
//Debug.Log($"cluster receiver count = {receivers.Count}");
if (receivers.Count == 0)
this.nuclei.Remove(nucleus);
}
}
#region Serialization
public void Export(string path) {
ClusterData data = new(this); //this.ToJSON();
string json = JsonUtility.ToJson(data, prettyPrint: true);
// Debug.Log($"Exporting json to {path}");
File.WriteAllText(path, json);
}
public static ClusterData Import(string path) {
string json = File.ReadAllText(path);
// Debug.Log($"Importing json from {path}");
return JsonUtility.FromJson(json);
}
#endregion
}
[Serializable]
public class ClusterData {
public string name;
public List neurons = new();
public List clusters = new();
public ClusterData(Cluster cluster) {
this.name = cluster.name;
foreach (Nucleus nucleus in cluster.nuclei) {
if (nucleus is Neuron neuron) {
NeuronData neuronData = new(neuron);
this.neurons.Add(neuronData);
foreach (Synapse synapse in neuron.synapses) {
if (synapse.neuron.parent.name != cluster.name) {
ExternalClusterData clusterData = new(synapse.neuron.parent);
if (GetCluster(clusterData.name) == null) {
//if (this.clusters.Find(data => data.name == clusterData.name) == null) {
this.clusters.Add(clusterData);
//Debug.Log("Add cluster");
}
}
}
}
}
}
public ExternalClusterData GetCluster(string clusterName) {
foreach (ExternalClusterData cluster in this.clusters) {
if (cluster.name == clusterName)
return cluster;
}
return null;
}
}
[Serializable]
public class ExternalClusterData {
public string name;
public string prefabName;
public uint instanceCount;
public ExternalClusterData(Cluster cluster) {
this.name = cluster.name;
this.prefabName = cluster.prefab.name;
this.instanceCount = (uint)cluster.instanceCount;
}
}
}