using System; using System.Collections.Generic; using System.Linq; using UnityEngine; using Unity.Mathematics; using static Unity.Mathematics.math; [Serializable] public class Cluster : Nucleus { #region Init public Cluster(ClusterPrefab prefab, Cluster parent) { this.prefab = prefab; this.name = prefab.name; this.parent = parent; this.parent?.nuclei.Add(this); ClonePrefab(); this.sortedNuclei = TopologicalSort(this.nuclei); } public Cluster(ClusterPrefab prefab, ClusterPrefab parent = null) { this.prefab = prefab; this.name = prefab.name; this.cluster = parent; if (this.cluster != null) this.cluster.nuclei.Add(this); ClonePrefab(); this.sortedNuclei = TopologicalSort(this.nuclei); } private void ClonePrefab() { Nucleus[] prefabNuclei = this.prefab.nuclei.ToArray(); // first clone the nuclei without their connections foreach (Nucleus nucleus in this.prefab.nuclei) nucleus.ShallowCloneTo(this); Nucleus[] clonedNuclei = this.nuclei.ToArray(); // Now clone the connections for (int nucleusIx = 0; nucleusIx < prefabNuclei.Length; nucleusIx++) { Nucleus prefabNucleus = prefabNuclei[nucleusIx]; Nucleus clonedReceptor = clonedNuclei[nucleusIx]; if (clonedReceptor == null) continue; // Copy the receivers, which will also create the synapses foreach (Nucleus receiver in prefabNucleus.receivers) { 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.nucleus == prefabNucleus) { weight = synapse.weight; break; } } clonedReceptor.AddReceiver(clonedReceiver, weight); } } // Copy nucleus arrays for (int nucleusIx = 0; nucleusIx < prefabNuclei.Length; nucleusIx++) { Nucleus prefabReceptor = prefabNuclei[nucleusIx]; if (prefabReceptor is not Nucleus prefabNucleus) continue; if (prefabNucleus.array == null || prefabNucleus.array.nuclei == null || prefabNucleus.array.nuclei.Length == 0) continue; Nucleus clonedNucleus = clonedNuclei[nucleusIx] as Nucleus; if (prefabNucleus == prefabNucleus.array.nuclei[0]) { // We clone the array only for the first entry NucleusArray clonedArray = new(prefabNucleus.array.nuclei.Length, "array"); int arrayIx = 0; foreach (Nucleus prefabArrayNucleus in prefabNucleus.array.nuclei) { int arrayNucleusIx = GetNucleusIndex(prefabNuclei, prefabArrayNucleus); Nucleus clonedArrayNucleus = clonedNuclei[arrayNucleusIx]; clonedArray.nuclei[arrayIx] = clonedArrayNucleus; arrayIx++; } clonedNucleus.array = clonedArray; } else { // The others will refer to the array created for the first nucleus in the array int firstNucleusIx = GetNucleusIndex(prefabNuclei, prefabNucleus.array.nuclei[0]); Nucleus clonedFirstNucleus = clonedNuclei[firstNucleusIx] as Nucleus; clonedNucleus.array = clonedFirstNucleus.array; } } } // Sort the nuclei in a correct evaluation order private List TopologicalSort(List nodes) { Dictionary inDegree = new(); foreach (Nucleus node in nodes) inDegree[node] = 0; // Initialize in-degree to zero // Calculate in-degrees foreach (Nucleus node in nodes) { foreach (Nucleus receiver in node.receivers) inDegree[receiver]++; } Queue queue = new(); foreach (Nucleus node in nodes) { if (inDegree[node] == 0) // Nodes with no dependencies queue.Enqueue(node); } List sortedOrder = new(); while (queue.Count > 0) { Nucleus current = queue.Dequeue(); sortedOrder.Add(current); // Process the node foreach (Nucleus receiver in current.receivers) { 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() { //Neuron clone = new(this.cluster, this.name) { Neuron clone = new(this.parent, this.name) { array = this.array, }; 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; } public override Nucleus ShallowCloneTo(Cluster parent) { Cluster clone = new(this.prefab, parent) { name = this.name, }; return clone; } private int GetNucleusIndex(Nucleus[] nucleiArray, Nucleus nucleus) { for (int i = 0; i < nucleiArray.Length; i++) { if (nucleus == nucleiArray[i]) return i; } return -1; } #endregion Init public ClusterPrefab prefab; [SerializeReference] public List nuclei = new(); // the nuclei sorted using topological sorting // to ensure that the cluster is computer in the right order public List sortedNuclei; public List _inputs = null; public virtual List inputs { get { if (this._inputs == null) { this._inputs = new(); foreach (Nucleus receptor in this.nuclei) { if (receptor is Nucleus nucleus) { // inputs have no incoming synapses yet. if (nucleus.synapses.Count == 0) this._inputs.Add(nucleus); } } } return this._inputs; } } public virtual Nucleus output {//=> this.nuclei[0] as Nucleus; get { if (this.nuclei.Count > 0) return this.nuclei[0]; return 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) { foreach (Nucleus receptor in this.nuclei) { if (receptor is Nucleus nucleus) if (nucleus.name == nucleusName) return nucleus; } return null; } #region Update public override void UpdateStateIsolated() { float3 bias = new(0, 0, 0); UpdateStateIsolated(bias); } public override void UpdateStateIsolated(float3 bias) { float3 sum = bias; //Applying the weight factors foreach (Synapse synapse in this.synapses) { if (lengthsq(synapse.nucleus.outputValue) > 0) { sum += synapse.weight * synapse.nucleus.outputValue; this.stale = 0; } } this.inputs[0].UpdateStateIsolated(sum); foreach (Nucleus receptor in this.sortedNuclei) { if (receptor is Nucleus nucleus && nucleus != this.inputs[0]) nucleus.UpdateStateIsolated(); } this.outputValue = this.output.outputValue; UpdateNuclei(); } public override void UpdateNuclei() { this.stale++; if (this.stale > 5) _outputValue = Vector3.zero; foreach (Nucleus nucleus in this.nuclei) nucleus.UpdateNuclei(); } #endregion Update }