Merge commit 'dd326823a8256f3ddb808e071d98c4aede72e410'

This commit is contained in:
Pascal Serrarens 2026-05-05 08:49:23 +02:00
commit ec2a6b7ae9
5 changed files with 484 additions and 294 deletions

View File

@ -64,30 +64,22 @@ namespace NanoBrain {
public class GraphEditor : GraphView {
protected ClusterPrefab prefab;
protected Nucleus currentPrefabNucleus;
protected override Nucleus currentNucleus {
get => base.currentNucleus;
set {
base.currentNucleus = value;
this.currentPrefabNucleus = value != null ? this.prefab.GetNucleus(value.name) : null;
}
}
public GraphEditor(ClusterPrefab prefab) : base(prefab.output.parent) {
this.prefab = prefab;
// In a Prefab editor, no instance exists but we need it for the ClusterViewer.
// So we create a temporary instance
Cluster cluster = new(prefab);
this.currentCluster = cluster;
Button addButton = new(() => OnAddClusterOutput()) {
text = "Add"
};
topMenuContainer?.Add(addButton);
Add(topMenuContainer);
}
void OnAddClusterOutput() {
Nucleus newOutput = new Neuron(this.prefab, "New Output");
this.prefab.RefreshOutputs();
// outputsPopup.choices = this.prefab.outputs.Select(output => output.name).ToList();
// outputsPopup.value = newOutput.name;
this.currentNucleus = newOutput;
this.currentCluster = new(prefab);
}
public void SetGraph(GameObject gameObject, VisualElement inspectorContainer) {
@ -151,69 +143,76 @@ namespace NanoBrain {
if (serializedObject == null || serializedObject.targetObject == null)
return;
if (this.currentNucleus == null)
return;
serializedObject.Update();
GUIStyle headerStyle = new(EditorStyles.boldLabel) {
alignment = TextAnchor.MiddleLeft,
margin = new RectOffset(10, 0, 4, 4)
};
GUIStyle boldTextFieldStyle = new(EditorStyles.textField) {
fontStyle = FontStyle.Bold
};
// Nucleus type
string nucleusType = this.currentNucleus.GetType().Name;
GUILayout.Label(nucleusType, headerStyle);
// Nucleus name
if (this.currentNucleus.parent is Cluster parentCluster) {
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button(this.currentNucleus.parent.name))
OnClusterClick(parentCluster);
EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.TextField(this.currentNucleus.name, boldTextFieldStyle);
EditorGUI.EndDisabledGroup();
if (GUILayout.Button("Reimport"))
ReimportCluster(parentCluster);
EditorGUILayout.EndHorizontal();
if (this.currentNucleus == null) {
OutputsInspector(ref anythingChanged);
return;
}
else {
string newName = EditorGUILayout.TextField(this.currentNucleus.name, boldTextFieldStyle);
if (newName != this.currentNucleus.name) {
this.currentNucleus.name = newName;
this.prefab.RefreshOutputs();
// outputsPopup.choices = this.prefab.outputs.Select(output => output.name).ToList();
anythingChanged = true;
}
}
GUIStyle headerStyle = new(EditorStyles.boldLabel) {
alignment = TextAnchor.MiddleLeft,
margin = new RectOffset(10, 0, 4, 4)
};
// Nucleus type
string nucleusType = this.currentNucleus.GetType().Name;
GUILayout.Label(nucleusType, headerStyle);
// Current output value
if (Application.isPlaying) {
if (currentNucleus is Neuron currentNeuron1) {
GUIContent nameLabel = new("Output", currentNeuron1.outputValue.ToString());
EditorGUILayout.FloatField(nameLabel, currentNeuron1.outputMagnitude);
// Nucleus name
Cluster cluster = this.currentPrefabNucleus as Cluster;
if (cluster != null) {
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button(this.currentNucleus.parent.name))
OnClusterClick(cluster);
EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.TextField(this.currentNucleus.name, boldTextFieldStyle);
EditorGUI.EndDisabledGroup();
if (GUILayout.Button("Reimport"))
ReimportCluster(cluster);
EditorGUILayout.EndHorizontal();
}
else {
string newName = EditorGUILayout.TextField(this.currentNucleus.name, boldTextFieldStyle);
if (newName != this.currentNucleus.name) {
Nucleus prefabNucleus = this.prefab.GetNucleus(this.currentNucleus.name);
prefabNucleus.name = newName;
// This changes it in the temporary cluster instance
this.currentNucleus.name = newName;
this.prefab.RefreshOutputs();
// outputsPopup.choices = this.prefab.outputs.Select(output => output.name).ToList();
anythingChanged = true;
}
}
// Current output value
if (Application.isPlaying) {
if (currentNucleus is Neuron currentNeuron1) {
GUIContent nameLabel = new("Output", currentNeuron1.outputValue.ToString());
EditorGUILayout.FloatField(nameLabel, currentNeuron1.outputMagnitude);
}
else
EditorGUILayout.LabelField(" ");
}
else
EditorGUILayout.LabelField(" ");
// Memory cell
if (this.currentNucleus is MemoryCell memory)
MemoryCellInspector(memory, ref anythingChanged);
// Cluster
else if (cluster != null)
ClusterInspector(cluster, ref anythingChanged);
// Other
else
NucleusInspector(this.currentNucleus, ref anythingChanged);
if (GUILayout.Button("Delete"))
DeleteNucleus(this.currentNucleus);
}
else
EditorGUILayout.LabelField(" ");
// Memory cell
if (this.currentNucleus is MemoryCell memory)
MemoryCellInspector(memory, ref anythingChanged);
// Cluster
else if (this.currentNucleus is Cluster cluster)
ClusterInspector(cluster, ref anythingChanged);
// Other
else
NucleusInspector(this.currentNucleus, ref anythingChanged);
if (GUILayout.Button("Delete"))
DeleteNucleus(this.currentNucleus);
serializedObject.ApplyModifiedProperties();
if (anythingChanged) {
@ -222,6 +221,24 @@ namespace NanoBrain {
}
}
protected void OutputsInspector(ref bool anythingChanged) {
GUIStyle headerStyle = new(EditorStyles.boldLabel) {
alignment = TextAnchor.MiddleLeft,
margin = new RectOffset(10, 0, 4, 4)
};
GUILayout.Label("Outputs", headerStyle);
bool connecting = GUILayout.Button("Add Output Neuron");
if (connecting) {
Nucleus newOutput = new Neuron(this.prefab, "New Output");
// Regenerate the temporary clsuter instance
// See also the constructor
this.currentCluster = new(this.prefab);
this.currentNucleus = newOutput;
this.selectedOutput = this.currentNucleus;
}
}
protected void MemoryCellInspector(MemoryCell memoryCell, ref bool anythingChanged) {
memoryCell.staticMemory = EditorGUILayout.Toggle("Static Memory", memoryCell.staticMemory);
NucleusInspector(memoryCell, ref anythingChanged);
@ -241,7 +258,6 @@ namespace NanoBrain {
if (GUILayout.Button("Add")) {
Undo.RecordObject(prefabAsset, "Array add " + prefabAsset.name);
//cluster.AddInstance(this.prefab);
cluster.AddInstance();
anythingChanged = true;
}
@ -284,14 +300,17 @@ namespace NanoBrain {
EditorGUIUtility.labelWidth = 100;
Vector3 newBias = EditorGUILayout.Vector3Field("Bias", this.currentNucleus.bias);
anythingChanged |= newBias != this.currentNucleus.bias;
this.currentNucleus.bias = newBias;
if (newBias != this.currentPrefabNucleus.bias) {
anythingChanged |= newBias != this.currentNucleus.bias;
this.currentPrefabNucleus.bias = newBias;
this.currentNucleus.bias = newBias;
}
EditorGUIUtility.labelWidth = previousLabelWidth;
Nucleus[] array = null;
int elementIx = -1;
if (this.currentNucleus.synapses.Count > 0) {
Synapse[] synapses = this.currentNucleus.synapses.ToArray();
if (this.currentPrefabNucleus.synapses.Count > 0) {
Synapse[] synapses = this.currentPrefabNucleus.synapses.ToArray();
foreach (Synapse synapse in synapses) {
if (synapse.neuron == null)
continue;
@ -330,24 +349,20 @@ namespace NanoBrain {
EditorGUILayout.BeginHorizontal();
if (synapse.neuron.clusterPrefab != this.currentNucleus.clusterPrefab) {
// If it is a cluster
// If it is a different cluster
GUIStyle labelStyle = new(GUI.skin.label);
float labelWidth = 200;
if (synapse.neuron.clusterPrefab != null) {
labelWidth = labelStyle.CalcSize(new GUIContent($"{synapse.neuron.clusterPrefab.name}.")).x;
GUILayout.Label($"{synapse.neuron.clusterPrefab.name}", GUILayout.Width(labelWidth));
}
//string[] options = synapse.neuron.parent.clusterNuclei.Select(n => n.name).ToArray();
string[] options = synapse.neuron.clusterPrefab.nuclei.Select(n => n.name).ToArray();
int selectedIndex = System.Array.IndexOf(options, synapse.neuron.name);
int newIndex = EditorGUILayout.Popup(selectedIndex, options);
// if (newIndex != selectedIndex && synapse.neuron.clusterPrefab.nuclei[newIndex] is Neuron newNeuron)
// ChangeSynapse(synapse, newNeuron);
if (newIndex != selectedIndex) {
// It shall be ensured that the parent.clusterNuclei and
// clusterPrefab.nuclei contain the same neurons in the same order....
Nucleus selectedNucleus = synapse.neuron.parent.clusterNuclei[newIndex];
Neuron newNeuron = selectedNucleus as Neuron;
// Nucleus selectedNucleus = synapse.neuron.parent.clusterNuclei[newIndex];
// Neuron newNeuron = selectedNucleus as Neuron;
Neuron newNeuron = synapse.neuron.clusterPrefab.nuclei[newIndex] as Neuron;
ChangeSynapse(synapse, newNeuron);
}
}
@ -367,14 +382,6 @@ namespace NanoBrain {
EditorGUI.indentLevel++;
float newWeight = EditorGUILayout.FloatField("Weight", synapse.weight);
if (newWeight != synapse.weight) {
// if (synapse.neuron.parent is IReceptor receptor) {
// Nucleus[] receptorArray = receptor.nucleiArray;
// foreach (Synapse s in this.currentNucleus.synapses) {
// if (s.neuron.parent is IReceptor r && r.nucleiArray == receptorArray)
// s.weight = newWeight;
// }
// }
// else
synapse.weight = newWeight;
anythingChanged = true;
}
@ -430,15 +437,6 @@ namespace NanoBrain {
case Nucleus.Type.Cluster:
AddClusterInput(nucleus);
break;
// case Nucleus.Type.Receptor:
// AddReceptorInput(nucleus);
// break;
// case Nucleus.Type.ClusterReceptor:
// AddClusterReceptorInput(nucleus);
// break;
// case Nucleus.Type.ClusterArray:
// AddClusterArrayInput(nucleus);
// break;
default:
break;
}
@ -535,17 +533,15 @@ namespace NanoBrain {
}
}
}
this.prefab.nuclei.Remove(nucleus);
this.currentCluster.DeleteNucleus(nucleus);//clusterNuclei.Remove(nucleus);
// if (outputsPopup.value == nucleus.name) {
// this.prefab.RefreshOutputs();
// outputsPopup.choices = this.prefab.outputs.Select(output => output.name).ToList();
// outputsPopup.index = 0;
// }
// this.prefab.nuclei.Remove(nucleus);
// Neuron.Delete(nucleus);
this.prefab.RefreshOutputs();
Neuron.Delete(nucleus);
this.currentNucleus = this.prefab.output;
this.selectedOutput = this.currentNucleus;
}
Nucleus.Type selectedType = Nucleus.Type.None;
@ -570,7 +566,7 @@ namespace NanoBrain {
}
protected virtual void ChangeSynapse(Synapse synapse, Neuron newNucleus) {
Neuron synapseNeuron = synapse.neuron as Neuron;
Neuron synapseNeuron = synapse.neuron;
if (synapse.neuron.parent is Cluster subCluster && subCluster.prefab != this.prefab) {
// if (synapse.neuron.parent is ClusterReceptor receptor) {
// // the new nucleus is part of a (cluster) receptor,
@ -600,8 +596,8 @@ namespace NanoBrain {
// }
// else {
// it is a neuron in a subcluster
synapseNeuron.RemoveReceiver(this.currentNucleus);
newNucleus.AddReceiver(this.currentNucleus);
synapseNeuron.RemoveReceiver(this.currentPrefabNucleus);
newNucleus.AddReceiver(this.currentPrefabNucleus);
// }
}
else {

View File

@ -15,8 +15,15 @@ namespace NanoBrain {
//protected readonly ClusterPrefab prefab;
protected Cluster currentCluster;
protected SerializedObject serializedBrain;
protected Nucleus currentNucleus;
protected Nucleus _currentNucleus;
protected virtual Nucleus currentNucleus {
get => _currentNucleus;
set => _currentNucleus = value;
}
//protected Nucleus currentNucleus;
protected Nucleus selectedOutput;
// Only used when selecting a synapse to a multi-cluster
protected Nucleus selectedSynapseNeuron;
protected GameObject gameObject;
private bool expandArray = false;
@ -324,7 +331,7 @@ namespace NanoBrain {
if (nucleus is Neuron neuron)
receivers = neuron.receivers;
else if (nucleus is Cluster cluster)
receivers = cluster.CollectReceivers();
receivers = cluster.CollectReceivers(true);
else
return;
@ -354,7 +361,6 @@ namespace NanoBrain {
float margin = 10 + spacing / 2;
int row = 0;
List<Nucleus[]> drawnArrays = new();
foreach (Nucleus receiver in receivers) {
Nucleus receiverNucleus = receiver;
if (receiverNucleus == null)
@ -380,6 +386,10 @@ namespace NanoBrain {
}
protected void DrawSynapses(Nucleus nucleus, Vector3 parentPos, float size) {
if (this.selectedSynapseNeuron != null) {
DrawClusterSynapses(this.selectedSynapseNeuron, parentPos, size);
return;
}
if (nucleus == null)
return;
@ -387,15 +397,19 @@ namespace NanoBrain {
// This is used to 'scale' the output value colors of the nuclei
float maxValue = 0;
int neuronCount = 0;
List<Neuron> drawnNeurons = new();
List<string> drawnNeuronNames = new();
foreach (Synapse synapse in nucleus.synapses) {
if (synapse.neuron == null)
continue;
// Count multiple synapses to the same neuron only once
if (drawnNeurons.Contains(synapse.neuron))
string neuronName = synapse.neuron.name;
if (synapse.neuron.parent != null)
neuronName = synapse.neuron.parent.baseName + "." + neuronName;
if (drawnNeuronNames.Contains(neuronName))
continue;
drawnNeurons.Add(synapse.neuron);
drawnNeuronNames.Add(neuronName);
float value = synapse.neuron.outputMagnitude * synapse.weight;
if (value > maxValue)
@ -409,15 +423,20 @@ namespace NanoBrain {
float margin = 10 + spacing / 2;
int row = 0;
drawnNeurons = new();
//List<Neuron> drawnNeurons = new();
drawnNeuronNames = new();
foreach (Synapse synapse in nucleus.synapses) {
if (synapse.neuron is null)
continue;
// Draw multiple synapses to the same neuron only once
if (drawnNeurons.Contains(synapse.neuron))
string neuronName = synapse.neuron.name;
if (synapse.neuron.parent != null)
neuronName = synapse.neuron.parent.baseName + "." + neuronName;
if (drawnNeuronNames.Contains(neuronName))
continue;
drawnNeurons.Add(synapse.neuron);
drawnNeuronNames.Add(neuronName);
Vector3 pos = new(250, margin + row * spacing, 0.0f);
DrawEdge(parentPos, pos);
@ -435,6 +454,50 @@ namespace NanoBrain {
}
}
protected void DrawClusterSynapses(Nucleus nucleus, Vector3 parentPos, float size) {
if (nucleus == null || nucleus.parent == null || nucleus.parent.siblingClusters == null)
return;
// Hack to disable showing labels
expandArray = true;
float maxValue = 0;
foreach (Cluster sibling in nucleus.parent.siblingClusters) {
Neuron siblingNeuron = sibling.GetNucleus(nucleus.name) as Neuron;
float value = siblingNeuron.outputMagnitude; // no need to add weight as they are all the same
if (value > maxValue)
maxValue = value;
}
// Determine the spacing of the nuclei in the layer
float spacing = 400f / nucleus.parent.instanceCount;
float margin = 10 + spacing / 2;
int row = 0;
foreach (Cluster sibling in nucleus.parent.siblingClusters) {
Neuron siblingNeuron = sibling.GetNucleus(nucleus.name) as Neuron;
Vector3 position = new(250, margin + row * spacing, 0.0f);
DrawEdge(parentPos, position);
Color color = Color.black;
if (Application.isPlaying) {
if (maxValue == 0 || !float.IsFinite(maxValue))
maxValue = 1;
float brightness = siblingNeuron.outputMagnitude / maxValue;
color = new Color(brightness, brightness, brightness, 1f);
} DrawNucleus(siblingNeuron, position, size, color);
GUIStyle style = new(EditorStyles.label) {
alignment = TextAnchor.UpperCenter,
normal = { textColor = Color.white },
fontStyle = FontStyle.Bold,
};
Vector3 labelPos = position - Vector3.down * (size + 5); // below neuron
string name = $"{sibling.baseName}.{nucleus.name}";
Handles.Label(labelPos, name, style);
row++;
}
expandArray = false;
}
protected void DrawOutputs(Vector2 parentPos, float size) {
// Determine the maximum value in this layer
// This is used to 'scale' the output value colors of the nuclei
@ -534,7 +597,7 @@ namespace NanoBrain {
else if (nucleus is Cluster cluster)
DrawCluster(cluster, position, color, size);
if (expandArray == false || nucleus != currentNucleus) {
if (expandArray == false) {// || nucleus != currentNucleus) {
// put name below nucleus
Vector3 labelPos = position - Vector3.down * (size + 5); // below neuron
style.alignment = TextAnchor.UpperCenter;
@ -728,24 +791,33 @@ namespace NanoBrain {
protected void OnNeuronClick(Nucleus nucleus) {
if (nucleus == this.currentNucleus) {
if (Application.isPlaying) {
if (nucleus is Cluster)
expandArray = !expandArray;
else
expandArray = false;
}
else {
if (nucleus is Cluster cluster)
OnClusterClick(cluster);
}
this.selectedSynapseNeuron = null;
// if (Application.isPlaying) {
// if (nucleus is Cluster)
// expandArray = !expandArray;
// else
// expandArray = false;
// }
// else {
if (nucleus is Cluster cluster)
OnClusterClick(cluster);
// }
}
else if (nucleus.parent != null && this.currentNucleus != null && nucleus.parent != this.currentNucleus.parent) {
// We go to a different cluster
if (Application.isPlaying) {
this.currentNucleus = nucleus;
if (this.currentNucleus is Neuron neuron && neuron.receivers.Count == 0)
this.selectedOutput = this.currentNucleus;
expandArray = false;
if (this.selectedSynapseNeuron == null && nucleus.parent.instanceCount > 1) {
this.selectedSynapseNeuron = nucleus;
expandArray = false;
}
else {
this.currentNucleus = nucleus;
if (this.currentNucleus is Neuron neuron && neuron.receivers.Count == 0)
this.selectedOutput = this.currentNucleus;
this.selectedSynapseNeuron = null;
expandArray = false;
}
}
else {
// select the cluster, not the neuron in the cluster

View File

@ -32,9 +32,11 @@ namespace NanoBrain {
}
// This should not be serialized
[SerializeReference]
//[SerializeReference]
[NonSerialized]
public Cluster[] siblingClusters;
// This serialization should be enough
[SerializeField]
public int instanceCount = 1;
public Dictionary<int, Cluster> thingClusters = new();
@ -93,6 +95,8 @@ namespace NanoBrain {
nucleus.ShallowCloneTo(this);
}
Nucleus[] clonedNuclei = this.clusterNuclei.ToArray();
// foreach (Nucleus n in clonedNuclei)
// n.name += "(c)";
// Now clone the connections
for (int nucleusIx = 0; nucleusIx < prefabNuclei.Length; nucleusIx++) {
@ -106,19 +110,15 @@ namespace NanoBrain {
foreach (Synapse prefabSynapse in prefabNeuron.synapses) {
Neuron synapseNeuron = prefabSynapse.neuron;
if (synapseNeuron.parent is not null && synapseNeuron.clusterPrefab != this.clusterPrefab) {
if (synapseNeuron.clusterPrefab != null && synapseNeuron.clusterPrefab != this.prefab) {
// Neuron is in another cluster, find the cloned cluster first
Cluster prefabCluster = synapseNeuron.parent;
int clusterIx = GetNucleusIndex(prefabNuclei, prefabCluster);
if (clusterIx < 0)
// Could not find the cluster in the prefab
continue;
if (clonedNuclei[clusterIx] is not Cluster clonedCluster)
// Could not find the cloned cluster
ClusterPrefab prefabCluster = synapseNeuron.clusterPrefab;
Cluster clonedCluster = this.clusterNuclei.Find(n => n.name == prefabCluster.name) as Cluster;
if (clonedCluster == null)
continue;
// Now find the neuron in that cloned cluster
int neuronIx = GetNucleusIndex(prefabCluster.prefab.nuclei, prefabSynapse.neuron);
int neuronIx = GetNucleusIndex(prefabCluster.nuclei, prefabSynapse.neuron.name);
if (neuronIx < 0)
// Could not find the neuron in the prefab cluster
continue;
@ -127,6 +127,7 @@ namespace NanoBrain {
continue;
clonedSender.AddReceiver(clonedNeuron, prefabSynapse.weight);
//Debug.Log($"Add synapse {clonedCluster.name}.{clonedSender.name} -> {clonedNeuron.name} [{clonedSender.receivers.Count}]");
}
else {
int ix = GetNucleusIndex(prefabNuclei, prefabSynapse.neuron);
@ -137,6 +138,7 @@ namespace NanoBrain {
// Copy the receivers which will also create the synapse
clonedSender.AddReceiver(clonedNeuron, prefabSynapse.weight);
// Debug.Log($"Add synapse {clonedSender.name} -> {clonedNeuron.name}");
}
}
@ -162,99 +164,157 @@ namespace NanoBrain {
// clonedNeuron.AddReceiver(clonedReceiver, weight);
// }
}
/*
// Copy the siblings for clusters
for (int nucleusIx = 0; nucleusIx < prefabNuclei.Length; nucleusIx++) {
Nucleus prefabNucleus = prefabNuclei[nucleusIx];
if (prefabNucleus is not Cluster prefabCluster)
continue;
if (prefabCluster.siblingClusters == null || prefabCluster.siblingClusters.Length == 0)
continue;
if (Application.isPlaying) {
// Only create cluster siblings at runtime
foreach (Nucleus clonedNucleus in clonedNuclei) {
if (clonedNucleus is not Cluster clonedCluster)
continue;
Cluster clonedNucleus = clonedNuclei[nucleusIx] as Cluster;
if (prefabCluster == prefabCluster.siblingClusters[0]) {
// We clone the array only for the first entry
//NucleusArray clonedArray = new(prefabReceptor.nucleiArray.Length);
Cluster[] clonedArray = new Cluster[prefabCluster.siblingClusters.Length];
int arrayIx = 0;
foreach (Cluster prefabArrayNucleus in prefabCluster.siblingClusters) {
int arrayNucleusIx = GetNucleusIndex(prefabNuclei, prefabArrayNucleus);
if (arrayNucleusIx >= 0) {
Cluster clonedArrayNucleus = clonedNuclei[arrayNucleusIx] as Cluster;
clonedArray[arrayIx] = clonedArrayNucleus;
}
else {
Debug.LogError($" Could not find prefab nucleus {prefabNucleus.name} in the clones");
}
arrayIx++;
}
clonedNucleus.siblingClusters = clonedArray;
}
else {
// The others will refer to the array created for the first nucleus in the array
int firstNucleusIx = GetNucleusIndex(prefabNuclei, prefabCluster.siblingClusters[0]);
Cluster clonedFirstNucleus = clonedNuclei[firstNucleusIx] as Cluster;
clonedNucleus.siblingClusters = clonedFirstNucleus.siblingClusters;
}
}
/*
// Collect the subclusters
List<Cluster> subClusters = new();
foreach (Nucleus nucleus in prefabNuclei) {
foreach (Synapse synapse in nucleus.synapses) {
Nucleus synapseNucleus = synapse.neuron;
Cluster subCluster = synapseNucleus.parent;
if (subCluster is null ||
synapseNucleus.clusterPrefab == this.clusterPrefab) {
continue;
}
// if (synapseNucleus is not Cluster subCluster)
// continue;
if (subClusters.Contains(subCluster))
continue;
subClusters.Add(subCluster);
}
}
// Create the subcluster instances
foreach (Cluster subCluster in subClusters) {
for (int ix = 0; ix < subCluster.instanceCount; ix++) {
// create the new instance
Cluster clusterInstance = new(subCluster.prefab);
// connect it
foreach ((Neuron sender, Nucleus receiver) in subCluster.CollectConnections()) {
int receiverIx = GetNucleusIndex(prefabNuclei, receiver);
if (receiverIx < 0)
continue;
if (clonedNuclei[receiverIx] 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.neuron == sender) {
weight = synapse.weight;
break;
}
}
if (clusterInstance.GetNucleus(sender.name) is not Neuron clonedSender)
continue;
clonedSender.AddReceiver(clonedReceiver, weight);
}
}
}
*/
foreach (Nucleus nucleus in this.clusterNuclei) {
if (nucleus is Cluster clonedSubCluster)
RestoreAllExternalReceivers(clonedSubCluster, this.prefab, this);
List<Cluster> siblings = new() {
clonedCluster
};
for (int instanceIx = 1; instanceIx < clonedCluster.instanceCount; instanceIx++) {
// Create another sibling
Debug.Log($"create {clonedCluster.prefab.name} sibling");
Cluster sibling = new(clonedCluster.prefab, this) {
name = $"{clonedCluster.baseName}: {instanceIx}",
clusterPrefab = this.clusterPrefab,
instanceCount = this.instanceCount,
};
siblings.Add(sibling);
CopyAllExternalReceivers(clonedCluster, sibling, clonedCluster.prefab, this);
}
Cluster[] siblingClusters = siblings.ToArray();
foreach (Cluster sibling in siblings)
sibling.siblingClusters = siblingClusters;
}
}
/*
for (int nucleusIx = 0; nucleusIx < clonedNuclei.Length; nucleusIx++) {
Nucleus prefabNucleus = prefabNuclei[nucleusIx];
if (prefabNucleus is not Cluster prefabCluster)
continue;
if (prefabCluster.instanceCount <= 1)
continue;
Cluster clonedNucleus = clonedNuclei[nucleusIx] as Cluster;
if (prefabCluster == prefabCluster.siblingClusters[0]) {
// We clone the array only for the first entry
//NucleusArray clonedArray = new(prefabReceptor.nucleiArray.Length);
Cluster[] clonedArray = new Cluster[prefabCluster.siblingClusters.Length];
int arrayIx = 0;
foreach (Cluster prefabArrayNucleus in prefabCluster.siblingClusters) {
int arrayNucleusIx = GetNucleusIndex(prefabNuclei, prefabArrayNucleus);
if (arrayNucleusIx >= 0) {
Cluster clonedArrayNucleus = clonedNuclei[arrayNucleusIx] as Cluster;
clonedArray[arrayIx] = clonedArrayNucleus;
}
else {
Debug.LogError($" Could not find prefab nucleus {prefabNucleus.name} in the clones");
}
arrayIx++;
}
clonedNucleus.siblingClusters = clonedArray;
}
else {
// The others will refer to the array created for the first nucleus in the array
int firstNucleusIx = GetNucleusIndex(prefabNuclei, prefabCluster.siblingClusters[0]);
Cluster clonedFirstNucleus = clonedNuclei[firstNucleusIx] as Cluster;
clonedNucleus.siblingClusters = clonedFirstNucleus.siblingClusters;
}
}
}
/*
// Collect the subclusters
List<Cluster> subClusters = new();
foreach (Nucleus nucleus in prefabNuclei) {
foreach (Synapse synapse in nucleus.synapses) {
Nucleus synapseNucleus = synapse.neuron;
Cluster subCluster = synapseNucleus.parent;
if (subCluster is null ||
synapseNucleus.clusterPrefab == this.clusterPrefab) {
continue;
}
// if (synapseNucleus is not Cluster subCluster)
// continue;
if (subClusters.Contains(subCluster))
continue;
subClusters.Add(subCluster);
}
}
// Create the subcluster instances
foreach (Cluster subCluster in subClusters) {
for (int ix = 0; ix < subCluster.instanceCount; ix++) {
// create the new instance
Cluster clusterInstance = new(subCluster.prefab);
// connect it
foreach ((Neuron sender, Nucleus receiver) in subCluster.CollectConnections()) {
int receiverIx = GetNucleusIndex(prefabNuclei, receiver);
if (receiverIx < 0)
continue;
if (clonedNuclei[receiverIx] 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.neuron == sender) {
weight = synapse.weight;
break;
}
}
if (clusterInstance.GetNucleus(sender.name) is not Neuron clonedSender)
continue;
clonedSender.AddReceiver(clonedReceiver, weight);
}
}
}
*/
// foreach (Nucleus nucleus in this.clusterNuclei) {
// if (nucleus is Cluster clonedSubCluster)
// RestoreAllExternalReceivers(clonedSubCluster, this.prefab, this);
// }
}
private void CloneSynapses(Neuron prefabNeuron, Neuron clonedNeuron) {
foreach (Synapse prefabSynapse in prefabNeuron.synapses) {
Neuron synapseNeuron = prefabSynapse.neuron;
if (synapseNeuron.clusterPrefab != null && synapseNeuron.clusterPrefab != this.prefab) {
// Neuron is in another cluster, find the cloned cluster first
ClusterPrefab prefabCluster = synapseNeuron.clusterPrefab;
Cluster clonedCluster = this.clusterNuclei.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.clusterNuclei[neuronIx] is not Neuron clonedSender)
// Could not find the neuron in the cloned cluster
continue;
clonedSender.AddReceiver(clonedNeuron, prefabSynapse.weight);
//Debug.Log($"Add synapse {clonedCluster.name}.{clonedSender.name} -> {clonedNeuron.name} [{clonedSender.receivers.Count}]");
}
else {
Neuron clonedSender = this.clusterNuclei.Find(n => n.name == prefabSynapse.neuron.name) as Neuron;
// Copy the receivers which will also create the synapse
clonedSender.AddReceiver(clonedNeuron, prefabSynapse.weight);
// Debug.Log($"Add synapse {clonedSender.name} -> {clonedNeuron.name}");
}
}
}
/// <summary>
@ -347,33 +407,29 @@ namespace NanoBrain {
Cluster clone = new(this.prefab, parent) {
name = this.name,
clusterPrefab = this.clusterPrefab,
instanceCount = this.instanceCount,
};
// Somehow siblingClusters should be cloned too. Believe I do this in ClonePrefab right now.
return clone;
}
private static void RestoreAllExternalReceivers(Cluster clonedCluster, ClusterPrefab prefabParent, Cluster clonedParent) {
int clonedClusterIx = GetNucleusIndex(clonedParent.clusterNuclei, clonedCluster);
if (prefabParent.nuclei[clonedClusterIx] is not Cluster sourceCluster)
return;
private static void CopyAllExternalReceivers(Cluster sourceCluster, Cluster sibling, ClusterPrefab prefabParent, Cluster clonedParent) {
for (int nucleusIx = 0; nucleusIx < sourceCluster.clusterNuclei.Count; nucleusIx++) {
Nucleus sourceNucleus = sourceCluster.clusterNuclei[nucleusIx];
if (sourceNucleus is not Neuron sourceNeuron)
continue;
if (clonedCluster.clusterNuclei[nucleusIx] is not Neuron clonedNeuron)
if (sibling.clusterNuclei[nucleusIx] is not Neuron clonedNeuron)
continue;
// copy the receivers (and thus synapses) from the source to the clone
// copy the receivers (and thus synapses) from the source to the sibling
foreach (Nucleus receiver in sourceNeuron.receivers) {
int ix = GetNucleusIndex(prefabParent.nuclei, receiver);
int ix = GetNucleusIndex(clonedParent.clusterNuclei, receiver);
if (ix < 0 || ix >= clonedParent.clusterNuclei.Count)
continue;
Nucleus clonedReceiver = clonedParent.clusterNuclei[ix];
// Find the synapse for the weight
float weight = 1;
foreach (Synapse synapse in receiver.synapses) {
@ -384,10 +440,11 @@ namespace NanoBrain {
}
}
clonedNeuron.AddReceiver(clonedReceiver, weight);
// Debug.Log($"external: {clonedReceiver.name} receives from {clonedNeuron.name} {clonedNeuron.GetHashCode()}");
clonedNeuron.AddReceiver(receiver, weight);
Debug.Log($"external: {receiver.name} receives from {clonedNeuron.name} {clonedNeuron.GetHashCode()}");
}
}
}
protected int GetNucleusIndex(Nucleus[] nuclei, Nucleus nucleus) {
@ -402,13 +459,25 @@ namespace NanoBrain {
int i = 0;
foreach (Nucleus nucleiElement in nuclei) {
//for (int i = 0; i < nuclei.Length; i++) {
if (nucleus == nucleiElement)
if (nucleiElement == nucleus)
return i;
i++;
}
return -1;
}
public static int GetNucleusIndex(List<Nucleus> 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;
}
#endregion Init
#region Cluster Array
@ -470,6 +539,7 @@ namespace NanoBrain {
return cluster;
Cluster selectedCluster = SelectCluster();
selectedCluster.name = baseName + ": " + thingName;
thingClusters[thingId] = selectedCluster;
return selectedCluster;
}
@ -528,10 +598,6 @@ namespace NanoBrain {
#endregion ClusterArray
//public Dictionary<string, Nucleus> nucleiDict = new();
public List<Nucleus> _inputs = null;
public virtual List<Nucleus> inputs {
get {
@ -550,22 +616,33 @@ namespace NanoBrain {
public Dictionary<Nucleus, List<Nucleus>> computeOrders = new();
private void ComputeOrders() {
foreach (Nucleus input in this._inputs)
computeOrders[input] = TopologicalSort2(input);
foreach (Nucleus nucleus in this.clusterNuclei) {
// if (nucleus is Cluster cluster) {
// List<Synapse> synapses = this.CollectSynapsesTo(cluster);
// foreach (Synapse synapse in synapses) {
// computeOrders[synapse.neuron] = TopologicalSort2(synapse.neuron);
// Debug.Log($"{this.baseName}: Order for {cluster.baseName}.{synapse.neuron.name}");
// }
// // List<Nucleus> receivers = cluster.CollectReceivers();
// // foreach (Nucleus receiver in receivers)
// // computeOrders[receiver] = TopologicalSort2(receiver);
// }
// else {
computeOrders[nucleus] = TopologicalSort2(nucleus);
Debug.Log($"{this.baseName} Order for {nucleus.name}");
// }
}
}
private List<Nucleus> TopologicalSort2(Nucleus startNode) {
Dictionary<Nucleus, int> inDegree = new();
HashSet<Nucleus> visited = new();
// Initialize in-degrees and mark all nodes as unvisited
foreach (Nucleus node in this.clusterNuclei)
inDegree[node] = 0;
//HashSet<Nucleus> visited = new();
// Calculate in-degrees for all nodes reachable from the start node
Queue<Nucleus> queue = new Queue<Nucleus>();
Queue<Nucleus> queue = new();
queue.Enqueue(startNode);
visited.Add(startNode);
//visited.Add(startNode);
inDegree[startNode] = 0;
while (queue.Count > 0) {
Nucleus current = queue.Dequeue();
@ -575,25 +652,24 @@ namespace NanoBrain {
else if (current is Cluster cluster)
receivers = cluster.CollectReceivers();
// if (current is Neuron neuron) {
foreach (Nucleus receiver in receivers) {
if (!visited.Contains(receiver)) {
visited.Add(receiver);
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 visited) {
foreach (Nucleus node in inDegree.Keys) {
if (inDegree[node] == 0)
queue.Enqueue(node);
}
List<Nucleus> sortedOrder = new List<Nucleus>();
List<Nucleus> sortedOrder = new();
while (queue.Count > 0) {
Nucleus current = queue.Dequeue();
sortedOrder.Add(current); // Process the node
@ -604,21 +680,18 @@ namespace NanoBrain {
else if (current is Cluster cluster)
receivers = cluster.CollectReceivers();
//if (current is Neuron neuron) {
foreach (Nucleus receiver in receivers) {
if (visited.Contains(receiver)) {
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.");
// if (sortedOrder.Count != visited.Count)
// throw new InvalidOperationException("Graph is not a DAG; a cycle exists.");
return sortedOrder;
}
@ -643,6 +716,9 @@ namespace NanoBrain {
return this._outputs;
}
}
public void RefreshOutputs() {
this._outputs = null;
}
public bool TryGetNucleus(string nucleusName, out Nucleus foundNucleus) {
foreach (Nucleus receptor in this.clusterNuclei) {
@ -685,18 +761,39 @@ namespace NanoBrain {
}
}
public bool DeleteNucleus(Nucleus nucleus) {
if (this.clusterNuclei.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.clusterNuclei.IndexOf(nucleus);
this.clusterNuclei.Remove(nucleus);
this.prefab.nuclei.RemoveAt(nucleusIx);
RefreshOutputs();
return true;
}
#region Receivers
public virtual List<Nucleus> CollectReceivers() {
public virtual List<Nucleus> CollectReceivers(bool removeDuplicates = false) {
List<Nucleus> receivers = new();
foreach (Nucleus outputNucleus in this.clusterNuclei) {
if (outputNucleus is not Neuron output)
continue;
// Debug.Log($"output {this.name} {outputNucleus.name}");
foreach (Nucleus receiver in output.receivers) {
// Only add receivers outside this cluster
if (receiver.clusterPrefab != this.prefab)
receivers.Add(receiver);
// Debug.Log($"output {receiver.name}");
// Only add receivers outside this cluster
if (receiver.clusterPrefab != this.prefab) {
if (removeDuplicates == false || receivers.Contains(receiver) == false)
// Debug.Log($" YES");
receivers.Add(receiver);
}
}
}
return receivers;
@ -717,6 +814,19 @@ namespace NanoBrain {
}
return connections;
}
public List<Synapse> CollectSynapsesTo(Cluster otherCluster) {
List<Synapse> collectedSynapses = new();
foreach (Nucleus nucleus in this.clusterNuclei) {
if (nucleus is not Neuron neuron)
continue;
foreach (Synapse synapse in nucleus.synapses) {
if (synapse.neuron.parent == otherCluster)
collectedSynapses.Add(synapse);
}
}
return collectedSynapses;
}
public void MoveReceivers(Cluster newCluster) {
Debug.Log($"Move receivers for {this.name} to {newCluster.name}");
@ -752,23 +862,31 @@ namespace NanoBrain {
// 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}");
Debug.LogError($"{this.name} compute orders does not contain an order for {startNucleus.name}");
return;
}
List<Nucleus> computeOrder = this.computeOrders[startNucleus];
if (startNucleus.trace)
Debug.Log($"Update from {startNucleus.name}");
//if (startNucleus.trace)
Debug.Log($"Update from {startNucleus.name}");
foreach (Nucleus nucleus in computeOrder) {
if (nucleus is not Cluster) {
nucleus.UpdateStateIsolated();
if (startNucleus.trace && nucleus is Neuron neuron)
Debug.Log($" {nucleus.name}[{nucleus.GetHashCode()}]"); // = {neuron.outputValue}");
//if (startNucleus.trace && nucleus is Neuron neuron)
Debug.Log($" {nucleus.name}");
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);
}
}
}
}
}
// continue in parent
this.parent?.UpdateFromNucleus(this);
//this.parent?.UpdateFromNucleus(this);
UpdateNuclei();
}

View File

@ -33,8 +33,10 @@ namespace NanoBrain {
public Neuron(ClusterPrefab prefab, string name) {
this.clusterPrefab = prefab;
this.name = name;
if (this.clusterPrefab != null)
if (this.clusterPrefab != null) {
this.clusterPrefab.nuclei.Add(this);
this.clusterPrefab.RefreshOutputs();
}
else
Debug.LogError("No prefab when adding neuron to prefab");
}
@ -264,15 +266,19 @@ namespace NanoBrain {
}
public static void Delete(Nucleus nucleus) {
foreach (Synapse synapse in nucleus.synapses) {
if (synapse.neuron is Neuron synapse_nucleus) {
if (synapse_nucleus.receivers.Count > 1) {
// there is another nucleus feeding into this input nucleus
synapse_nucleus.receivers.RemoveAll(r => r == nucleus);
}
else {
// No other links, delete it.
Neuron.Delete(synapse_nucleus);
if (nucleus == null)
return;
if (nucleus.synapses != null) {
foreach (Synapse synapse in nucleus.synapses) {
if (synapse.neuron is Neuron synapse_nucleus) {
if (synapse_nucleus.receivers.Count > 1) {
// there is another nucleus feeding into this input nucleus
synapse_nucleus.receivers.RemoveAll(r => r == nucleus);
}
else {
// No other links, delete it.
Neuron.Delete(synapse_nucleus);
}
}
}
}
@ -520,6 +526,8 @@ namespace NanoBrain {
public virtual void AddReceiver(Nucleus receiverToAdd, float weight = 1) {
this._receivers.Add(receiverToAdd);
receiverToAdd.AddSynapse(this, weight);
//Debug.Log($"Add synapse {this.clusterPrefab.name}.{this.name} -> {receiverToAdd.name} --- [{this.receivers.Count}]");
}
public virtual void RemoveReceiver(Nucleus receiverToRemove) {

View File

@ -29,11 +29,7 @@ namespace NanoBrain {
this.weight = weight;
}
public bool isSleeping {
get {
return this.neuron.isSleeping;
}
}
public bool isSleeping => this.neuron.isSleeping;
}
}