diff --git a/Runtime/Scripts/Core/Cluster.cs b/Runtime/Scripts/Core/Cluster.cs index 5309887..beb2430 100644 --- a/Runtime/Scripts/Core/Cluster.cs +++ b/Runtime/Scripts/Core/Cluster.cs @@ -81,6 +81,8 @@ namespace NanoBrain { [HideInInspector] public List nuclei = new(); + #region Timed Actions + public class TimedAction { public TimedAction(string neuronName, Action action, long timestamp) { this.neuronName = neuronName; @@ -146,6 +148,8 @@ namespace NanoBrain { } } + #endregion Timed Actions + #region Init public Cluster() { @@ -368,6 +372,90 @@ namespace NanoBrain { } } + public Cluster Clone() { + Cluster clone = new() { + name = this.name, + prefab = this.prefab, + version = this.version, + parent = this.parent + }; + if (clone.parent != null) { + // Do we need to clone the parent here too???? + } + + // first clone the nuclei without their connections + foreach (Nucleus nucleus in this.nuclei) + nucleus.ShallowCloneTo(clone); + + Nucleus[] clonedNuclei = clone.nuclei.ToArray(); + // Now clone the connections + for (int nucleusIx = 0; nucleusIx < this.nuclei.Count; nucleusIx++) { + Nucleus sourceNucleus = this.nuclei[nucleusIx]; + if (sourceNucleus is not Neuron sourceNeuron) + continue; + + Nucleus clonedNucleus = clonedNuclei[nucleusIx]; + if (clonedNucleus == null || clonedNucleus is not Neuron clonedNeuron) + continue; + + foreach (Synapse sourceSynapse in sourceNeuron.synapses) { + Neuron synapseNeuron = sourceSynapse.neuron; + if (synapseNeuron.parent.prefab != null && synapseNeuron.parent.prefab != clone.prefab) { + // Neuron is in another cluster, find the cloned cluster first + Cluster sourceCluster = synapseNeuron.parent; + Cluster clonedCluster = clone.nuclei.Find(n => n.name == sourceCluster.name) as Cluster; + if (clonedCluster == null) + continue; + + // Now find the neuron in that cloned cluster + int neuronIx = GetNucleusIndex(sourceCluster.nuclei, sourceSynapse.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, sourceSynapse.weight, sourceSynapse.trainable); + //Debug.Log($"Add synapse {clonedCluster.name}.{clonedSender.name} -> {clonedNeuron.name} [{clonedSender.receivers.Count}]"); + } + else { + int ix = GetNucleusIndex(clone.prefab.cluster.nuclei, sourceSynapse.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, sourceSynapse.weight, sourceSynapse.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 = clone.parent, + instanceCount = clone.instanceCount, + }; + siblings.Add(sibling); + CopyAllExternalReceivers(clonedCluster, sibling, this); + } + Cluster[] siblingClusters = siblings.ToArray(); + foreach (Cluster sibling in siblings) + sibling.instances = siblingClusters; + } + + return clone; + } + /// \copydoc NanoBrain::Nucleus::ShallowCloneTo public override Nucleus ShallowCloneTo(Cluster parent) { // Clusters should not be cloned, but instantiated from the prefab.... @@ -1011,10 +1099,10 @@ namespace NanoBrain { #region Serialization public void Export(string path) { - ClusterData data = new(this); //this.ToJSON(); + ClusterData data = new(this); string json = JsonUtility.ToJson(data, prettyPrint: true); - // Debug.Log($"Exporting json to {path}"); + Debug.Log($"Exporting {this.name} to {path}"); File.WriteAllText(path, json); } diff --git a/Runtime/Scripts/Core/Population.cs b/Runtime/Scripts/Core/Population.cs index f854db5..a02a2ff 100644 --- a/Runtime/Scripts/Core/Population.cs +++ b/Runtime/Scripts/Core/Population.cs @@ -3,6 +3,9 @@ using UnityEngine; namespace NanoBrain { public class Population { + + # region Members + public class Member { public Member(Cluster cluster) { this.brain = cluster; @@ -21,12 +24,49 @@ namespace NanoBrain { return newMember; } - /// - /// Generational update - /// - public virtual void Update() { + #endregion Members + #region Saving + + public enum SaveMethod { + Average, + Mean, + First, + All } + public void Save(string path, SaveMethod saveMethod = SaveMethod.Average) { + Cluster cluster = null; + switch (saveMethod) { + case SaveMethod.First: + Member member = this.members[0]; + if (member == null) + return; + + cluster = member.brain; + break; + case SaveMethod.Average: + cluster = CalculateAverageCluster(); + break; + } + cluster?.Export(path); + } + + protected Cluster CalculateAverageCluster() { + if (members.Count <= 0) + return null; + + Cluster result = members[0].brain.Clone(); + for (int memberIx = 1; memberIx < members.Count; memberIx++) { + Member member = members[memberIx]; + result.ProcessWeightsFrom(member.brain, Average); + } + + return result; + } + + #endregion Saving + + #region New Generation public void NewGeneration() { foreach (Member member in this.members) { @@ -118,6 +158,8 @@ namespace NanoBrain { return selectedMembers; } + #endregion New Generation + private void LogTrainableWeights(Cluster cluster) { string s = ""; foreach (Nucleus nucleus in cluster.nuclei) { @@ -131,5 +173,7 @@ namespace NanoBrain { } Debug.Log(s); } + + public virtual void Update() { } } } \ No newline at end of file