305 lines
9.1 KiB
C#
305 lines
9.1 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
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[] nucleiArray = 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 < nucleiArray.Length; nucleusIx++) {
|
|
IReceptor receptor = nucleiArray[nucleusIx];
|
|
IReceptor clonedSender = clonedNuclei[nucleusIx];
|
|
if (clonedSender == null)
|
|
continue;
|
|
|
|
// Copy the receivers, which will also create the synapses
|
|
foreach (INucleus receiver in receptor.receivers) {
|
|
int ix = GetNucleusIndex(nucleiArray, receiver);
|
|
if (ix < 0)
|
|
continue;
|
|
|
|
if (clonedNuclei[ix] is not INucleus clonedReceiver)
|
|
continue;
|
|
|
|
// Find the synapse for the weight
|
|
float weight = 1;
|
|
foreach (Synapse synapse in receiver.synapses) {
|
|
if (synapse.nucleus == receptor) {
|
|
weight = synapse.weight;
|
|
break;
|
|
}
|
|
}
|
|
|
|
clonedSender.AddReceiver(clonedReceiver, weight);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sort the nuclei in a correct evaluation order
|
|
private List<IReceptor> TopologicalSort(List<IReceptor> nodes) {
|
|
Dictionary<IReceptor, int> 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<IReceptor> queue = new();
|
|
foreach (IReceptor node in nodes) {
|
|
if (inDegree[node] == 0) // Nodes with no dependencies
|
|
queue.Enqueue(node);
|
|
}
|
|
|
|
List<IReceptor> 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<IReceptor> nuclei = new();
|
|
// the nuclei sorted using topological sorting
|
|
// to ensure that the cluster is computer in the right order
|
|
public List<IReceptor> sortedNuclei;
|
|
|
|
public List<INucleus> _inputs = null;
|
|
public virtual List<INucleus> 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<Synapse> _synapses = new();
|
|
public List<Synapse> 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<INucleus> _receivers = new();
|
|
public List<INucleus> 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 inputValue) {
|
|
float3 sum = inputValue; // new(0, 0, 0);
|
|
|
|
//Applying the weight factgors
|
|
foreach (Synapse synapse in this.synapses) {
|
|
sum += synapse.weight * synapse.nucleus.outputValue;
|
|
}
|
|
|
|
//this.prefab.inputs[0].UpdateState(sum);
|
|
this.inputs[0].UpdateState(sum);
|
|
|
|
UpdateResult(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
|
|
}
|