Compare commits

..

No commits in common. "da370bb7013ecf8dbf5635adc4969ad72b6103f4" and "7ef8e42e091cd5a46bf77dfbf9b1a3b3a4422752" have entirely different histories.

25 changed files with 663 additions and 871 deletions

View File

@ -1,11 +1,2 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 9197e2d322d23b5798ab4aef729815b0 guid: 9197e2d322d23b5798ab4aef729815b0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,11 +1,2 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: f05072314d39990639a2dbf99f322664 guid: f05072314d39990639a2dbf99f322664
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -64,24 +64,22 @@ namespace NanoBrain {
public class GraphEditor : GraphView { public class GraphEditor : GraphView {
protected ClusterPrefab prefab; protected ClusterPrefab prefab;
//protected Nucleus currentPrefabNucleus; protected Nucleus currentPrefabNucleus;
protected override Nucleus currentNucleus { protected override Nucleus currentNucleus {
get => base.currentNucleus; get => base.currentNucleus;
set { set {
base.currentNucleus = value; base.currentNucleus = value;
// this.currentPrefabNucleus = value != null ? this.prefab.GetNucleus(value.name) : null; this.currentPrefabNucleus = value != null ? this.prefab.GetNucleus(value.name) : null;
} }
} }
public GraphEditor(ClusterPrefab prefab) : base(prefab.cluster.defaultOutput.parent) { public GraphEditor(ClusterPrefab prefab) : base(prefab.output.parent) {
this.prefab = prefab; this.prefab = prefab;
// In a Prefab editor, no instance exists but we need it for the ClusterViewer. // In a Prefab editor, no instance exists but we need it for the ClusterViewer.
// So we create a temporary instance // So we create a temporary instance
//this.currentCluster = new(prefab); this.currentCluster = new(prefab);
this.currentCluster = prefab.cluster;
this.currentCluster.Refresh();
} }
public void SetGraph(GameObject gameObject, VisualElement inspectorContainer) { public void SetGraph(GameObject gameObject, VisualElement inspectorContainer) {
@ -89,7 +87,7 @@ namespace NanoBrain {
if (Application.isPlaying == false) if (Application.isPlaying == false)
this.serializedBrain = new SerializedObject(this.prefab); this.serializedBrain = new SerializedObject(this.prefab);
this.selectedOutput = this.currentCluster.defaultOutput; this.selectedOutput = this.currentCluster.outputs[0];
this.currentNucleus = this.selectedOutput; this.currentNucleus = this.selectedOutput;
//this.currentCluster = this.currentNucleus.parent; //this.currentCluster = this.currentNucleus.parent;
Rebuild(inspectorContainer); Rebuild(inspectorContainer);
@ -122,7 +120,7 @@ namespace NanoBrain {
// create a SerializedObject wrapper so Unity inspector controls work (and Undo) // create a SerializedObject wrapper so Unity inspector controls work (and Undo)
SerializedObject so = new(prefabAsset); SerializedObject so = new(prefabAsset);
foreach (Nucleus nucleus in this.prefab.cluster.nuclei) { foreach (Nucleus nucleus in this.prefab.nuclei) {
nucleus.Initialize(); nucleus.Initialize();
} }
@ -165,10 +163,29 @@ namespace NanoBrain {
GUILayout.Label(nucleusType, headerStyle); GUILayout.Label(nucleusType, headerStyle);
// Nucleus name // Nucleus name
string newName = EditorGUILayout.TextField(this.currentNucleus.name, boldTextFieldStyle); Cluster cluster = this.currentPrefabNucleus as Cluster;
if (newName != this.currentNucleus.name) { if (cluster != null) {
this.currentNucleus.name = newName; EditorGUILayout.BeginHorizontal();
anythingChanged = true; 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 // Current output value
@ -187,7 +204,7 @@ namespace NanoBrain {
if (this.currentNucleus is MemoryCell memory) if (this.currentNucleus is MemoryCell memory)
MemoryCellInspector(memory, ref anythingChanged); MemoryCellInspector(memory, ref anythingChanged);
// Cluster // Cluster
else if (this.currentNucleus is Cluster cluster) else if (cluster != null)
ClusterInspector(cluster, ref anythingChanged); ClusterInspector(cluster, ref anythingChanged);
// Other // Other
else else
@ -213,8 +230,10 @@ namespace NanoBrain {
bool connecting = GUILayout.Button("Add Output Neuron"); bool connecting = GUILayout.Button("Add Output Neuron");
if (connecting) { if (connecting) {
Nucleus newOutput = new Neuron(this.currentCluster, "New Output"); Nucleus newOutput = new Neuron(this.prefab, "New Output");
this.currentCluster.Refresh(); // Regenerate the temporary clsuter instance
// See also the constructor
this.currentCluster = new(this.prefab);
this.currentNucleus = newOutput; this.currentNucleus = newOutput;
this.selectedOutput = this.currentNucleus; this.selectedOutput = this.currentNucleus;
} }
@ -262,9 +281,9 @@ namespace NanoBrain {
if (breakOnWake && this.currentNucleus is Neuron currentNeuron) { if (breakOnWake && this.currentNucleus is Neuron currentNeuron) {
if (currentNeuron.isSleeping == false) if (currentNeuron.isSleeping == false)
Debug.Break(); Debug.Break();
// trace = EditorGUILayout.Toggle("Trace", trace);
// currentNeuron.trace = trace;
} }
trace = EditorGUILayout.Toggle("Trace", trace);
this.currentNucleus.trace = trace;
} }
protected void SynapsesInspector(ref bool anythingChanged) { protected void SynapsesInspector(ref bool anythingChanged) {
@ -274,30 +293,31 @@ namespace NanoBrain {
Neuron.CombinatorType newCombinator = (Neuron.CombinatorType)EditorGUILayout.EnumPopup("Combinator", neuron2.combinator); Neuron.CombinatorType newCombinator = (Neuron.CombinatorType)EditorGUILayout.EnumPopup("Combinator", neuron2.combinator);
anythingChanged |= newCombinator != neuron2.combinator; anythingChanged |= newCombinator != neuron2.combinator;
neuron2.combinator = newCombinator; neuron2.combinator = newCombinator;
EditorGUIUtility.wideMode = true;
float previousLabelWidth = EditorGUIUtility.labelWidth;
EditorGUIUtility.labelWidth = 100;
Vector3 newBias = EditorGUILayout.Vector3Field("Bias", neuron2.bias);
if (newBias != neuron2.bias) {
anythingChanged |= newBias != neuron2.bias;
neuron2.bias = newBias;
}
EditorGUIUtility.labelWidth = previousLabelWidth;
} }
EditorGUIUtility.wideMode = true;
float previousLabelWidth = EditorGUIUtility.labelWidth;
EditorGUIUtility.labelWidth = 100;
Vector3 newBias = EditorGUILayout.Vector3Field("Bias", this.currentNucleus.bias);
if (newBias != this.currentPrefabNucleus.bias) {
anythingChanged |= newBias != this.currentNucleus.bias;
this.currentPrefabNucleus.bias = newBias;
this.currentNucleus.bias = newBias;
}
EditorGUIUtility.labelWidth = previousLabelWidth;
Nucleus[] array = null; Nucleus[] array = null;
int elementIx = -1; int elementIx = -1;
if (this.currentNucleus is Neuron currentNeuron && currentNeuron.synapses.Count > 0) { if (this.currentPrefabNucleus.synapses.Count > 0) {
Synapse[] synapses = currentNeuron.synapses.ToArray(); Synapse[] synapses = this.currentPrefabNucleus.synapses.ToArray();
foreach (Synapse synapse in synapses) { foreach (Synapse synapse in synapses) {
if (synapse.neuron == null) if (synapse.neuron == null)
continue; continue;
if (array != null) { if (array != null) {
if (synapse.neuron.parent is Cluster iCluster && elementIx > 0) { if (synapse.neuron.parent is Cluster iCluster && elementIx > 0) {
int thisElementIx = Cluster.GetNucleusIndex(iCluster.nuclei, synapse.neuron); int thisElementIx = Cluster.GetNucleusIndex(iCluster.clusterNuclei, synapse.neuron);
if (thisElementIx == elementIx) if (thisElementIx == elementIx)
continue; continue;
else else
@ -312,7 +332,7 @@ namespace NanoBrain {
if (synapse.neuron.parent is Cluster iReceptor) { if (synapse.neuron.parent is Cluster iReceptor) {
array = iReceptor.siblingClusters; array = iReceptor.siblingClusters;
if (iReceptor is Cluster iCluster) if (iReceptor is Cluster iCluster)
elementIx = Cluster.GetNucleusIndex(iCluster.nuclei, synapse.neuron); elementIx = Cluster.GetNucleusIndex(iCluster.clusterNuclei, synapse.neuron);
} }
} }
@ -328,19 +348,21 @@ namespace NanoBrain {
else { else {
EditorGUILayout.BeginHorizontal(); EditorGUILayout.BeginHorizontal();
if (synapse.neuron.parent != this.currentNucleus.parent) { if (synapse.neuron.clusterPrefab != this.currentNucleus.clusterPrefab) {
// If it is a different cluster // If it is a different cluster
GUIStyle labelStyle = new(GUI.skin.label); GUIStyle labelStyle = new(GUI.skin.label);
float labelWidth = 200; float labelWidth = 200;
if (synapse.neuron.parent != null) { if (synapse.neuron.clusterPrefab != null) {
labelWidth = labelStyle.CalcSize(new GUIContent($"{synapse.neuron.parent.name}.")).x; labelWidth = labelStyle.CalcSize(new GUIContent($"{synapse.neuron.clusterPrefab.name}.")).x;
GUILayout.Label($"{synapse.neuron.parent.name}", GUILayout.Width(labelWidth)); GUILayout.Label($"{synapse.neuron.clusterPrefab.name}", GUILayout.Width(labelWidth));
} }
string[] options = synapse.neuron.parent.nuclei.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 selectedIndex = System.Array.IndexOf(options, synapse.neuron.name);
int newIndex = EditorGUILayout.Popup(selectedIndex, options); int newIndex = EditorGUILayout.Popup(selectedIndex, options);
if (newIndex != selectedIndex) { if (newIndex != selectedIndex) {
Neuron newNeuron = synapse.neuron.parent.nuclei[newIndex] 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); ChangeSynapse(synapse, newNeuron);
} }
} }
@ -348,12 +370,13 @@ namespace NanoBrain {
GUILayout.Label(synapse.neuron.name); GUILayout.Label(synapse.neuron.name);
bool disconnecting = GUILayout.Button("Disconnect", GUILayout.Width(80)); bool disconnecting = GUILayout.Button("Disconnect", GUILayout.Width(80));
if (disconnecting) { if (disconnecting && synapse.neuron is Neuron synapseNeuron) {
synapse.neuron.RemoveReceiver(this.currentNucleus); synapseNeuron.RemoveReceiver(this.currentNucleus);
this.currentCluster.Refresh(); this.prefab.GarbageCollection();
anythingChanged = true; anythingChanged = true;
} }
EditorGUILayout.EndHorizontal(); EditorGUILayout.EndHorizontal();
} }
EditorGUI.indentLevel++; EditorGUI.indentLevel++;
@ -420,14 +443,13 @@ namespace NanoBrain {
} }
protected virtual void AddNeuronInput(Nucleus nucleus) { protected virtual void AddNeuronInput(Nucleus nucleus) {
Neuron newNeuron = new(this.currentCluster, "New Neuron"); Neuron newNeuroid = new(this.prefab, "New neuron");
//Neuron newNeuroid = new(this.prefab.cluster, "New neuron"); newNeuroid.AddReceiver(nucleus);
newNeuron.AddReceiver(nucleus); this.currentNucleus = newNeuroid;
this.currentNucleus = newNeuron;
} }
protected virtual void AddMemoryCellInput(Nucleus nucleus) { protected virtual void AddMemoryCellInput(Nucleus nucleus) {
MemoryCell newMemory = new(this.prefab.cluster, "New memory cell"); MemoryCell newMemory = new(this.prefab, "New memory cell");
newMemory.AddReceiver(nucleus); newMemory.AddReceiver(nucleus);
this.currentNucleus = newMemory; this.currentNucleus = newMemory;
} }
@ -436,7 +458,7 @@ namespace NanoBrain {
ClusterPickerWindow.ShowPicker(brain => OnClusterPicked(nucleus, brain), "Select Cluster"); ClusterPickerWindow.ShowPicker(brain => OnClusterPicked(nucleus, brain), "Select Cluster");
} }
private void OnClusterPicked(Nucleus nucleus, ClusterPrefab selectedPrefab) { private void OnClusterPicked(Nucleus nucleus, ClusterPrefab selectedPrefab) {
Cluster subclusterInstance = new(selectedPrefab, this.currentCluster); Cluster subclusterInstance = new(selectedPrefab, this.prefab);
subclusterInstance.defaultOutput.AddReceiver(nucleus); subclusterInstance.defaultOutput.AddReceiver(nucleus);
} }
@ -471,12 +493,11 @@ namespace NanoBrain {
if (cluster == null) if (cluster == null)
return false; return false;
Neuron currentNeuron = this.currentNucleus as Neuron; IEnumerable<Nucleus> synapseNuclei = this.currentNucleus.synapses
IEnumerable<Nucleus> synapseNuclei = currentNeuron.synapses
.Where(synapse => synapse.neuron != null) .Where(synapse => synapse.neuron != null)
.Select(synapse => synapse.neuron); .Select(synapse => synapse.neuron);
IEnumerable<Nucleus> nuclei = cluster.cluster.nuclei IEnumerable<Nucleus> nuclei = cluster.nuclei
.Except(synapseNuclei); .Except(synapseNuclei);
IEnumerable<string> nucleiNames = nuclei IEnumerable<string> nucleiNames = nuclei
.Select(n => { .Select(n => {
@ -492,13 +513,10 @@ namespace NanoBrain {
EditorGUILayout.EndHorizontal(); EditorGUILayout.EndHorizontal();
if (connecting) { if (connecting) {
Nucleus nucleus = nuclei.ElementAt(selectedConnectNucleus); Nucleus nucleus = nuclei.ElementAt(selectedConnectNucleus);
if (nucleus is Cluster subCluster) { if (nucleus is Cluster subCluster)
subCluster.AddArrayReceiver(this.currentNucleus); subCluster.AddArrayReceiver(this.currentNucleus);
} else if (nucleus is Neuron neuron)
else if (nucleus is Neuron neuron) {
neuron.AddReceiver(this.currentNucleus); neuron.AddReceiver(this.currentNucleus);
}
this.currentCluster.Refresh();
} }
return connecting; return connecting;
} }
@ -519,10 +537,10 @@ namespace NanoBrain {
// this.prefab.nuclei.Remove(nucleus); // this.prefab.nuclei.Remove(nucleus);
// Neuron.Delete(nucleus); // Neuron.Delete(nucleus);
this.prefab.cluster.RefreshOutputs(); this.prefab.RefreshOutputs();
this.currentNucleus = this.prefab.cluster.defaultOutput; this.currentNucleus = this.prefab.output;
this.selectedOutput = this.currentNucleus; this.selectedOutput = this.currentNucleus;
} }
@ -540,6 +558,11 @@ namespace NanoBrain {
AddInput(selectedType, this.currentNucleus); AddInput(selectedType, this.currentNucleus);
} }
return connecting; return connecting;
// if (selectedType == Nucleus.Type.None)
// return false;
// AddInput(selectedType, this.currentNucleus);
// return true;
} }
protected virtual void ChangeSynapse(Synapse synapse, Neuron newNucleus) { protected virtual void ChangeSynapse(Synapse synapse, Neuron newNucleus) {
@ -573,8 +596,8 @@ namespace NanoBrain {
// } // }
// else { // else {
// it is a neuron in a subcluster // it is a neuron in a subcluster
synapseNeuron.RemoveReceiver(this.currentNucleus); synapseNeuron.RemoveReceiver(this.currentPrefabNucleus);
newNucleus.AddReceiver(this.currentNucleus); newNucleus.AddReceiver(this.currentPrefabNucleus);
// } // }
} }
else { else {
@ -583,6 +606,19 @@ namespace NanoBrain {
} }
} }
protected virtual void DisconnectNucleus(Neuron nucleus) {
if (this.currentNucleus.clusterPrefab == null)
return;
string[] names = this.currentNucleus.synapses.Select(synapse => synapse.neuron.name).ToArray();
int selectedIndex = -1;
selectedIndex = EditorGUILayout.Popup("Disconnect from", selectedIndex, names);
if (selectedIndex >= 0 && selectedIndex < this.currentNucleus.clusterPrefab.nuclei.Count) {
Synapse synapse = this.currentNucleus.synapses[selectedIndex];
Neuron synapseNeuron = synapse.neuron as Neuron;
synapseNeuron.RemoveReceiver(this.currentNucleus);
}
}
#endregion Synapses #endregion Synapses
#endregion Inspector #endregion Inspector

View File

@ -1,11 +1,2 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 1fc1fb7db9f7ad54a87d31313e7f457d guid: 1fc1fb7db9f7ad54a87d31313e7f457d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -106,8 +106,6 @@ namespace NanoBrain {
public void SetGraph(GameObject gameObject) { public void SetGraph(GameObject gameObject) {
this.gameObject = gameObject; this.gameObject = gameObject;
if (this.currentCluster == null)
return;
if (Application.isPlaying == false) if (Application.isPlaying == false)
this.serializedBrain = new SerializedObject(this.currentCluster.prefab); this.serializedBrain = new SerializedObject(this.currentCluster.prefab);
@ -130,7 +128,7 @@ namespace NanoBrain {
} }
public void OnIMGUI() { public void OnIMGUI() {
if (Application.isPlaying == false && serializedBrain != null) if (Application.isPlaying == false)
serializedBrain.Update(); serializedBrain.Update();
Handles.BeginGUI(); Handles.BeginGUI();
@ -213,8 +211,7 @@ namespace NanoBrain {
} }
private void DescendGraph(Dag.Node receiver, ref int ix, Dag dag) { private void DescendGraph(Dag.Node receiver, ref int ix, Dag dag) {
Neuron receiverNeuron = receiver.nucleus as Neuron; foreach (Synapse synapse in receiver.nucleus.synapses) {
foreach (Synapse synapse in receiverNeuron.synapses) {
Nucleus nucleus = synapse.neuron; Nucleus nucleus = synapse.neuron;
if (nucleus.parent != null && nucleus.parent != currentNucleus.parent) { if (nucleus.parent != null && nucleus.parent != currentNucleus.parent) {
nucleus = nucleus.parent; nucleus = nucleus.parent;
@ -387,9 +384,6 @@ namespace NanoBrain {
} }
protected void DrawSynapses(Nucleus nucleus, Vector3 parentPos, float size) { protected void DrawSynapses(Nucleus nucleus, Vector3 parentPos, float size) {
if (nucleus is not Neuron neuron)
return;
if (this.selectedSynapseNeuron != null) { if (this.selectedSynapseNeuron != null) {
DrawClusterSynapses(this.selectedSynapseNeuron, parentPos, size); DrawClusterSynapses(this.selectedSynapseNeuron, parentPos, size);
return; return;
@ -402,7 +396,7 @@ namespace NanoBrain {
float maxValue = 0; float maxValue = 0;
int neuronCount = 0; int neuronCount = 0;
List<string> drawnNeuronNames = new(); List<string> drawnNeuronNames = new();
foreach (Synapse synapse in neuron.synapses) { foreach (Synapse synapse in nucleus.synapses) {
if (synapse.neuron == null) if (synapse.neuron == null)
continue; continue;
@ -429,7 +423,7 @@ namespace NanoBrain {
int row = 0; int row = 0;
//List<Neuron> drawnNeurons = new(); //List<Neuron> drawnNeurons = new();
drawnNeuronNames = new(); drawnNeuronNames = new();
foreach (Synapse synapse in neuron.synapses) { foreach (Synapse synapse in nucleus.synapses) {
if (synapse.neuron is null) if (synapse.neuron is null)
continue; continue;
@ -488,15 +482,14 @@ namespace NanoBrain {
maxValue = 1; maxValue = 1;
float brightness = siblingNeuron.outputMagnitude / maxValue; float brightness = siblingNeuron.outputMagnitude / maxValue;
color = new Color(brightness, brightness, brightness, 1f); color = new Color(brightness, brightness, brightness, 1f);
} } DrawNucleus(siblingNeuron, position, size, color);
DrawNucleus(siblingNeuron, position, size, color);
GUIStyle style = new(EditorStyles.label) { GUIStyle style = new(EditorStyles.label) {
alignment = TextAnchor.UpperCenter, alignment = TextAnchor.UpperCenter,
normal = { textColor = Color.white }, normal = { textColor = Color.white },
fontStyle = FontStyle.Bold, fontStyle = FontStyle.Bold,
}; };
Vector3 labelPos = position - Vector3.down * (size + 5); // below neuron Vector3 labelPos = position - Vector3.down * (size + 5); // below neuron
string name = $"{sibling.baseName}\n{nucleus.name}"; string name = $"{sibling.baseName}.{nucleus.name}";
Handles.Label(labelPos, name, style); Handles.Label(labelPos, name, style);
row++; row++;
} }
@ -504,9 +497,6 @@ namespace NanoBrain {
} }
protected void DrawOutputs(Vector2 parentPos, float size) { protected void DrawOutputs(Vector2 parentPos, float size) {
if (this.currentCluster == null)
return;
// Determine the maximum value in this layer // Determine the maximum value in this layer
// This is used to 'scale' the output value colors of the nuclei // This is used to 'scale' the output value colors of the nuclei
float maxValue = 0; float maxValue = 0;
@ -613,12 +603,18 @@ namespace NanoBrain {
if (nucleus.parent != null && currentNucleus != null && nucleus.parent != currentNucleus.parent && nucleus.parent is Cluster parentCluster1) { if (nucleus.parent != null && currentNucleus != null && nucleus.parent != currentNucleus.parent && nucleus.parent is Cluster parentCluster1) {
// This neuron is part of another cluster // This neuron is part of another cluster
parentCluster1.name ??= ""; parentCluster1.name ??= "";
string baseName = "";
int colonPos = parentCluster1.name.IndexOf(":"); int colonPos = parentCluster1.name.IndexOf(":");
string baseName;
if (colonPos > 0 && colonPos < parentCluster1.name.Length - 2) if (colonPos > 0 && colonPos < parentCluster1.name.Length - 2)
baseName = parentCluster1.name[..colonPos] + "\n"; baseName = parentCluster1.name[..colonPos] + ".";
else else
baseName = parentCluster1.name + "\n"; baseName = parentCluster1.name + ".";
// if (colonPos > 0 && colonPos < parentCluster1.name.Length - 2) {
// // if it is an array, we should not show the :0 of the first element
// //baseName = baseName[..colonPos];
// Handles.Label(labelPos, baseName + nucleus.name, style);
// }
// else
Handles.Label(labelPos, baseName + nucleus.name, style); Handles.Label(labelPos, baseName + nucleus.name, style);
} }
else { else {
@ -853,34 +849,22 @@ namespace NanoBrain {
void OnSceneGUI(SceneView sceneView) { void OnSceneGUI(SceneView sceneView) {
if (this.gameObject != null) { if (this.gameObject != null) {
// if (this.currentNucleus is IReceptor receptor) {
Handles.color = Color.yellow; // foreach (Nucleus nucleus in receptor.nucleiArray) {
if (this.selectedSynapseNeuron != null) { // if (nucleus is Neuron neuron) {
foreach (Cluster sibling in this.selectedSynapseNeuron.parent.siblingClusters) { // Vector3 worldVector = this.gameObject.transform.TransformVector(neuron.outputValue);
Neuron siblingNeuron = sibling.GetNucleus(this.selectedSynapseNeuron.name) as Neuron; // Handles.color = Color.yellow;
Vector3 worldVector = this.gameObject.transform.TransformVector(siblingNeuron.outputValue); // Handles.DrawLine(this.gameObject.transform.position, this.gameObject.transform.position + worldVector);
Handles.DrawLine(this.gameObject.transform.position, this.gameObject.transform.position + worldVector); // }
} // }
// if (this.currentNucleus is Cluster cluster) { // }
// foreach (Cluster sibling in cluster.siblingClusters) { // else {
if (this.currentNucleus is Neuron currentNeuron) {
// } Vector3 worldVector = this.gameObject.transform.TransformVector(currentNeuron.outputValue);
// } Handles.color = Color.yellow;
// // if (this.currentNucleus is IReceptor receptor) { Handles.DrawLine(this.gameObject.transform.position, this.gameObject.transform.position + worldVector);
// // foreach (Nucleus nucleus in receptor.nucleiArray) {
// // if (nucleus is Neuron neuron) {
// // Vector3 worldVector = this.gameObject.transform.TransformVector(neuron.outputValue);
// // Handles.color = Color.yellow;
// // Handles.DrawLine(this.gameObject.transform.position, this.gameObject.transform.position + worldVector);
// // }
// // }
}
else {
if (this.currentNucleus is Neuron currentNeuron) {
Vector3 worldVector = this.gameObject.transform.TransformVector(currentNeuron.outputValue);
Handles.DrawLine(this.gameObject.transform.position, this.gameObject.transform.position + worldVector);
}
} }
// }
} }
} }

View File

@ -1,11 +1,2 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 4fe58945c76d153edacc220597474ad2 guid: 4fe58945c76d153edacc220597474ad2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,11 +1,2 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: c7539a20f7894542ca347730cd8417b1 guid: c7539a20f7894542ca347730cd8417b1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,83 +0,0 @@
using UnityEngine;
using UnityEditor;
using Unity.Mathematics;
using System;
using System.Reflection;
using System.Collections;
namespace NanoBrain {
[CustomPropertyDrawer(typeof(Neuron))]
class Neuron_Drawer : PropertyDrawer {
public static void Insepctor(SerializedObject serializedObject, string propertyName ) {
EditorGUILayout.PropertyField(serializedObject.FindProperty(propertyName));
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
// Draw foldout + properties
label = EditorGUI.BeginProperty(position, label, property);
// Begin indent block
int indent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
object instance = GetTargetObjectOfProperty(property);
float lineHeight = EditorGUIUtility.singleLineHeight;
Rect r = new(position.x, position.y, position.width, lineHeight);
if (instance != null) {
FieldInfo field = typeof(Neuron).GetField("_outputValue", BindingFlags.NonPublic | BindingFlags.Instance);
if (field != null) {
float3 val = (float3)field.GetValue(instance);
EditorGUI.Vector3Field(r, $"Neuron: {label}", val);
}
}
EditorGUI.indentLevel = indent;
EditorGUI.EndProperty();
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
// height for 1 line
return (EditorGUIUtility.singleLineHeight * 1) + (EditorGUIUtility.standardVerticalSpacing * 0);
}
public static object GetTargetObjectOfProperty(SerializedProperty prop) {
var path = prop.propertyPath.Replace(".Array.data[", "[");
object obj = prop.serializedObject.targetObject;
var elements = path.Split('.');
foreach (var element in elements) {
if (element.Contains("[")) {
var elementName = element.Substring(0, element.IndexOf("["));
var index = Convert.ToInt32(element.Substring(element.IndexOf("[")).Replace("[", "").Replace("]", ""));
obj = GetValue_Imp(obj, elementName, index);
}
else {
obj = GetValue_Imp(obj, element);
}
}
return obj;
}
static object GetValue_Imp(object source, string name) {
if (source == null)
return null;
Type t = source.GetType();
FieldInfo f = t.GetField(name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
if (f != null)
return f.GetValue(source);
PropertyInfo p = t.GetProperty(name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
return p?.GetValue(source, null);
}
static object GetValue_Imp(object source, string name, int index) {
if (GetValue_Imp(source, name) is not IEnumerable enumerable)
return null;
IEnumerator en = enumerable.GetEnumerator();
for (int i = 0; i <= index; i++) {
if (!en.MoveNext())
return null;
}
return en.Current;
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: aa0e340763ca6299e93d514b271ae38d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,4 +1,4 @@
#if !UNITY_5_6_OR_NEWER //#if !UNITY_5_6_OR_NEWER
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using NUnit.Framework; using NUnit.Framework;
@ -268,4 +268,4 @@ namespace LinearAlgebra.Test {
} }
} }
#endif //#endif

View File

@ -45,7 +45,7 @@ namespace NanoBrain {
/// <param name="name">The name of the Neuron for which the weights are updated</param> /// <param name="name">The name of the Neuron for which the weights are updated</param>
/// <param name="weight">The new Synapse weight</param> /// <param name="weight">The new Synapse weight</param>
public static void UpdateWeight(Cluster brain, string name, float weight) { public static void UpdateWeight(Cluster brain, string name, float weight) {
Neuron root = brain.defaultOutput; Nucleus root = brain.defaultOutput;
foreach (Synapse synapse in root.synapses) { foreach (Synapse synapse in root.synapses) {
if (synapse.neuron.name == name) { if (synapse.neuron.name == name) {
if (synapse.weight != weight) { if (synapse.weight != weight) {

View File

@ -1,11 +1,2 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 92f34a5e4027a1dc39efd8ce63cf6aba guid: 92f34a5e4027a1dc39efd8ce63cf6aba
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -41,7 +41,7 @@ namespace NanoBrain {
public Dictionary<int, Cluster> thingClusters = new(); public Dictionary<int, Cluster> thingClusters = new();
[SerializeReference] [SerializeReference]
public List<Nucleus> nuclei = new(); public List<Nucleus> clusterNuclei = new();
// the nuclei sorted using topological sorting // the nuclei sorted using topological sorting
// to ensure that the cluster is computer in the right order // to ensure that the cluster is computer in the right order
public List<Nucleus> sortedNuclei; public List<Nucleus> sortedNuclei;
@ -58,10 +58,10 @@ namespace NanoBrain {
this.name = prefab.name; this.name = prefab.name;
this.parent = parent; this.parent = parent;
this.parent?.nuclei.Add(this); this.parent?.clusterNuclei.Add(this);
ClonePrefab(); ClonePrefab();
_ = this.inputs; _ = this.inputs;
this.sortedNuclei = TopologicalSort(this.nuclei); this.sortedNuclei = TopologicalSort(this.clusterNuclei);
} }
/// <summary> /// <summary>
@ -72,15 +72,14 @@ namespace NanoBrain {
public Cluster(ClusterPrefab prefab, ClusterPrefab parent = null) { public Cluster(ClusterPrefab prefab, ClusterPrefab parent = null) {
this.prefab = prefab; this.prefab = prefab;
this.name = prefab.name; this.name = prefab.name;
if (parent != null) this.clusterPrefab = parent;
this.parent = parent.cluster;
// if (this.parent.prefab != null) if (this.clusterPrefab != null)
// this.parent.prefab.cluster.nuclei.Add(this); this.clusterPrefab.nuclei.Add(this);
ClonePrefab(); ClonePrefab();
_ = this.inputs; _ = this.inputs;
this.sortedNuclei = TopologicalSort(this.nuclei); this.sortedNuclei = TopologicalSort(this.clusterNuclei);
} }
/// <summary> /// <summary>
@ -89,13 +88,15 @@ namespace NanoBrain {
/// Strange that this does not take any parameters or return values. /// Strange that this does not take any parameters or return values.
/// Where which the clone be found??? /// Where which the clone be found???
private void ClonePrefab() { private void ClonePrefab() {
Nucleus[] prefabNuclei = this.prefab.cluster.nuclei.ToArray(); Nucleus[] prefabNuclei = this.prefab.nuclei.ToArray();
// first clone the nuclei without their connections // first clone the nuclei without their connections
foreach (Nucleus nucleus in prefabNuclei) { foreach (Nucleus nucleus in prefabNuclei) {
nucleus.ShallowCloneTo(this); nucleus.ShallowCloneTo(this);
} }
Nucleus[] clonedNuclei = this.nuclei.ToArray(); Nucleus[] clonedNuclei = this.clusterNuclei.ToArray();
// foreach (Nucleus n in clonedNuclei)
// n.name += "(c)";
// Now clone the connections // Now clone the connections
for (int nucleusIx = 0; nucleusIx < prefabNuclei.Length; nucleusIx++) { for (int nucleusIx = 0; nucleusIx < prefabNuclei.Length; nucleusIx++) {
@ -109,10 +110,10 @@ namespace NanoBrain {
foreach (Synapse prefabSynapse in prefabNeuron.synapses) { foreach (Synapse prefabSynapse in prefabNeuron.synapses) {
Neuron synapseNeuron = prefabSynapse.neuron; Neuron synapseNeuron = prefabSynapse.neuron;
if (synapseNeuron.parent.prefab != null && synapseNeuron.parent.prefab != this.prefab) { if (synapseNeuron.clusterPrefab != null && synapseNeuron.clusterPrefab != this.prefab) {
// Neuron is in another cluster, find the cloned cluster first // Neuron is in another cluster, find the cloned cluster first
Cluster prefabCluster = synapseNeuron.parent; ClusterPrefab prefabCluster = synapseNeuron.clusterPrefab;
Cluster clonedCluster = this.nuclei.Find(n => n.name == prefabCluster.name) as Cluster; Cluster clonedCluster = this.clusterNuclei.Find(n => n.name == prefabCluster.name) as Cluster;
if (clonedCluster == null) if (clonedCluster == null)
continue; continue;
@ -121,7 +122,7 @@ namespace NanoBrain {
if (neuronIx < 0) if (neuronIx < 0)
// Could not find the neuron in the prefab cluster // Could not find the neuron in the prefab cluster
continue; continue;
if (clonedCluster.nuclei[neuronIx] is not Neuron clonedSender) if (clonedCluster.clusterNuclei[neuronIx] is not Neuron clonedSender)
// Could not find the neuron in the cloned cluster // Could not find the neuron in the cloned cluster
continue; continue;
@ -140,6 +141,28 @@ namespace NanoBrain {
// Debug.Log($"Add synapse {clonedSender.name} -> {clonedNeuron.name}"); // Debug.Log($"Add synapse {clonedSender.name} -> {clonedNeuron.name}");
} }
} }
// // Copy the receivers, which will also create the synapses
// foreach (Nucleus receiver in prefabNeuron.receivers.ToArray()) {
// 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.neuron == prefabNucleus) {
// weight = synapse.weight;
// break;
// }
// }
// clonedNeuron.AddReceiver(clonedReceiver, weight);
// }
} }
if (Application.isPlaying) { if (Application.isPlaying) {
@ -156,7 +179,7 @@ namespace NanoBrain {
Debug.Log($"create {clonedCluster.prefab.name} sibling"); Debug.Log($"create {clonedCluster.prefab.name} sibling");
Cluster sibling = new(clonedCluster.prefab, this) { Cluster sibling = new(clonedCluster.prefab, this) {
name = $"{clonedCluster.baseName}: {instanceIx}", name = $"{clonedCluster.baseName}: {instanceIx}",
parent = this.parent, clusterPrefab = this.clusterPrefab,
instanceCount = this.instanceCount, instanceCount = this.instanceCount,
}; };
siblings.Add(sibling); siblings.Add(sibling);
@ -166,46 +189,133 @@ namespace NanoBrain {
foreach (Cluster sibling in siblings) foreach (Cluster sibling in siblings)
sibling.siblingClusters = siblingClusters; sibling.siblingClusters = siblingClusters;
} }
// Ensure that all neurons are computed to initialize bias
foreach (Nucleus clonedNucleus in clonedNuclei) {
if (clonedNucleus is not Cluster)
clonedNucleus.UpdateStateIsolated();
}
} }
/*
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;
}
}
} }
// private void CloneSynapses(Neuron prefabNeuron, Neuron clonedNeuron) { /*
// foreach (Synapse prefabSynapse in prefabNeuron.synapses) { // Collect the subclusters
// Neuron synapseNeuron = prefabSynapse.neuron; List<Cluster> subClusters = new();
// if (synapseNeuron.parent.prefab != null && synapseNeuron.parent.prefab != this.prefab) { foreach (Nucleus nucleus in prefabNuclei) {
// // Neuron is in another cluster, find the cloned cluster first foreach (Synapse synapse in nucleus.synapses) {
// ClusterPrefab prefabCluster = synapseNeuron.parent.prefab; Nucleus synapseNucleus = synapse.neuron;
// Cluster clonedCluster = this.nuclei.Find(n => n.name == prefabCluster.name) as Cluster; Cluster subCluster = synapseNucleus.parent;
// if (clonedCluster == null) if (subCluster is null ||
// continue; synapseNucleus.clusterPrefab == this.clusterPrefab) {
// // Now find the neuron in that cloned cluster continue;
// int neuronIx = GetNucleusIndex(prefabCluster.cluster.nuclei, prefabSynapse.neuron.name); }
// if (neuronIx < 0) // if (synapseNucleus is not Cluster subCluster)
// // Could not find the neuron in the prefab cluster // continue;
// continue; if (subClusters.Contains(subCluster))
// if (clonedCluster.nuclei[neuronIx] is not Neuron clonedSender) continue;
// // Could not find the neuron in the cloned cluster subClusters.Add(subCluster);
// continue; }
}
// 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;
// clonedSender.AddReceiver(clonedNeuron, prefabSynapse.weight); if (clonedNuclei[receiverIx] is not Nucleus clonedReceiver)
// //Debug.Log($"Add synapse {clonedCluster.name}.{clonedSender.name} -> {clonedNeuron.name} [{clonedSender.receivers.Count}]"); continue;
// }
// else {
// Neuron clonedSender = this.nuclei.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}");
// }
// }
// } // 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> /// <summary>
/// Sort the nuclei in a correct evaluation order /// Sort the nuclei in a correct evaluation order
@ -268,20 +378,20 @@ namespace NanoBrain {
public override Nucleus Clone(ClusterPrefab parent) { public override Nucleus Clone(ClusterPrefab parent) {
Cluster clone = new(this.prefab, parent); Cluster clone = new(this.prefab, parent);
// foreach (Synapse synapse in this.synapses) { foreach (Synapse synapse in this.synapses) {
// Synapse clonedSynapse = clone.AddSynapse(synapse.neuron); Synapse clonedSynapse = clone.AddSynapse(synapse.neuron);
// clonedSynapse.weight = synapse.weight; clonedSynapse.weight = synapse.weight;
// } }
foreach (Nucleus nucleus in this.nuclei) { foreach (Nucleus nucleus in this.clusterNuclei) {
if (nucleus is Neuron output) { if (nucleus is Neuron output) {
foreach (Nucleus receiver in output.receivers) { foreach (Nucleus receiver in output.receivers) {
int ix = GetNucleusIndex(this.nuclei, output); int ix = GetNucleusIndex(this.clusterNuclei, output);
Debug.Log($"{output.name} -> {receiver.name}: {ix}"); Debug.Log($"{output.name} -> {receiver.name}: {ix}");
if (ix < 0) if (ix < 0)
continue; continue;
if (clone.nuclei[ix] is not Neuron clonedOutput) if (clone.clusterNuclei[ix] is not Neuron clonedOutput)
continue; continue;
clonedOutput.AddReceiver(receiver); clonedOutput.AddReceiver(receiver);
@ -296,7 +406,7 @@ namespace NanoBrain {
// Clusters should not be cloned, but instantiated from the prefab.... // Clusters should not be cloned, but instantiated from the prefab....
Cluster clone = new(this.prefab, parent) { Cluster clone = new(this.prefab, parent) {
name = this.name, name = this.name,
parent = this.parent, clusterPrefab = this.clusterPrefab,
instanceCount = this.instanceCount, instanceCount = this.instanceCount,
}; };
// Somehow siblingClusters should be cloned too. Believe I do this in ClonePrefab right now. // Somehow siblingClusters should be cloned too. Believe I do this in ClonePrefab right now.
@ -306,26 +416,23 @@ namespace NanoBrain {
private static void CopyAllExternalReceivers(Cluster sourceCluster, Cluster sibling, ClusterPrefab prefabParent, Cluster clonedParent) { private static void CopyAllExternalReceivers(Cluster sourceCluster, Cluster sibling, ClusterPrefab prefabParent, Cluster clonedParent) {
for (int nucleusIx = 0; nucleusIx < sourceCluster.nuclei.Count; nucleusIx++) { for (int nucleusIx = 0; nucleusIx < sourceCluster.clusterNuclei.Count; nucleusIx++) {
Nucleus sourceNucleus = sourceCluster.nuclei[nucleusIx]; Nucleus sourceNucleus = sourceCluster.clusterNuclei[nucleusIx];
if (sourceNucleus is not Neuron sourceNeuron) if (sourceNucleus is not Neuron sourceNeuron)
continue; continue;
if (sibling.nuclei[nucleusIx] is not Neuron clonedNeuron) if (sibling.clusterNuclei[nucleusIx] is not Neuron clonedNeuron)
continue; continue;
// copy the receivers (and thus synapses) from the source to the sibling // copy the receivers (and thus synapses) from the source to the sibling
foreach (Nucleus receiver in sourceNeuron.receivers) { foreach (Nucleus receiver in sourceNeuron.receivers) {
if (receiver is not Neuron receiverNeuron) int ix = GetNucleusIndex(clonedParent.clusterNuclei, receiver);
continue; if (ix < 0 || ix >= clonedParent.clusterNuclei.Count)
int ix = GetNucleusIndex(clonedParent.nuclei, receiver);
if (ix < 0 || ix >= clonedParent.nuclei.Count)
continue; continue;
// Find the synapse for the weight // Find the synapse for the weight
float weight = 1; float weight = 1;
foreach (Synapse synapse in receiverNeuron.synapses) { foreach (Synapse synapse in receiver.synapses) {
// Find the weight for this synapse // Find the weight for this synapse
if (synapse.neuron == sourceNucleus) { if (synapse.neuron == sourceNucleus) {
weight = synapse.weight; weight = synapse.weight;
@ -423,43 +530,42 @@ namespace NanoBrain {
} }
} }
// public virtual Cluster GetThingCluster() { public virtual Cluster GetThingCluster() {
// Cluster selectedCluster = SelectCluster(); Cluster selectedCluster = SelectCluster();
// return selectedCluster; return selectedCluster;
// } }
// public virtual Cluster GetThingCluster(int thingId, string thingName = null) { public virtual Cluster GetThingCluster(int thingId, string thingName = null) {
// if (thingClusters.TryGetValue(thingId, out Cluster cluster)) if (thingClusters.TryGetValue(thingId, out Cluster cluster))
// return cluster; return cluster;
// Cluster selectedCluster = SelectCluster(); Cluster selectedCluster = SelectCluster();
// selectedCluster.name = baseName + ": " + thingName; selectedCluster.name = baseName + ": " + thingName;
// thingClusters[thingId] = selectedCluster; thingClusters[thingId] = selectedCluster;
// return selectedCluster; return selectedCluster;
// } }
// private Cluster SelectCluster() { private Cluster SelectCluster() {
// if (this.siblingClusters == null) if (this.siblingClusters == null)
// return this; return this;
// // Find a sleeping cluster // Find a sleeping cluster
// // foreach (Cluster cluster in this.siblingClusters) { foreach (Cluster cluster in this.siblingClusters) {
// // if (cluster.defaultOutput.isSleeping) { if (cluster.defaultOutput.isSleeping) {
// // RemoveThingCluster(cluster); RemoveThingCluster(cluster);
// // return cluster; return cluster;
// // } }
// // } }
// // Find longest unused cluster // Otherwise find longest unused cluster
// // Note this uses the default output... Cluster unusedCluster = this.siblingClusters[0];
// Cluster unusedCluster = this.siblingClusters[0]; for (int ix = 1; ix < this.siblingClusters.Length; ix++) {
// for (int ix = 1; ix < this.siblingClusters.Length; ix++) { if (this.siblingClusters[ix].defaultOutput.lastUpdate < unusedCluster.defaultOutput.lastUpdate)
// if (this.siblingClusters[ix].defaultOutput.lastUpdate < unusedCluster.defaultOutput.lastUpdate) unusedCluster = this.siblingClusters[ix];
// unusedCluster = this.siblingClusters[ix]; }
// }
// RemoveThingCluster(unusedCluster); RemoveThingCluster(unusedCluster);
// return unusedCluster; return unusedCluster;
// } }
private void RemoveThingCluster(Cluster cluster) { private void RemoveThingCluster(Cluster cluster) {
List<int> keysToRemove = new(); List<int> keysToRemove = new();
@ -497,34 +603,36 @@ namespace NanoBrain {
get { get {
if (this._inputs == null) { if (this._inputs == null) {
this._inputs = new(); this._inputs = new();
foreach (Nucleus nucleus in this.nuclei) { foreach (Nucleus nucleus in this.clusterNuclei) {
if (nucleus is not Neuron neuron)
continue;
// inputs have no synapses // inputs have no synapses
if (neuron.synapses.Count == 0) if (nucleus.synapses.Count == 0)
this._inputs.Add(nucleus); this._inputs.Add(nucleus);
} }
RefreshComputeOrders(); ComputeOrders();
} }
return this._inputs; return this._inputs;
} }
} }
private Dictionary<Nucleus, List<Nucleus>> _computeOrders; public Dictionary<Nucleus, List<Nucleus>> computeOrders = new();
public Dictionary<Nucleus, List<Nucleus>> computeOrders { private void ComputeOrders() {
get { foreach (Nucleus nucleus in this.clusterNuclei) {
if (_computeOrders == null || _computeOrders.Count == 0) { // if (nucleus is Cluster cluster) {
_computeOrders = new(); // List<Synapse> synapses = this.CollectSynapsesTo(cluster);
foreach (Nucleus nucleus in this.nuclei) // foreach (Synapse synapse in synapses) {
_computeOrders[nucleus] = TopologicalSort2(nucleus); // computeOrders[synapse.neuron] = TopologicalSort2(synapse.neuron);
} // Debug.Log($"{this.baseName}: Order for {cluster.baseName}.{synapse.neuron.name}");
return _computeOrders; // }
// // 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}");
// }
} }
} }
public void RefreshComputeOrders() {
this._computeOrders = null;
}
private List<Nucleus> TopologicalSort2(Nucleus startNode) { private List<Nucleus> TopologicalSort2(Nucleus startNode) {
Dictionary<Nucleus, int> inDegree = new(); Dictionary<Nucleus, int> inDegree = new();
@ -590,17 +698,17 @@ namespace NanoBrain {
public virtual Neuron defaultOutput {//=> this.nuclei[0] as Nucleus; public virtual Neuron defaultOutput {//=> this.nuclei[0] as Nucleus;
get { get {
if (this.nuclei.Count > 0) if (this.clusterNuclei.Count > 0)
return this.nuclei[0] as Neuron; return this.clusterNuclei[0] as Neuron;
return null; return null;
} }
} }
protected List<Neuron> _outputs = null; protected List<Neuron> _outputs = null;
public List<Neuron> outputs { public List<Neuron> outputs {
get { get {
if (this._outputs == null || this._outputs.Count == 0) { if (this._outputs == null) {
this._outputs = new(); this._outputs = new();
foreach (Nucleus nucleus in this.nuclei) { foreach (Nucleus nucleus in this.clusterNuclei) {
if (nucleus is Neuron neuron && neuron.receivers.Count == 0) if (nucleus is Neuron neuron && neuron.receivers.Count == 0)
this._outputs.Add(neuron); this._outputs.Add(neuron);
} }
@ -613,7 +721,7 @@ namespace NanoBrain {
} }
public bool TryGetNucleus(string nucleusName, out Nucleus foundNucleus) { public bool TryGetNucleus(string nucleusName, out Nucleus foundNucleus) {
foreach (Nucleus receptor in this.nuclei) { foreach (Nucleus receptor in this.clusterNuclei) {
if (receptor is Nucleus nucleus) if (receptor is Nucleus nucleus)
if (nucleus.name == nucleusName) { if (nucleus.name == nucleusName) {
foundNucleus = nucleus; foundNucleus = nucleus;
@ -629,7 +737,7 @@ namespace NanoBrain {
if (dotPosition >= 0) { if (dotPosition >= 0) {
string clusterName = nucleusName[..dotPosition]; string clusterName = nucleusName[..dotPosition];
string clusterName0 = clusterName + ": 0"; string clusterName0 = clusterName + ": 0";
foreach (Nucleus nucleus in this.nuclei) { foreach (Nucleus nucleus in this.clusterNuclei) {
if (nucleus is Cluster cluster) { if (nucleus is Cluster cluster) {
if (cluster.name == clusterName || cluster.name == clusterName0) { if (cluster.name == clusterName || cluster.name == clusterName0) {
string subNucleusName = nucleusName[(dotPosition + 1)..]; string subNucleusName = nucleusName[(dotPosition + 1)..];
@ -641,9 +749,9 @@ namespace NanoBrain {
} }
else { else {
string nucleusName0 = nucleusName + ": 0"; string nucleusName0 = nucleusName + ": 0";
foreach (Nucleus nucleus in this.nuclei) { foreach (Nucleus nucleus in this.clusterNuclei) {
if (nucleus is Cluster) { if (nucleus is Cluster) { //IReceptor receptor) {
if (nucleus.name == nucleusName || nucleus.name == nucleusName0) if (nucleus.name == nucleusName | nucleus.name == nucleusName0)
return nucleus; return nucleus;
} }
else if (nucleus.name == nucleusName) else if (nucleus.name == nucleusName)
@ -653,70 +761,17 @@ namespace NanoBrain {
} }
} }
public Neuron GetNeuron(string neuronName) {
foreach (Nucleus nucleus in this.nuclei) {
if (nucleus is Neuron neuron && neuron.name == neuronName)
return neuron;
}
return null;
}
public Neuron GetNeuron(int thingId, string neuronName, string thingName = null) {
if (this.siblingClusters == null || this.siblingClusters.Length <= 1)
return this.GetNeuron(neuronName);
// See if we are already using a cluster for thingId
if (thingClusters.TryGetValue(thingId, out Cluster cluster))
return cluster.GetNeuron(neuronName);
// Find the cluster with the lowest value neuron
Neuron lowestNeuron = null;
foreach (Cluster sibling in this.siblingClusters) {
Neuron neuron = sibling.GetNeuron(neuronName);
if (lowestNeuron == null || neuron.outputMagnitude < lowestNeuron.outputMagnitude)
lowestNeuron = neuron;
}
Cluster selectedCluster = lowestNeuron.parent;
RemoveThingCluster(selectedCluster);
selectedCluster.name = baseName + ": " + thingName;
thingClusters[thingId] = selectedCluster;
return lowestNeuron;
/*
// Find a sleeping cluster
// foreach (Cluster cluster in this.siblingClusters) {
// if (cluster.defaultOutput.isSleeping) {
// RemoveThingCluster(cluster);
// return cluster;
// }
// }
// Find longest unused cluster
// Note this uses the default output...
Cluster unusedCluster = this.siblingClusters[0];
for (int ix = 1; ix < this.siblingClusters.Length; ix++) {
if (this.siblingClusters[ix].defaultOutput.lastUpdate < unusedCluster.defaultOutput.lastUpdate)
unusedCluster = this.siblingClusters[ix];
}
RemoveThingCluster(unusedCluster);
//return unusedCluster;
Cluster cluster = GetThingCluster(thingId, thingName);
Neuron neuron = cluster?.GetNeuron(neuronName);
return neuron;
*/
}
public bool DeleteNucleus(Nucleus nucleus) { public bool DeleteNucleus(Nucleus nucleus) {
if (this.nuclei.Contains(nucleus) == false) { if (this.clusterNuclei.Contains(nucleus) == false) {
// Try to find the nucleus by name // Try to find the nucleus by name
if (TryGetNucleus(nucleus.name, out nucleus) == false) if (TryGetNucleus(nucleus.name, out nucleus) == false)
return false; return false;
} }
Neuron.Delete(nucleus); Neuron.Delete(nucleus);
//int nucleusIx = this.nuclei.IndexOf(nucleus); int nucleusIx = this.clusterNuclei.IndexOf(nucleus);
this.nuclei.Remove(nucleus); this.clusterNuclei.Remove(nucleus);
//this.prefab.cluster.nuclei.RemoveAt(nucleusIx); this.prefab.nuclei.RemoveAt(nucleusIx);
RefreshOutputs(); RefreshOutputs();
return true; return true;
@ -726,7 +781,7 @@ namespace NanoBrain {
public virtual List<Nucleus> CollectReceivers(bool removeDuplicates = false) { public virtual List<Nucleus> CollectReceivers(bool removeDuplicates = false) {
List<Nucleus> receivers = new(); List<Nucleus> receivers = new();
foreach (Nucleus outputNucleus in this.nuclei) { foreach (Nucleus outputNucleus in this.clusterNuclei) {
if (outputNucleus is not Neuron output) if (outputNucleus is not Neuron output)
continue; continue;
@ -734,7 +789,7 @@ namespace NanoBrain {
foreach (Nucleus receiver in output.receivers) { foreach (Nucleus receiver in output.receivers) {
// Debug.Log($"output {receiver.name}"); // Debug.Log($"output {receiver.name}");
// Only add receivers outside this cluster // Only add receivers outside this cluster
if (receiver.parent.prefab != this.prefab) { if (receiver.clusterPrefab != this.prefab) {
if (removeDuplicates == false || receivers.Contains(receiver) == false) if (removeDuplicates == false || receivers.Contains(receiver) == false)
// Debug.Log($" YES"); // Debug.Log($" YES");
receivers.Add(receiver); receivers.Add(receiver);
@ -747,13 +802,13 @@ namespace NanoBrain {
public List<(Neuron, Nucleus)> CollectConnections() { public List<(Neuron, Nucleus)> CollectConnections() {
List<(Neuron, Nucleus)> connections = new(); List<(Neuron, Nucleus)> connections = new();
foreach (Nucleus outputNucleus in this.nuclei) { foreach (Nucleus outputNucleus in this.clusterNuclei) {
if (outputNucleus is not Neuron output) if (outputNucleus is not Neuron output)
continue; continue;
foreach (Nucleus receiver in output.receivers) { foreach (Nucleus receiver in output.receivers) {
// Only add receivers outside this cluster // Only add receivers outside this cluster
if (receiver.parent.prefab != this.prefab) if (receiver.clusterPrefab != this.prefab)
connections.Add((output, receiver)); connections.Add((output, receiver));
} }
} }
@ -762,10 +817,10 @@ namespace NanoBrain {
public List<Synapse> CollectSynapsesTo(Cluster otherCluster) { public List<Synapse> CollectSynapsesTo(Cluster otherCluster) {
List<Synapse> collectedSynapses = new(); List<Synapse> collectedSynapses = new();
foreach (Nucleus nucleus in this.nuclei) { foreach (Nucleus nucleus in this.clusterNuclei) {
if (nucleus is not Neuron neuron) if (nucleus is not Neuron neuron)
continue; continue;
foreach (Synapse synapse in neuron.synapses) { foreach (Synapse synapse in nucleus.synapses) {
if (synapse.neuron.parent == otherCluster) if (synapse.neuron.parent == otherCluster)
collectedSynapses.Add(synapse); collectedSynapses.Add(synapse);
} }
@ -775,7 +830,7 @@ namespace NanoBrain {
public void MoveReceivers(Cluster newCluster) { public void MoveReceivers(Cluster newCluster) {
Debug.Log($"Move receivers for {this.name} to {newCluster.name}"); Debug.Log($"Move receivers for {this.name} to {newCluster.name}");
foreach (Nucleus outputNucleus in this.nuclei) { foreach (Nucleus outputNucleus in this.clusterNuclei) {
if (outputNucleus is not Neuron output) if (outputNucleus is not Neuron output)
continue; continue;
@ -787,13 +842,11 @@ namespace NanoBrain {
Debug.Log($"Check {this.name}.{output.name} receivers"); Debug.Log($"Check {this.name}.{output.name} receivers");
Nucleus[] receivers = output.receivers.ToArray(); Nucleus[] receivers = output.receivers.ToArray();
foreach (Nucleus receiver in receivers) { foreach (Nucleus receiver in receivers) {
if (receiver.parent.prefab != this.prefab) { if (receiver.clusterPrefab != this.prefab) {
// Replace synapse with new synapse // Replace synapse with new synapse
// to the new cluster // to the new cluster
Debug.Log($"move {receiver.name} from {this.name}.{output.name} to {newCluster.name}.{newOutput.name}"); Debug.Log($"move {receiver.name} from {this.name}.{output.name} to {newCluster.name}.{newOutput.name}");
if (receiver is not Neuron receiverNeuron) Synapse synapse = receiver.GetSynapse(output);
continue;
Synapse synapse = receiverNeuron.GetSynapse(output);
newOutput.AddReceiver(receiver, synapse.weight); newOutput.AddReceiver(receiver, synapse.weight);
output.RemoveReceiver(receiver); output.RemoveReceiver(receiver);
} }
@ -814,45 +867,41 @@ namespace NanoBrain {
} }
List<Nucleus> computeOrder = this.computeOrders[startNucleus]; List<Nucleus> computeOrder = this.computeOrders[startNucleus];
//if (startNucleus.trace)
Debug.Log($"Update from {startNucleus.name}");
foreach (Nucleus nucleus in computeOrder) { foreach (Nucleus nucleus in computeOrder) {
if (nucleus is not Cluster) { if (nucleus is not Cluster) {
nucleus.UpdateStateIsolated(); nucleus.UpdateStateIsolated();
//if (startNucleus.trace && nucleus is Neuron neuron)
Debug.Log($" {nucleus.name}");
if (nucleus is Neuron neuron) { if (nucleus is Neuron neuron) {
foreach (Nucleus receiver in neuron.receivers) { foreach (Nucleus receiver in neuron.receivers) {
if (receiver.parent != this) { if (receiver.parent != this) {
//Debug.Log($" External: {receiver.parent.name}.{receiver.name}"); Debug.Log($" External: {receiver.parent.name}.{receiver.name}");
receiver.parent.UpdateFromNucleus(receiver); receiver.parent.UpdateFromNucleus(receiver);
} }
} }
} }
} }
} }
//UpdateNuclei();
// continue in parent
//this.parent?.UpdateFromNucleus(this);
UpdateNuclei();
} }
public override void UpdateStateIsolated() { public override void UpdateStateIsolated() {
throw new Exception("Cluster should not be updated!"); throw new Exception("Cluster should not be updated!");
} }
// Don't think this does anything anymore... public override void UpdateNuclei() {
// public override void UpdateNuclei() { foreach (Nucleus nucleus in this.clusterNuclei)
// foreach (Nucleus nucleus in this.nuclei) nucleus.UpdateNuclei();
// nucleus.UpdateNuclei(); }
// }
#endregion Update #endregion Update
public void Refresh() {
// This should not be needed, but somehow somewhere the parent is changed...
foreach (Nucleus nucleus in this.nuclei) {
// if (nucleus is not Neuron neuron)
// continue;
nucleus.parent = this;
}
RefreshOutputs();
RefreshComputeOrders();
}
} }
} }

View File

@ -1,11 +1,2 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: f13cdc4a175a9f379a00317ae68d8bea guid: f13cdc4a175a9f379a00317ae68d8bea
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -12,7 +12,7 @@ namespace NanoBrain {
[Serializable] [Serializable]
public class MemoryCell : Neuron { public class MemoryCell : Neuron {
// public MemoryCell(ClusterPrefab cluster, string name) : base(cluster, name) { } public MemoryCell(ClusterPrefab cluster, string name) : base(cluster, name) { }
public MemoryCell(Cluster parent, string name) : base(parent, name) { } public MemoryCell(Cluster parent, string name) : base(parent, name) { }
public bool staticMemory = false; public bool staticMemory = false;

View File

@ -1,11 +1,2 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 29633aa3fe5cd9dcc8d886051f45d4d8 guid: 29633aa3fe5cd9dcc8d886051f45d4d8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -23,91 +23,26 @@ namespace NanoBrain {
public Neuron(Cluster parent, string name) { public Neuron(Cluster parent, string name) {
this.parent = parent; this.parent = parent;
this.name = name; this.name = name;
this.parent?.nuclei.Add(this); this.parent?.clusterNuclei.Add(this);
} }
/// <summary> /// <summary>
/// Create a new Neuron in a Cluster Prefab /// Create a new Neuron in a Cluster Prefab
/// </summary> /// </summary>
/// <param name="prefab">The Cluster Preafb in which the new Neuron should be created</param> /// <param name="prefab">The Cluster Preafb in which the new Neuron should be created</param>
/// <param name="name">The name of the new Neuron</param> /// <param name="name">The name of the new Neuron</param>
// public Neuron(ClusterPrefab prefab, string name) { public Neuron(ClusterPrefab prefab, string name) {
// this.clusterPrefab = prefab; this.clusterPrefab = prefab;
// this.name = name; this.name = name;
// if (this.clusterPrefab != null) { if (this.clusterPrefab != null) {
// this.clusterPrefab.cluster.nuclei.Add(this); this.clusterPrefab.nuclei.Add(this);
// this.clusterPrefab.cluster.RefreshOutputs(); this.clusterPrefab.RefreshOutputs();
// } }
// else else
// Debug.LogError("No prefab when adding neuron to prefab"); Debug.LogError("No prefab when adding neuron to prefab");
// } }
#region Serialization #region Serialization
/// <summary>
/// The bias
/// </summary>
/// The bias which a value which is always added to the combined value of the neuron
/// It does not have a synapse and therefore no weight of source nucleus
public Vector3 bias = Vector3.zero;
#region Synapses
[SerializeField]
private List<Synapse> _synapses = new();
/// <summary>
/// The synapses of the nucleus
/// </summary>
public List<Synapse> synapses => _synapses;
/// <summary>
/// Add a new synapse to this nuclues
/// </summary>
/// <param name="sendingNucleus">The nucleus from which the signals may originate</param>
/// <param name="weight">The weight applied to the input. Default value = 1</param>
/// <returns>The created Synapse</returns>
/// This will add a new input to this nucleus with the given weight.
public Synapse AddSynapse(Neuron sendingNucleus, float weight = 1) {
Synapse synapse = new(sendingNucleus, weight);
this.synapses.Add(synapse);
return synapse;
}
// public Synapse AddSynapse(ClusterPrefab clusterPrefab, string neuronName, float weight = 1) {
// }
/// <summary>
/// Find a synapse
/// </summary>
/// <param name="sender">The sender of the input to the Synapse</param>
/// <returns>The found Synapse or null when the sender has no synapse to this nucleus.</returns>
public Synapse GetSynapse(Nucleus sender) {
foreach (Synapse synapse in this.synapses)
if (synapse.neuron == sender)
return synapse;
return null;
}
/// <summary>
/// Remove a synapse from a Nucleus
/// </summary>
/// <param name="sendingNucleus">Remote the synapse connecting to this Nucleus</param>
public void RemoveSynapse(Nucleus sendingNucleus) {
this.synapses.RemoveAll(synapse => synapse.neuron == sendingNucleus);
}
#endregion Synapses
/// <summary>
/// Set the bias, recalculate the output and update all Nuclei receiving from this Nucleus
/// </summary>
/// <param name="inputValue"></param>
public virtual void SetBias(Vector3 inputValue) {
this.bias = inputValue;
this.lastUpdate = Time.time;
this.parent?.UpdateFromNucleus(this);
}
/// <summary> /// <summary>
/// The type of combinators /// The type of combinators
/// </summary> /// </summary>
@ -117,6 +52,8 @@ namespace NanoBrain {
Sum, Sum,
/// Multiply the weighted values /// Multiply the weighted values
Product, Product,
/// Take the maximum of all the weighted values
Max,
} }
/// <summary> /// <summary>
/// The type of combinator used for this Neuron /// The type of combinator used for this Neuron
@ -274,11 +211,16 @@ namespace NanoBrain {
public float outputSqrMagnitude => _outputValue.sqrMagnitude; public float outputSqrMagnitude => _outputValue.sqrMagnitude;
#endif #endif
public bool isFiring => this.outputMagnitude > 0.5f; public bool isFiring {
get {
SleepCheck();
return this.outputMagnitude > 0.5f;
}
}
public Action WhenFiring; public Action WhenFiring;
public bool persistOutput = false;
public virtual bool isSleeping => !persistOutput && (Time.time - this.lastUpdate > this.timeToSleep); public virtual bool isSleeping => Time.time - this.lastUpdate > this.timeToSleep; //this.outputMagnitude == 0;
public void SleepCheck() { public void SleepCheck() {
if (this.isSleeping) { if (this.isSleeping) {
#if UNITY_MATHEMATICS #if UNITY_MATHEMATICS
@ -289,27 +231,20 @@ namespace NanoBrain {
} }
} }
/// <summary> [NonSerialized]
/// Toggle for printing debugging trace data
/// </summary>
//public bool trace = false;
//[NonSerialized]
public float lastUpdate = 0; public float lastUpdate = 0;
public readonly float timeToSleep = 1f; public readonly float timeToSleep = 1f;
/// \copydoc NanoBrain::Nucleus::ShallowCloneTo /// \copydoc NanoBrain::Nucleus::ShallowCloneTo
public override Nucleus ShallowCloneTo(Cluster newParent) { public override Nucleus ShallowCloneTo(Cluster newParent) {
Neuron clone = new(newParent, this.name) { Neuron clone = new(newParent, this.name);
// prefabNucleus = this
};
CloneFields(clone); CloneFields(clone);
return clone; return clone;
} }
/// \copydoc NanoBrain::Nucleus::Clone /// \copydoc NanoBrain::Nucleus::Clone
public override Nucleus Clone(ClusterPrefab prefab) { public override Nucleus Clone(ClusterPrefab prefab) {
Neuron clone = new(prefab.cluster, this.name); Neuron clone = new(prefab, this.name);
CloneFields(clone); CloneFields(clone);
foreach (Synapse synapse in this.synapses) { foreach (Synapse synapse in this.synapses) {
Synapse clonedSynapse = clone.AddSynapse(synapse.neuron); Synapse clonedSynapse = clone.AddSynapse(synapse.neuron);
@ -322,8 +257,8 @@ namespace NanoBrain {
} }
protected virtual void CloneFields(Neuron clone) { protected virtual void CloneFields(Neuron clone) {
clone.clusterPrefab = this.clusterPrefab;
clone.bias = this.bias; clone.bias = this.bias;
clone.persistOutput = this.persistOutput;
clone.combinator = this.combinator; clone.combinator = this.combinator;
clone.curve = this.curve; clone.curve = this.curve;
clone.curvePreset = this.curvePreset; clone.curvePreset = this.curvePreset;
@ -333,8 +268,8 @@ namespace NanoBrain {
public static void Delete(Nucleus nucleus) { public static void Delete(Nucleus nucleus) {
if (nucleus == null) if (nucleus == null)
return; return;
if (nucleus is Neuron neuron) { if (nucleus.synapses != null) {
foreach (Synapse synapse in neuron.synapses) { foreach (Synapse synapse in nucleus.synapses) {
if (synapse.neuron is Neuron synapse_nucleus) { if (synapse.neuron is Neuron synapse_nucleus) {
if (synapse_nucleus.receivers.Count > 1) { if (synapse_nucleus.receivers.Count > 1) {
// there is another nucleus feeding into this input nucleus // there is another nucleus feeding into this input nucleus
@ -346,43 +281,45 @@ namespace NanoBrain {
} }
} }
} }
}
if (nucleus is Neuron neuron) {
foreach (Nucleus receiver in neuron.receivers) { foreach (Nucleus receiver in neuron.receivers) {
if (receiver is not Neuron receiverNeuron) if (receiver != null && receiver.synapses != null)
continue; receiver.synapses.RemoveAll(s => s.neuron == nucleus);
if (receiver != null && receiverNeuron.synapses != null)
receiverNeuron.synapses.RemoveAll(s => s.neuron == nucleus);
} }
} }
else if (nucleus is Cluster cluster) { else if (nucleus is Cluster cluster) {
// remove all receivers for this cluster // remove all receivers for this cluster
foreach (Nucleus clusterNucleus in cluster.nuclei) { foreach (Nucleus clusterNucleus in cluster.clusterNuclei) {
if (clusterNucleus is Neuron output) { if (clusterNucleus is Neuron output) {
foreach (Nucleus receiver in output.receivers) { foreach (Nucleus receiver in output.receivers) {
if (receiver is not Neuron receiverNeuron) receiver.synapses.RemoveAll(s => s.neuron == output);
continue;
receiverNeuron.synapses.RemoveAll(s => s.neuron == output);
} }
} }
} }
} }
if (nucleus.parent.prefab != null) { if (nucleus.clusterPrefab != null) {
nucleus.parent.prefab.cluster.nuclei.RemoveAll(n => n == nucleus); nucleus.clusterPrefab.nuclei.RemoveAll(n => n == nucleus);
nucleus.parent.prefab.cluster.RefreshOutputs(); nucleus.clusterPrefab.RefreshOutputs();
nucleus.parent.prefab.GarbageCollection(); nucleus.clusterPrefab.GarbageCollection();
} }
} }
public override void UpdateStateIsolated() { public override void UpdateStateIsolated() {
CheckSleepingSynapses();
var result = Combinator(); var result = Combinator();
this.outputValue = ApplyActivator(result); this.outputValue = Activator(result);
this.lastUpdate = Time.time; this.lastUpdate = Time.time;
} }
protected void CheckSleepingSynapses() { protected void CheckSleepingSynapses() {
foreach (Synapse synapse in this.synapses) foreach (Synapse synapse in this.synapses) {
synapse.neuron.SleepCheck(); if (synapse.isSleeping) {
synapse.neuron.outputValue = Vector3.zero;
}
}
} }
#region Combinator #region Combinator
@ -392,27 +329,42 @@ namespace NanoBrain {
protected Func<float3> Combinator => combinator switch { protected Func<float3> Combinator => combinator switch {
CombinatorType.Sum => CombinatorSum, CombinatorType.Sum => CombinatorSum,
CombinatorType.Product => CombinatorProduct, CombinatorType.Product => CombinatorProduct,
CombinatorType.Max => CombinatorMax,
_ => CombinatorSum _ => CombinatorSum
}; };
public float3 CombinatorSum() { public float3 CombinatorSum() {
float3 sum = this.bias; float3 sum = this.bias;
foreach (Synapse synapse in this.synapses) { foreach (Synapse synapse in this.synapses)
synapse.neuron.SleepCheck();
sum += synapse.weight * synapse.neuron.outputValue; sum += synapse.weight * synapse.neuron.outputValue;
}
return sum; return sum;
} }
public float3 CombinatorProduct() { public float3 CombinatorProduct() {
float3 product = this.bias; float3 product = this.bias;
foreach (Synapse synapse in this.synapses) { foreach (Synapse synapse in this.synapses) {
synapse.neuron.SleepCheck();
product *= synapse.weight * synapse.neuron.outputValue; product *= synapse.weight * synapse.neuron.outputValue;
} }
return product; return product;
} }
public float3 CombinatorMax() {
float3 max = this.bias;
float maxLength = length(max);
//Applying the weight factors
foreach (Synapse synapse in this.synapses) {
float3 input = synapse.weight * synapse.neuron.outputValue;
float inputLength = length(input);
if (inputLength > maxLength) {
max = input;
maxLength = inputLength;
}
}
return max;
}
#else #else
protected Func<Vector3> Combinator => combinator switch { protected Func<Vector3> Combinator => combinator switch {
@ -461,20 +413,6 @@ namespace NanoBrain {
#if UNITY_MATHEMATICS #if UNITY_MATHEMATICS
// This does not allocate memory and seems faster than the solution below
float3 ApplyActivator(float3 x) {
switch (curvePreset) {
case ActivationType.Linear: return ActivatorLinear(x);
case ActivationType.Sqrt: return ActivatorSqrt(x);
case ActivationType.Power: return ActivatorPower(x);
case ActivationType.Reciprocal: return ActivatorReciprocal(x);
case ActivationType.Tanh: return ActivatorTanh(x);
case ActivationType.Binary: return ActivatorBinary(x);
case ActivationType.Normalized: return ActivatorNormalized(x);
default: return ActivatorCustom(x);
}
}
public Func<float3, float3> Activator => this.curvePreset switch { public Func<float3, float3> Activator => this.curvePreset switch {
ActivationType.Linear => ActivatorLinear, ActivationType.Linear => ActivatorLinear,
ActivationType.Sqrt => ActivatorSqrt, ActivationType.Sqrt => ActivatorSqrt,
@ -586,40 +524,25 @@ namespace NanoBrain {
} }
public virtual void AddReceiver(Nucleus receiverToAdd, float weight = 1) { public virtual void AddReceiver(Nucleus receiverToAdd, float weight = 1) {
if (receiverToAdd is not Neuron receiverNeuron) this._receivers.Add(receiverToAdd);
return; receiverToAdd.AddSynapse(this, weight);
this._receivers.Add(receiverNeuron);
receiverNeuron.AddSynapse(this, weight);
//Debug.Log($"Add synapse {this.clusterPrefab.name}.{this.name} -> {receiverToAdd.name} --- [{this.receivers.Count}]"); //Debug.Log($"Add synapse {this.clusterPrefab.name}.{this.name} -> {receiverToAdd.name} --- [{this.receivers.Count}]");
} }
public virtual void RemoveReceiver(Nucleus receiverToRemove) { public virtual void RemoveReceiver(Nucleus receiverToRemove) {
if (receiverToRemove is not Neuron receiverNeuron) this._receivers.RemoveAll(receiver => receiver == receiverToRemove);
return; receiverToRemove.synapses.RemoveAll(synapse => synapse.neuron == this);
this._receivers.RemoveAll(receiver => receiver == receiverNeuron);
receiverNeuron.synapses.RemoveAll(synapse => synapse.neuron == this);
// Nucleus prefabReceiver = receiverToRemove.prefabNucleus;
// if (this.prefabNucleus is Neuron prefabNeuron && prefabReceiver != null) {
// prefabNeuron.receivers.RemoveAll(receiver => receiver == prefabReceiver);
// prefabReceiver.synapses.RemoveAll(synapse => synapse.neuron == prefabNeuron);
// }
} }
#endregion Receivers #endregion Receivers
/// <summary> public override void ProcessStimulus(Vector3 inputValue) {
/// Process an external stimulus ;
/// </summary>
/// <param name="inputValue">The value of the stimulus</param>
/// <param name="thingId">The id of the thing causing the stimulus</param>
/// <param name="thingName">The name of the thing causing the stimulus</param>
public virtual void ProcessStimulus(Vector3 inputValue) {
this.lastUpdate = Time.time; this.lastUpdate = Time.time;
this.bias = inputValue; this.bias = inputValue;
this.parent?.UpdateFromNucleus(this); this.parent.UpdateFromNucleus(this);
} }
} }

View File

@ -1,11 +1,2 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 750748f3f0e7d472fbf88ab02987074c guid: 750748f3f0e7d472fbf88ab02987074c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -17,15 +17,11 @@ public abstract class Nucleus {
/// </summary> /// </summary>
public string name; public string name;
// [NonSerialized]
// public Nucleus prefabNucleus;
/// <summary> /// <summary>
/// The cluster prefab in which the nucleus is located /// The cluster prefab in which the nucleus is located
/// </summary> /// </summary>
// [SerializeReference] [SerializeReference]
// public ClusterPrefab clusterPrefab; public ClusterPrefab clusterPrefab;
/// <summary> /// <summary>
/// The cluster instance in which the nucleus is located /// The cluster instance in which the nucleus is located
/// </summary> /// </summary>
@ -35,7 +31,7 @@ public abstract class Nucleus {
/// <summary> /// <summary>
/// Toggle for printing debugging trace data /// Toggle for printing debugging trace data
/// </summary> /// </summary>
//public bool trace = false; public bool trace = false;
/// <summary> /// <summary>
/// Function to make a partial clone of this nucleus /// Function to make a partial clone of this nucleus
@ -65,60 +61,60 @@ public abstract class Nucleus {
public virtual void Initialize() {} public virtual void Initialize() {}
// #region Synapses #region Synapses
// /// <summary> /// <summary>
// /// The bias of the nucleus /// The bias of the nucleus
// /// </summary> /// </summary>
// /// The bias which a value which is always added to the combined value of the nucleus /// The bias which a value which is always added to the combined value of the nucleus
// /// It does not have a synapse and therefore no weight of source nucleus /// It does not have a synapse and therefore no weight of source nucleus
// //public Vector3 bias = Vector3.zero; public Vector3 bias = Vector3.zero;
// [SerializeField] [SerializeField]
// private List<Synapse> _synapses = new(); private List<Synapse> _synapses = new();
// /// <summary> /// <summary>
// /// The synapses of the nucleus /// The synapses of the nucleus
// /// </summary> /// </summary>
// public List<Synapse> synapses => _synapses; public List<Synapse> synapses => _synapses;
// /// <summary> /// <summary>
// /// Add a new synapse to this nuclues /// Add a new synapse to this nuclues
// /// </summary> /// </summary>
// /// <param name="sendingNucleus">The nucleus from which the signals may originate</param> /// <param name="sendingNucleus">The nucleus from which the signals may originate</param>
// /// <param name="weight">The weight applied to the input. Default value = 1</param> /// <param name="weight">The weight applied to the input. Default value = 1</param>
// /// <returns>The created Synapse</returns> /// <returns>The created Synapse</returns>
// /// This will add a new input to this nucleus with the given weight. /// This will add a new input to this nucleus with the given weight.
// public Synapse AddSynapse(Neuron sendingNucleus, float weight = 1) { public Synapse AddSynapse(Neuron sendingNucleus, float weight = 1) {
// Synapse synapse = new(sendingNucleus, weight); Synapse synapse = new(sendingNucleus, weight);
// this.synapses.Add(synapse); this.synapses.Add(synapse);
// return synapse; return synapse;
// } }
// // public Synapse AddSynapse(ClusterPrefab clusterPrefab, string neuronName, float weight = 1) { // public Synapse AddSynapse(ClusterPrefab clusterPrefab, string neuronName, float weight = 1) {
// // } // }
// /// <summary> /// <summary>
// /// Find a synapse /// Find a synapse
// /// </summary> /// </summary>
// /// <param name="sender">The sender of the input to the Synapse</param> /// <param name="sender">The sender of the input to the Synapse</param>
// /// <returns>The found Synapse or null when the sender has no synapse to this nucleus.</returns> /// <returns>The found Synapse or null when the sender has no synapse to this nucleus.</returns>
// public Synapse GetSynapse(Nucleus sender) { public Synapse GetSynapse(Nucleus sender) {
// foreach (Synapse synapse in this.synapses) foreach (Synapse synapse in this.synapses)
// if (synapse.neuron == sender) if (synapse.neuron == sender)
// return synapse; return synapse;
// return null; return null;
// } }
// /// <summary> /// <summary>
// /// Remove a synapse from a Nucleus /// Remove a synapse from a Nucleus
// /// </summary> /// </summary>
// /// <param name="sendingNucleus">Remote the synapse connecting to this Nucleus</param> /// <param name="sendingNucleus">Remote the synapse connecting to this Nucleus</param>
// public void RemoveSynapse(Nucleus sendingNucleus) { public void RemoveSynapse(Nucleus sendingNucleus) {
// this.synapses.RemoveAll(synapse => synapse.neuron == sendingNucleus); this.synapses.RemoveAll(synapse => synapse.neuron == sendingNucleus);
// } }
// #endregion Synapses #endregion Synapses
#region Update #region Update
@ -133,14 +129,23 @@ public abstract class Nucleus {
public virtual void UpdateNuclei() { public virtual void UpdateNuclei() {
} }
// /// <summary> /// <summary>
// /// Set the bias, recalculate the output and update all Nuclei receiving from this Nucleus /// Set the bias, recalculate the output and update all Nuclei receiving from this Nucleus
// /// </summary> /// </summary>
// /// <param name="inputValue"></param> /// <param name="inputValue"></param>
// public virtual void SetBias(Vector3 inputValue) { public virtual void SetBias(Vector3 inputValue) {
// this.bias = inputValue; this.bias = inputValue;
// this.parent.UpdateFromNucleus(this); this.parent.UpdateFromNucleus(this);
// } }
/// <summary>
/// Process an external stimulus
/// </summary>
/// <param name="inputValue">The value of the stimulus</param>
/// <param name="thingId">The id of the thing causing the stimulus</param>
/// <param name="thingName">The name of the thing causing the stimulus</param>
public virtual void ProcessStimulus(Vector3 inputValue) { //, int thingId = 0, string thingName = "") {
}
#endregion Update #endregion Update

View File

@ -1,11 +1,2 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 4310eea6ab77628b085387a226c1c386 guid: 4310eea6ab77628b085387a226c1c386
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,11 +1,2 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 334a58eafccd60cbdb32f719e9e861c6 guid: 334a58eafccd60cbdb32f719e9e861c6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -8,146 +8,134 @@ namespace NanoBrain {
/// </summary> /// </summary>
[CreateAssetMenu(menuName = "Passer/Cluster")] [CreateAssetMenu(menuName = "Passer/Cluster")]
public class ClusterPrefab : ScriptableObject { public class ClusterPrefab : ScriptableObject {
/// The nuclei in this cluster
[SerializeReference]
// This list should not include any clusters...
public List<Nucleus> nuclei = new();
public Cluster cluster; /// <summary>
/* /// The output of this cluster
/// The nuclei in this cluster /// </summary>
[SerializeReference] /// <deprecated>This only returens the first(default) nucleus. Use outputs[0] instead</deprecated>
// This list should not include any clusters... public virtual Nucleus output => this.nuclei[0] as Nucleus;
public List<Nucleus> nuclei = new();
/// <summary> /// <summary>
/// The output of this cluster /// The nuclei in this cluster which are meant for receiving signals from outside the cluster
/// </summary> /// </summary>
/// <deprecated>This only returens the first(default) nucleus. Use outputs[0] instead</deprecated> /// <remark>This is currently the nuclei which do not have any incoming synapse</remark>
public virtual Nucleus output => this.nuclei[0] as Nucleus; public List<Nucleus> _inputs = null;
public virtual List<Nucleus> inputs {
/// <summary> get {
/// The nuclei in this cluster which are meant for receiving signals from outside the cluster if (this._inputs == null) {
/// </summary> this._inputs = new();
/// <remark>This is currently the nuclei which do not have any incoming synapse</remark> foreach (Nucleus receptor in this.nuclei) {
public List<Nucleus> _inputs = null; if (receptor is Nucleus nucleus) {
public virtual List<Nucleus> inputs { // inputs have no incoming synapses yet.
get { if (nucleus.synapses.Count == 0)
if (this._inputs == null) { this._inputs.Add(nucleus);
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;
} }
} }
/// <summary> return this._inputs;
/// The nuclei in this cluster which are meant for sending signals onward }
/// </summary> }
private List<Nucleus> _outputs = null; /// <summary>
public List<Nucleus> outputs { /// The nuclei in this cluster which are meant for sending signals onward
get { /// </summary>
if (this._outputs == null) private List<Nucleus> _outputs = null;
RefreshOutputs(); public List<Nucleus> outputs {
return this._outputs; get {
} if (this._outputs == null)
} RefreshOutputs();
/// <summary> return this._outputs;
/// Redetermine the outpus in the cluster }
/// </summary> }
public void RefreshOutputs() { /// <summary>
this._outputs = new(); /// Redetermine the outpus in the cluster
foreach (Nucleus nucleus in this.nuclei) { /// </summary>
if (nucleus is Neuron neuron && neuron.receivers.Count == 0) public void RefreshOutputs() {
this._outputs.Add(nucleus); this._outputs = new();
} foreach (Nucleus nucleus in this.nuclei) {
} if (nucleus is Neuron neuron && neuron.receivers.Count == 0)
*/ this._outputs.Add(nucleus);
}
}
/// <summary> /// <summary>
/// Retrieve a nucleus in this cluster /// Retrieve a nucleus in this cluster
/// </summary> /// </summary>
/// <param name="nucleusName">The name of the nucleus</param> /// <param name="nucleusName">The name of the nucleus</param>
/// <returns>The Nucleus with the given name or null if no such Nucleus could be found</returns> /// <returns>The Nucleus with the given name or null if no such Nucleus could be found</returns>
public Nucleus GetNucleus(string nucleusName) { public Nucleus GetNucleus(string nucleusName) {
return cluster.GetNucleus(nucleusName); foreach (Nucleus nucleus in this.nuclei) {
// foreach (Nucleus nucleus in this.nuclei) { if (nucleus.name == nucleusName)
// if (nucleus.name == nucleusName) return nucleus;
// return nucleus; }
// } return null;
// return null;
} }
// Call this function to ensure that there is at least one nucleus // Call this function to ensure that there is at least one nucleus
// This is an invariant and should be ensured before the nucleus is used // This is an invariant and should be ensured before the nucleus is used
// because output requires it. // because output requires it.
public void EnsureInitialization() { public void EnsureInitialization() {
this.cluster.prefab = this; nuclei ??= new List<Nucleus>();
this.cluster.name = this.name; if (nuclei.Count == 0)
this.cluster.nuclei ??= new List<Nucleus>(); new Neuron(this, "Output"); // Every cluster should have at least 1 neuron
if (this.cluster.nuclei.Count <= 0)
new Neuron(this.cluster, "Output"); // Every cluster should have at least 1 neuron
this.cluster.instanceCount = 1;
// nuclei ??= new List<Nucleus>();
// if (nuclei.Count == 0)
// new Neuron(this, "Output"); // Every cluster should have at least 1 neuron
} }
public void GarbageCollection() { public void GarbageCollection() {
// HashSet<Nucleus> visitedNuclei = new(); HashSet<Nucleus> visitedNuclei = new();
// foreach (Nucleus output in this.outputs) foreach (Nucleus output in this.outputs)
// MarkNuclei(visitedNuclei, output); MarkNuclei(visitedNuclei, output);
// //Debug.Log($"Garbage collection found {visitedNuclei.Count} Nuclei"); //Debug.Log($"Garbage collection found {visitedNuclei.Count} Nuclei");
// this.nuclei.RemoveAll(nucleus => visitedNuclei.Contains(nucleus) == false); this.nuclei.RemoveAll(nucleus => visitedNuclei.Contains(nucleus) == false);
} }
// public void MarkNuclei(HashSet<Nucleus> visitedNuclei, Nucleus nucleus) { public void MarkNuclei(HashSet<Nucleus> visitedNuclei, Nucleus nucleus) {
// if (nucleus is null) if (nucleus is null)
// return; return;
// if (nucleus.parent != null && nucleus.parent.prefab != this) if (nucleus.parent != null && nucleus.parent.prefab != this)
// visitedNuclei.Add(nucleus.parent); visitedNuclei.Add(nucleus.parent);
// else else
// visitedNuclei.Add(nucleus); visitedNuclei.Add(nucleus);
// if (nucleus is Neuron neuron) { if (nucleus.synapses != null) {
// if (neuron.synapses != null) { HashSet<Synapse> visitedSynapses = new();
// HashSet<Synapse> visitedSynapses = new(); foreach (Synapse synapse in nucleus.synapses) {
// foreach (Synapse synapse in neuron.synapses) { if (synapse != null && synapse.neuron != null) {
// if (synapse != null && synapse.neuron != null) { visitedSynapses.Add(synapse);
// visitedSynapses.Add(synapse); if (synapse.neuron is Nucleus synapse_nucleus)
// if (synapse.neuron is Nucleus synapse_nucleus) MarkNuclei(visitedNuclei, synapse_nucleus);
// MarkNuclei(visitedNuclei, synapse_nucleus); }
// } }
// } nucleus.synapses.RemoveAll(synapse => visitedSynapses.Contains(synapse) == false);
// neuron.synapses.RemoveAll(synapse => visitedSynapses.Contains(synapse) == false); }
// } if (nucleus is Neuron neuron && neuron.receivers != null) {
// if (neuron.receivers != null) { HashSet<Nucleus> visitedReceivers = new();
// HashSet<Nucleus> visitedReceivers = new(); foreach (Nucleus receiver in neuron.receivers) {
// foreach (Nucleus receiver in neuron.receivers) { if (receiver != null && receiver != null) {
// if (receiver != null && receiver != null) { visitedReceivers.Add(receiver);
// visitedReceivers.Add(receiver); visitedNuclei.Add(receiver);
// visitedNuclei.Add(receiver); }
// } }
// } neuron.receivers.RemoveAll(receiver => visitedReceivers.Contains(receiver) == false);
// neuron.receivers.RemoveAll(receiver => visitedReceivers.Contains(receiver) == false); }
// } }
// }
// }
// public virtual void UpdateNuclei() { public virtual void UpdateNuclei() {
// foreach (Nucleus nucleus in this.nuclei) foreach (Nucleus nucleus in this.nuclei)
// nucleus.UpdateNuclei(); nucleus.UpdateNuclei();
// } }
// public int GetNucleusIndex(Nucleus receiver) { public int GetNucleusIndex(Nucleus receiver) {
// int ix = 0; int ix = 0;
// foreach (Nucleus nucleus in this.nuclei) { foreach (Nucleus nucleus in this.nuclei) {
// if (receiver == nucleus) if (receiver == nucleus)
// return ix; return ix;
// ix++; ix++;
// } }
// return -1; return -1;
// } }
} }
} }