using System; using System.Collections.Generic; using System.Linq; using UnityEngine; using Unity.Mathematics; using static Unity.Mathematics.math; [Serializable] public class Cluster : INucleus { // The ScriptableObject asset from which the runtime object has been created [SerializeField] protected string _name; public virtual string name { get => _name; set => _name = value; } #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); } // public Cluster(ClusterPrefab parent, ClusterPrefab realPrefab) { // this.prefab = realPrefab; // this.name = realPrefab.name; // this.cluster = parent; // if (this.cluster != null) // this.cluster.nuclei.Add(this); // ClonePrefab(); // } private void ClonePrefab() { IReceptor[] nuclei = this.prefab.nuclei.ToArray(); // first clone the nuclei without their connections foreach (IReceptor nucleus in this.prefab.nuclei) nucleus.ShallowCloneTo(this); IReceptor[] clonedNuclei = this.nuclei.ToArray(); // Now clone the connections for (int nucleusIx = 0; nucleusIx < nuclei.Length; nucleusIx++) { IReceptor receptor = nuclei[nucleusIx]; IReceptor clonedReceptor = clonedNuclei[nucleusIx]; if (clonedReceptor == null) continue; // Copy the receivers, which will also create the synapses foreach (INucleus receiver in receptor.receivers) { int ix = GetNucleusIndex(nuclei, receiver); if (ix < 0) continue; if (clonedNuclei[ix] is not INucleus clonedReceiver) continue; // Find the synapse for the weight float weight = 1; //NucleusArray clonedNucleusArray = null; foreach (Synapse synapse in receiver.synapses) { // Find the weight for this synapse if (synapse.nucleus == receptor) weight = synapse.weight; // if (synapse.nucleus is INucleus synapseNucleus) { // if (synapseNucleus.array != null && synapseNucleus.array.nuclei.Length > 0) { // Debug.Log("Clone: Nucleus array"); // if (clonedNucleusArray == null) { // // copy the array // clonedNucleusArray = new NucleusArray(synapseNucleus.array.nuclei.Length, "array"); // for (int arrayIx = 0; arrayIx < synapseNucleus.array.nuclei.Length; arrayIx++) { // IReceptor arrayNucleus = synapseNucleus.array.nuclei[arrayIx]; // int ix2 = GetNucleusIndex(nuclei, arrayNucleus); // clonedNucleusArray.nuclei[arrayIx] = clonedNuclei[ix2]; // } // } // synapseNucleus.array = clonedNucleusArray; // } // } } clonedReceptor.AddReceiver(clonedReceiver, weight); // Nucleus clonedNucleus = clonedReceptor as Nucleus; // if (clonedNucleus is not null) { // Synapse clonedSynapse = clonedNucleus.GetSynapse(clonedReceiver); // if (clonedSynapse.nucleus is INucleus synapseNucleus) { // if (synapseNucleus.array != null && synapseNucleus.array.nuclei.Length > 0) { // Debug.Log("Clone: Nucleus array"); // if (clonedNucleusArray == null) { // // copy the array // clonedNucleusArray = new NucleusArray(synapseNucleus.array.nuclei.Length, "array"); // for (int arrayIx = 0; arrayIx < synapseNucleus.array.nuclei.Length; arrayIx++) { // IReceptor arrayNucleus = synapseNucleus.array.nuclei[arrayIx]; // int ix2 = GetNucleusIndex(nuclei, arrayNucleus); // clonedNucleusArray.nuclei[arrayIx] = clonedNuclei[ix2]; // } // } // synapseNucleus.array = clonedNucleusArray; // } // } // } } // if (receptor is INucleus nucleus) { // if (clonedSender is not INucleus clonedNucleus) { // Debug.LogError("INucleus clone is not an INucleus!"); // continue; // } // clonedNucleus.array = new NucleusArray(nucleus.array._nuclei.Length, "array"); // for (int arrayIx = 0; arrayIx < nucleus.array._nuclei.Length; arrayIx++) { // //foreach (INucleus arrayNucleus in nucleus.array.nuclei) { // IReceptor arrayNucleus = nucleus.array._nuclei[arrayIx]; // int ix = GetNucleusIndex(nuclei, arrayNucleus); // clonedNucleus.array._nuclei[arrayIx] = clonedNuclei[ix]; // } // } } for (int nucleusIx = 0; nucleusIx < nuclei.Length; nucleusIx++) { IReceptor prefabReceptor = nuclei[nucleusIx]; if (prefabReceptor is not INucleus prefabNucleus) continue; if (prefabNucleus.array == null || prefabNucleus.array.nuclei == null || prefabNucleus.array.nuclei.Length == 0) continue; INucleus clonedNucleus = clonedNuclei[nucleusIx] as INucleus; 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 (IReceptor prefabArrayNucleus in prefabNucleus.array.nuclei) { int arrayNucleusIx = GetNucleusIndex(nuclei, prefabArrayNucleus); IReceptor 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(nuclei, prefabNucleus.array.nuclei[0]); INucleus clonedFirstNucleus = clonedNuclei[firstNucleusIx] as INucleus; clonedNucleus.array = clonedFirstNucleus.array; } } } // Sort the nuclei in a correct evaluation order private List TopologicalSort(List nodes) { Dictionary inDegree = new(); foreach (IReceptor node in nodes) inDegree[node] = 0; // Initialize in-degree to zero // Calculate in-degrees foreach (IReceptor node in nodes) { foreach (INucleus receiver in node.receivers) inDegree[receiver]++; } Queue queue = new(); foreach (IReceptor node in nodes) { if (inDegree[node] == 0) // Nodes with no dependencies queue.Enqueue(node); } List sortedOrder = new(); while (queue.Count > 0) { IReceptor current = queue.Dequeue(); sortedOrder.Add(current); // Process the node foreach (INucleus 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 virtual IReceptor Clone() { Neuron clone = new(this.cluster, this.name) { array = this.array, }; foreach (Synapse synapse in this.synapses) { Synapse clonedSynapse = clone.AddSynapse(synapse.nucleus); clonedSynapse.weight = synapse.weight; } foreach (INucleus receiver in this.receivers) { clone.AddReceiver(receiver); } return clone; } public IReceptor ShallowCloneTo(Cluster parent) { Cluster clone = new(this.prefab, parent) { name = this.name, }; return clone; } private int GetNucleusIndex(IReceptor[] nucleiArray, IReceptor nucleus) { for (int i = 0; i < nucleiArray.Length; i++) { if (nucleus == nucleiArray[i]) return i; } return -1; } #endregion Init public ClusterPrefab prefab; public ClusterPrefab cluster { get; set; } public Cluster parent { get; set; } [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 (IReceptor receptor in this.nuclei) { if (receptor is INucleus nucleus) { // inputs have no incoming synapses yet. if (nucleus.synapses.Count == 0) this._inputs.Add(nucleus); } } } return this._inputs; } } public virtual INucleus output {//=> this.nuclei[0] as INucleus; get { if (this.nuclei.Count > 0) return this.nuclei[0] as INucleus; return null; } } // Not sure if this belongs here... [SerializeReference] private NucleusArray _array; public NucleusArray array { get { return _array; } set { _array = value; } } #region Synapses [SerializeField] private List _synapses = new(); public List synapses => _synapses; public Synapse AddSynapse(IReceptor sendingNucleus, float weight = 1.0f) { Synapse synapse = new(sendingNucleus, weight); this._synapses.Add(synapse); return synapse; } // Does this even exist already? public void RemoveSynapse() { } #endregion Synapses #region Receivers [SerializeReference] private List _receivers = new(); public List receivers { get { return _receivers; } set { _receivers = value; } } public virtual void AddReceiver(INucleus receivingNucleus, float weight = 1) { this._receivers.Add(receivingNucleus); receivingNucleus.AddSynapse(this, weight); } public void RemoveReceiver(INucleus receiverNucleus) { this._receivers.RemoveAll(receiver => receiver == receiverNucleus); receiverNucleus.synapses.RemoveAll(synapse => synapse.nucleus == this); } #endregion Receivers #region Runtime [NonSerialized] private int stale = 1000; public bool isSleeping => lengthsq(this.outputValue) == 0; [NonSerialized] protected float3 _outputValue; public virtual float3 outputValue { get { return _outputValue; } set { this.stale = 0; _outputValue = value; } } #region Update // public virtual void UpdateState() { // UpdateState(new float3(0, 0, 0)); // } // public void UpdateState(float3 bias) { // float3 sum = bias; // new(0, 0, 0); // //Applying the weight factors // foreach (Synapse synapse in this.synapses) { // sum += synapse.weight * synapse.nucleus.outputValue; // } // //this.inputs[0].UpdateState(sum); // this.inputs[0].UpdateStateIsolated(sum); // foreach (IReceptor receptor in this.sortedNuclei) { // if (receptor is INucleus nucleus && nucleus != this.inputs[0]) // nucleus.UpdateStateIsolated(); // } // UpdateResult(this.output.outputValue); // } public void UpdateStateIsolated() { float3 bias = new(0, 0, 0); UpdateStateIsolated(bias); } public void UpdateStateIsolated(float3 bias) { float3 sum = bias; // new(0, 0, 0); //Applying the weight factors foreach (Synapse synapse in this.synapses) { sum += synapse.weight * synapse.nucleus.outputValue; } //this.inputs[0].UpdateState(sum); this.inputs[0].UpdateStateIsolated(sum); foreach (IReceptor receptor in this.sortedNuclei) { if (receptor is INucleus nucleus && nucleus != this.inputs[0]) nucleus.UpdateStateIsolated(); } this.outputValue = this.output.outputValue; } // public virtual void UpdateResult(Vector3 result) { // // float d = Vector3.Distance(result, this.outputValue); // // if (d < 0.5f) { // // //Debug.Log($"insignificant update: {d}"); // // return; // // } // this.outputValue = result; // foreach (INucleus receiver in this.receivers) // receiver.UpdateState(); // } public void UpdateNuclei() { this.stale++; if (this.stale > 2) _outputValue = Vector3.zero; //foreach (IReceptor nucleus in this.prefab.nuclei) foreach (IReceptor nucleus in this.nuclei) nucleus.UpdateNuclei(); } #endregion Update #endregion Runtime }