Compare commits
No commits in common. "main" and "Training" have entirely different histories.
@ -27,12 +27,13 @@ namespace NanoBrain.Unity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clusterPrefab.cluster.name = clusterPrefab.name;
|
view.currentCluster ??= clusterPrefab.cluster;
|
||||||
clusterPrefab.cluster.Cleanup();
|
|
||||||
|
|
||||||
view.currentCluster = clusterPrefab.cluster;
|
|
||||||
view.currentNucleus = clusterPrefab.cluster.defaultOutput;
|
view.currentNucleus = clusterPrefab.cluster.defaultOutput;
|
||||||
view.selectedOutput = view.currentNucleus;
|
view.selectedOutput = view.currentNucleus;
|
||||||
|
|
||||||
|
clusterPrefab.cluster.name = clusterPrefab.name;
|
||||||
|
|
||||||
|
clusterPrefab.cluster.Cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnDisable() {
|
void OnDisable() {
|
||||||
@ -47,30 +48,28 @@ namespace NanoBrain.Unity {
|
|||||||
EditorGUI.BeginChangeCheck();
|
EditorGUI.BeginChangeCheck();
|
||||||
|
|
||||||
// Begin horizontal split
|
// Begin horizontal split
|
||||||
// EditorGUILayout.BeginHorizontal();
|
EditorGUILayout.BeginHorizontal();
|
||||||
|
|
||||||
// // Left: fixed-width drawing area
|
// Left: fixed-width drawing area
|
||||||
// GUILayoutOption[] leftOptions = { GUILayout.Width(drawAreaWidth) };
|
GUILayoutOption[] leftOptions = { GUILayout.Width(drawAreaWidth) };
|
||||||
// Rect drawRect = GUILayoutUtility.GetRect(drawAreaWidth, 450f, leftOptions); // height adjustable
|
Rect drawRect = GUILayoutUtility.GetRect(drawAreaWidth, 450f, leftOptions); // height adjustable
|
||||||
|
|
||||||
// // add padding inside rect
|
// add padding inside rect
|
||||||
// Rect innerRect = new(drawRect.x + padding, drawRect.y + padding,
|
Rect innerRect = new(drawRect.x + padding, drawRect.y + padding,
|
||||||
// drawRect.width - padding * 2, drawRect.height - padding * 2);
|
drawRect.width - padding * 2, drawRect.height - padding * 2);
|
||||||
|
|
||||||
// view.Render(innerRect);
|
view.Render(innerRect);
|
||||||
|
|
||||||
// Right: info panel (takes remaining width)
|
// Right: info panel (takes remaining width)
|
||||||
EditorGUILayout.BeginVertical(GUILayout.ExpandWidth(true));
|
EditorGUILayout.BeginVertical(GUILayout.ExpandWidth(true));
|
||||||
float prevLabelWidth = EditorGUIUtility.labelWidth;
|
float prevLabelWidth = EditorGUIUtility.labelWidth;
|
||||||
EditorGUIUtility.labelWidth = 100f; // smaller labels -> larger fields
|
EditorGUIUtility.labelWidth = 100f; // smaller labels -> larger fields
|
||||||
|
|
||||||
if (GUILayout.Button("Import"))
|
|
||||||
ImportJson();
|
|
||||||
InspectorHandler(serializedObject);
|
InspectorHandler(serializedObject);
|
||||||
|
|
||||||
EditorGUIUtility.labelWidth = prevLabelWidth;
|
EditorGUIUtility.labelWidth = prevLabelWidth;
|
||||||
EditorGUILayout.EndVertical(); // end right column
|
EditorGUILayout.EndVertical(); // end right column
|
||||||
// EditorGUILayout.EndHorizontal(); // end split
|
EditorGUILayout.EndHorizontal(); // end split
|
||||||
|
|
||||||
if (EditorGUI.EndChangeCheck()) {
|
if (EditorGUI.EndChangeCheck()) {
|
||||||
serializedObject.Update();
|
serializedObject.Update();
|
||||||
@ -84,35 +83,6 @@ namespace NanoBrain.Unity {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void ImportJson() {
|
|
||||||
string path = EditorUtility.OpenFilePanel(
|
|
||||||
"Import JSON",
|
|
||||||
"",
|
|
||||||
"json"
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(path) && System.IO.File.Exists(path)) {
|
|
||||||
ClusterData newClusterData = Cluster.Import(path);
|
|
||||||
clusterPrefab.cluster = new(newClusterData) {
|
|
||||||
name = clusterPrefab.name
|
|
||||||
};
|
|
||||||
clusterPrefab.cluster.Cleanup();
|
|
||||||
|
|
||||||
view.currentCluster = clusterPrefab.cluster;
|
|
||||||
view.currentNucleus = clusterPrefab.cluster.defaultOutput;
|
|
||||||
view.selectedOutput = view.currentNucleus;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool HasPreviewGUI() => true;
|
|
||||||
|
|
||||||
public override void OnPreviewGUI(Rect r, GUIStyle background) {
|
|
||||||
base.OnPreviewGUI(r, background);
|
|
||||||
GUI.Label(r, "welcome");
|
|
||||||
view.Render(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Inspector
|
#region Inspector
|
||||||
|
|
||||||
private bool showSynapses = true;
|
private bool showSynapses = true;
|
||||||
|
|||||||
@ -1,11 +1,2 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 879b585246de16598959c21b724cf105
|
guid: 879b585246de16598959c21b724cf105
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
|
|||||||
@ -316,7 +316,6 @@ namespace NanoBrain.Unity {
|
|||||||
foreach (Synapse synapse in receiverNeuron.synapses) {
|
foreach (Synapse synapse in receiverNeuron.synapses) {
|
||||||
Nucleus nucleus = synapse.neuron;
|
Nucleus nucleus = synapse.neuron;
|
||||||
if (nucleus.parent != null && currentNucleus != null && nucleus.parent != currentNucleus.parent) {
|
if (nucleus.parent != null && currentNucleus != null && nucleus.parent != currentNucleus.parent) {
|
||||||
// if (nucleus.parent != null && nucleus.parent != currentCluster) {
|
|
||||||
nucleus = nucleus.parent;
|
nucleus = nucleus.parent;
|
||||||
}
|
}
|
||||||
string nucleusName = nucleus.name;
|
string nucleusName = nucleus.name;
|
||||||
@ -625,18 +624,6 @@ namespace NanoBrain.Unity {
|
|||||||
|
|
||||||
if (nucleus.parent != null && this.currentNucleus != null && nucleus.parent != this.currentNucleus.parent && nucleus.parent is Cluster parentCluster1) {
|
if (nucleus.parent != null && this.currentNucleus != null && nucleus.parent != this.currentNucleus.parent && nucleus.parent is Cluster parentCluster1) {
|
||||||
// This neuron is part of another cluster
|
// This neuron is part of another cluster
|
||||||
if (nucleus is Cluster cluster) {
|
|
||||||
string clusterName = cluster.prefab.name;
|
|
||||||
|
|
||||||
int colonPos = clusterName.IndexOf(":");
|
|
||||||
string baseName;
|
|
||||||
if (colonPos > 0 && colonPos < clusterName.Length - 2)
|
|
||||||
baseName = clusterName[..colonPos] + "\n";
|
|
||||||
else
|
|
||||||
baseName = clusterName + "\n";
|
|
||||||
Handles.Label(labelPos, baseName + nucleus.name, style);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
parentCluster1.name ??= "";
|
parentCluster1.name ??= "";
|
||||||
int colonPos = parentCluster1.name.IndexOf(":");
|
int colonPos = parentCluster1.name.IndexOf(":");
|
||||||
string baseName;
|
string baseName;
|
||||||
@ -646,7 +633,6 @@ namespace NanoBrain.Unity {
|
|||||||
baseName = parentCluster1.name + "\n";
|
baseName = parentCluster1.name + "\n";
|
||||||
Handles.Label(labelPos, baseName + nucleus.name, style);
|
Handles.Label(labelPos, baseName + nucleus.name, style);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else {
|
else {
|
||||||
nucleus.name ??= "";
|
nucleus.name ??= "";
|
||||||
int colonPos = nucleus.name.IndexOf(":");
|
int colonPos = nucleus.name.IndexOf(":");
|
||||||
|
|||||||
@ -38,18 +38,13 @@ namespace NanoBrain.Unity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void InstantiateCluster(SerializedProperty property, ClusterView clusterView) {
|
private void InstantiateCluster(SerializedProperty property, ClusterView clusterView) {
|
||||||
if (property == null || clusterView.initialized)
|
if (property == null || property.serializedObject == null || clusterView.initialized)
|
||||||
return;
|
|
||||||
|
|
||||||
SerializedObject serializedObject = property.serializedObject;
|
|
||||||
if (serializedObject == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
UnityEngine.Object targetObject = serializedObject.targetObject;
|
|
||||||
if (targetObject == null)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
SerializedProperty prefabProp = property.FindPropertyRelative(nameof(Cluster.prefab));
|
SerializedProperty prefabProp = property.FindPropertyRelative(nameof(Cluster.prefab));
|
||||||
|
UnityEngine.Object targetObject = property.serializedObject.targetObject;
|
||||||
|
if (targetObject == null)
|
||||||
|
return;
|
||||||
|
|
||||||
Debug.Log($"Instantiate new Cluster for {targetObject.name}");
|
Debug.Log($"Instantiate new Cluster for {targetObject.name}");
|
||||||
|
|
||||||
@ -75,14 +70,11 @@ namespace NanoBrain.Unity {
|
|||||||
int indent = EditorGUI.indentLevel;
|
int indent = EditorGUI.indentLevel;
|
||||||
EditorGUI.indentLevel = 0;
|
EditorGUI.indentLevel = 0;
|
||||||
|
|
||||||
//SerializedProperty jsonProp = property.FindPropertyRelative(nameof(Cluster.jsonAsset));
|
|
||||||
|
|
||||||
SerializedProperty prefabProp = property.FindPropertyRelative(nameof(Cluster.prefab));
|
SerializedProperty prefabProp = property.FindPropertyRelative(nameof(Cluster.prefab));
|
||||||
|
|
||||||
// Draw the object field on the top line
|
// Draw the object field on the top line
|
||||||
Rect fieldRect = new(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight);
|
Rect fieldRect = new(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight);
|
||||||
EditorGUI.BeginChangeCheck();
|
EditorGUI.BeginChangeCheck();
|
||||||
//EditorGUI.PropertyField(fieldRect, jsonProp, label);
|
|
||||||
EditorGUI.PropertyField(fieldRect, prefabProp, label);
|
EditorGUI.PropertyField(fieldRect, prefabProp, label);
|
||||||
// If a new prefab has been selected
|
// If a new prefab has been selected
|
||||||
if (EditorGUI.EndChangeCheck()) {
|
if (EditorGUI.EndChangeCheck()) {
|
||||||
|
|||||||
@ -1,11 +1,2 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: ecce20d60829feced84788f7f9dcfe08
|
guid: ecce20d60829feced84788f7f9dcfe08
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
|
|||||||
2
Runtime/Scripts/ClusterJson.cs.meta
Normal file
2
Runtime/Scripts/ClusterJson.cs.meta
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 8ea9c456ab9da37daaa610edaadc38bb
|
||||||
@ -26,7 +26,6 @@ namespace NanoBrain {
|
|||||||
/// Cluster should always be created from prefabs
|
/// Cluster should always be created from prefabs
|
||||||
public ClusterPrefab prefab;
|
public ClusterPrefab prefab;
|
||||||
public ClusterJson json;
|
public ClusterJson json;
|
||||||
public TextAsset jsonAsset;
|
|
||||||
|
|
||||||
//[HideInInspector]
|
//[HideInInspector]
|
||||||
|
|
||||||
@ -70,86 +69,15 @@ namespace NanoBrain {
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// In a multi-cluster each instance can be used for a thing.
|
/// In a multi-cluster each instance can be used for a thing.
|
||||||
/// Cluster instance may also not (yet) be mapped to a thing.
|
/// Cluster instance may also not (yet) be mapped to a thing.
|
||||||
// [NonSerialized]
|
[NonSerialized]
|
||||||
// public Dictionary<int, Cluster> thingClusters = new();
|
public Dictionary<int, Cluster> thingClusters = new();
|
||||||
public int thingId;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// All nuclei in this cluster
|
/// All nuclei in this cluster
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[SerializeReference]
|
[SerializeReference]
|
||||||
[HideInInspector]
|
|
||||||
public List<Nucleus> nuclei = new();
|
public List<Nucleus> nuclei = new();
|
||||||
|
|
||||||
#region Timed Actions
|
|
||||||
|
|
||||||
public class TimedAction {
|
|
||||||
public TimedAction(string neuronName, Action action, long timestamp) {
|
|
||||||
this.neuronName = neuronName;
|
|
||||||
this.timestamp = timestamp;
|
|
||||||
this.action = action;
|
|
||||||
}
|
|
||||||
public string neuronName;
|
|
||||||
public long timestamp;
|
|
||||||
public Action action;
|
|
||||||
|
|
||||||
public static void AddTo(List<TimedAction> actions, string neuronName, Action action, long timestamp) {
|
|
||||||
int ix = -1;
|
|
||||||
int insertIx = actions.Count; // default: add at end
|
|
||||||
|
|
||||||
// Find existing item and the first item with timestamp > this.timestamp
|
|
||||||
for (int i = 0; i < actions.Count; i++) {
|
|
||||||
TimedAction item = actions[i];
|
|
||||||
|
|
||||||
if (ix < 0 && item.neuronName == neuronName) {
|
|
||||||
ix = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ix >= 0) {
|
|
||||||
// Debug.Log($"update {neuronName} {timestamp}");
|
|
||||||
actions[ix].action = action;
|
|
||||||
actions[ix].timestamp = timestamp;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debug.Log($"new {neuronName} {timestamp}");
|
|
||||||
// No existing item; compute insertion point (sorted by timestamp ascending)
|
|
||||||
for (int i = 0; i < actions.Count; i++) {
|
|
||||||
if (actions[i].timestamp > timestamp) {
|
|
||||||
insertIx = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TimedAction newAction = new(neuronName, action, timestamp);
|
|
||||||
actions.Insert(insertIx, newAction);
|
|
||||||
}
|
|
||||||
public static void Check(List<TimedAction> actions) {
|
|
||||||
long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
|
||||||
// if (actions.Count > 0) {
|
|
||||||
// Debug.Log($"{actions.Count} actions, {actions[0].neuronName} {actions[0].timestamp} {now} {actions[0].timestamp - now}");
|
|
||||||
// }
|
|
||||||
while (actions.Count > 0 && actions[0].timestamp <= now) {
|
|
||||||
Action action = actions[0].action;
|
|
||||||
actions.RemoveAt(0);
|
|
||||||
action();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public readonly List<TimedAction> actions = new();
|
|
||||||
public void CheckActions() {
|
|
||||||
TimedAction.Check(this.actions);
|
|
||||||
foreach (Nucleus nucleus in this.nuclei) {
|
|
||||||
if (nucleus is not Cluster cluster)
|
|
||||||
continue;
|
|
||||||
cluster.CheckActions();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion Timed Actions
|
|
||||||
|
|
||||||
#region Init
|
#region Init
|
||||||
|
|
||||||
public Cluster() {
|
public Cluster() {
|
||||||
@ -200,14 +128,8 @@ namespace NanoBrain {
|
|||||||
this.prefab = prefab;
|
this.prefab = prefab;
|
||||||
this.version = prefab.version;
|
this.version = prefab.version;
|
||||||
this.name = prefab.name;
|
this.name = prefab.name;
|
||||||
if (parent != null) {
|
if (parent != null)
|
||||||
TextAsset jsonFile = Resources.Load<TextAsset>(parent.name);
|
|
||||||
string json = jsonFile.text;
|
|
||||||
ClusterData clusterData = JsonUtility.FromJson<ClusterData>(json);
|
|
||||||
//ClusterData clusterData = Cluster.Import(jsonPath);
|
|
||||||
parent.cluster = new(clusterData);
|
|
||||||
this.parent = parent.cluster;
|
this.parent = parent.cluster;
|
||||||
}
|
|
||||||
|
|
||||||
ClonePrefab();
|
ClonePrefab();
|
||||||
}
|
}
|
||||||
@ -250,27 +172,21 @@ namespace NanoBrain {
|
|||||||
foreach (NeuronData neuronData in clusterData.neurons) {
|
foreach (NeuronData neuronData in clusterData.neurons) {
|
||||||
Neuron receiver = this.GetNeuron(neuronData.name);
|
Neuron receiver = this.GetNeuron(neuronData.name);
|
||||||
foreach (SynapseData synapseData in neuronData.synapses) {
|
foreach (SynapseData synapseData in neuronData.synapses) {
|
||||||
// If the synapse points to another cluster
|
|
||||||
if (synapseData.clusterName != this.name) {
|
if (synapseData.clusterName != this.name) {
|
||||||
// Add reference to external cluster
|
// Add reference to external cluster
|
||||||
// Do we know the external cluster already?
|
ClusterPrefab extPrefab = Resources.Load(synapseData.clusterName) as ClusterPrefab;
|
||||||
ExternalClusterData externalClusterData = clusterData.GetCluster(synapseData.clusterName);
|
if (extPrefab == null)
|
||||||
Cluster extCluster = this.GetCluster(synapseData.clusterName);
|
Debug.LogError($"Could not find cluster Resource {synapseData.clusterName}");
|
||||||
if (extCluster == null) {
|
else {
|
||||||
// if not: create new external cluster
|
uint instanceCount = 1;
|
||||||
Debug.Log("New ext cluster");
|
foreach (ExternalClusterData externalCluster in clusterData.clusters) {
|
||||||
ClusterPrefab extPrefab = Resources.Load(externalClusterData.prefabName) as ClusterPrefab;
|
if (externalCluster.name == synapseData.clusterName)
|
||||||
extCluster = new(extPrefab, this, instanceCount) {
|
instanceCount = externalCluster.instanceCount;
|
||||||
name = externalClusterData.name
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
extCluster.instanceCount = externalClusterData.instanceCount;
|
if (this.nuclei.Find(nucleus => nucleus.name == synapseData.clusterName) is not Cluster extCluster)
|
||||||
// Do we have the synapse already?
|
extCluster = new(extPrefab, this, instanceCount);
|
||||||
Neuron extNeuron = extCluster.GetNeuron(synapseData.neuronName);
|
Neuron extNeuron = extCluster.GetNeuron(synapseData.neuronName);
|
||||||
Synapse synapse = receiver.GetSynapse(extNeuron); //extCluster, synapseData.neuronName);
|
Debug.Log("Add receiver for external");
|
||||||
if (synapse == null) {
|
|
||||||
Debug.Log("new receiver for external");
|
|
||||||
// If not: create new synapse
|
|
||||||
extNeuron.AddReceiver(receiver);
|
extNeuron.AddReceiver(receiver);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -282,6 +198,7 @@ namespace NanoBrain {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Clone a prefab.
|
/// Clone a prefab.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -372,71 +289,6 @@ namespace NanoBrain {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Cluster Copy() {
|
|
||||||
Cluster clone = new() {
|
|
||||||
name = this.name,
|
|
||||||
prefab = this.prefab,
|
|
||||||
version = this.version,
|
|
||||||
parent = this.parent
|
|
||||||
};
|
|
||||||
if (clone.parent != null) {
|
|
||||||
// Do we need to clone the parent here too????
|
|
||||||
}
|
|
||||||
|
|
||||||
// first clone the nuclei without their connections
|
|
||||||
foreach (Nucleus nucleus in this.nuclei)
|
|
||||||
nucleus.ShallowCloneTo(clone);
|
|
||||||
|
|
||||||
Nucleus[] clonedNuclei = clone.nuclei.ToArray();
|
|
||||||
// Now clone the connections
|
|
||||||
for (int nucleusIx = 0; nucleusIx < this.nuclei.Count; nucleusIx++) {
|
|
||||||
Nucleus sourceNucleus = this.nuclei[nucleusIx];
|
|
||||||
if (sourceNucleus is not Neuron sourceNeuron)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
Nucleus clonedNucleus = clonedNuclei[nucleusIx];
|
|
||||||
if (clonedNucleus == null || clonedNucleus is not Neuron clonedNeuron)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
foreach (Synapse sourceSynapse in sourceNeuron.synapses) {
|
|
||||||
Neuron synapseNeuron = sourceSynapse.neuron;
|
|
||||||
if (synapseNeuron.parent.prefab != null && synapseNeuron.parent.prefab != clone.prefab) {
|
|
||||||
// Neuron is in another cluster, find the cloned cluster first
|
|
||||||
Cluster sourceCluster = synapseNeuron.parent;
|
|
||||||
//Cluster clonedCluster = clone.nuclei.Find(n => n.name == sourceCluster.name) as Cluster;
|
|
||||||
Nucleus clonedClusterNucleus = clone.nuclei.Find(n => n.name == sourceCluster.name);
|
|
||||||
if (clonedClusterNucleus is not Cluster clonedCluster)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Now find the neuron in that cloned cluster
|
|
||||||
int neuronIx = GetNucleusIndex(sourceCluster.nuclei, sourceSynapse.neuron.name);
|
|
||||||
if (neuronIx < 0)
|
|
||||||
// Could not find the neuron in the prefab cluster
|
|
||||||
continue;
|
|
||||||
if (clonedCluster.nuclei[neuronIx] is not Neuron clonedSender)
|
|
||||||
// Could not find the neuron in the cloned cluster
|
|
||||||
continue;
|
|
||||||
|
|
||||||
clonedSender.AddReceiver(clonedNeuron, sourceSynapse.weight, sourceSynapse.trainable);
|
|
||||||
//Debug.Log($"Add synapse {clonedCluster.name}.{clonedSender.name} -> {clonedNeuron.name} [{clonedSender.receivers.Count}]");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
int ix = GetNucleusIndex(clone.prefab.cluster.nuclei, sourceSynapse.neuron);
|
|
||||||
if (ix < 0)
|
|
||||||
continue;
|
|
||||||
if (clonedNuclei[ix] is not Neuron clonedSender)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Copy the receivers which will also create the synapse
|
|
||||||
clonedSender.AddReceiver(clonedNeuron, sourceSynapse.weight, sourceSynapse.trainable);
|
|
||||||
// Debug.Log($"Add synapse {clonedSender.name} -> {clonedNeuron.name}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return clone;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// \copydoc NanoBrain::Nucleus::ShallowCloneTo
|
/// \copydoc NanoBrain::Nucleus::ShallowCloneTo
|
||||||
public override Nucleus ShallowCloneTo(Cluster parent) {
|
public override Nucleus ShallowCloneTo(Cluster parent) {
|
||||||
// Clusters should not be cloned, but instantiated from the prefab....
|
// Clusters should not be cloned, but instantiated from the prefab....
|
||||||
@ -493,8 +345,8 @@ namespace NanoBrain {
|
|||||||
public static int GetNucleusIndex(List<Nucleus> nuclei, Nucleus nucleus) {
|
public static int GetNucleusIndex(List<Nucleus> nuclei, Nucleus nucleus) {
|
||||||
int i = 0;
|
int i = 0;
|
||||||
foreach (Nucleus nucleiElement in nuclei) {
|
foreach (Nucleus nucleiElement in nuclei) {
|
||||||
// if (nucleiElement == nucleus)
|
//for (int i = 0; i < nuclei.Length; i++) {
|
||||||
if (nucleiElement.name == nucleus.name)
|
if (nucleiElement == nucleus)
|
||||||
return i;
|
return i;
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
@ -518,37 +370,6 @@ namespace NanoBrain {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes all trainable weights to random values
|
|
||||||
/// </summary>
|
|
||||||
public void InitializeRandom() {
|
|
||||||
System.Random randomGenerator = new();
|
|
||||||
foreach (Nucleus nucleus in this.nuclei) {
|
|
||||||
if (nucleus is Neuron neuron) {
|
|
||||||
foreach (Synapse synapse in neuron.synapses) {
|
|
||||||
if (synapse.trainable) {
|
|
||||||
synapse.weight = (float)randomGenerator.NextDouble() * 2.0f - 1.0f;
|
|
||||||
// Debug.Log($"{neuron.name}-{synapse.neuron.name} weight = {synapse.weight}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void GaussianAdditiveMutation(float sigma) {
|
|
||||||
foreach (Nucleus nucleus in this.nuclei) {
|
|
||||||
if (nucleus is Neuron neuron) {
|
|
||||||
foreach (Synapse synapse in neuron.synapses) {
|
|
||||||
if (synapse.trainable) {
|
|
||||||
synapse.GaussianAdditiveMutation(sigma);
|
|
||||||
//synapse.weight = (float)randomGenerator.NextDouble() * 2.0f - 1.0f;
|
|
||||||
// Debug.Log($"{neuron.name}-{synapse.neuron.name} weight = {synapse.weight}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion Init
|
#endregion Init
|
||||||
|
|
||||||
#region Cluster Array
|
#region Cluster Array
|
||||||
@ -622,20 +443,18 @@ namespace NanoBrain {
|
|||||||
/// Remove a mapping from a thing to a cluster such that it becomes available for new things
|
/// Remove a mapping from a thing to a cluster such that it becomes available for new things
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="cluster">The multi-cluster instance which not no longer be mapped</param>
|
/// <param name="cluster">The multi-cluster instance which not no longer be mapped</param>
|
||||||
public void RemoveThingCluster(Cluster cluster) {
|
private void RemoveThingCluster(Cluster cluster) {
|
||||||
cluster.thingId = 0;
|
List<int> keysToRemove = new();
|
||||||
// int keyToRemove = -1;
|
foreach (KeyValuePair<int, Cluster> kvp in thingClusters) {
|
||||||
// foreach (KeyValuePair<int, Cluster> kvp in this.thingClusters) {
|
if (kvp.Value == cluster)
|
||||||
// if (kvp.Value == cluster) {
|
keysToRemove.Add(kvp.Key);
|
||||||
// keyToRemove = kvp.Key;
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (keyToRemove >= 0)
|
|
||||||
// this.thingClusters.Remove(keyToRemove);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (int thingId in keysToRemove)
|
||||||
|
thingClusters.Remove(thingId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public List<Neuron> GetAllInstances(Neuron nucleus) {
|
public List<Neuron> GetAllInstances(Neuron nucleus) {
|
||||||
List<Neuron> allInstances = new();
|
List<Neuron> allInstances = new();
|
||||||
|
|
||||||
@ -656,6 +475,28 @@ namespace NanoBrain {
|
|||||||
return allInstances;
|
return allInstances;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// public void Backpropagation(Func<Cluster, float> Observer, float target, float learningRate) {
|
||||||
|
// foreach (Nucleus nucleus in this.instances[0].nuclei) {
|
||||||
|
// if (nucleus is not Neuron neuron)
|
||||||
|
// continue;
|
||||||
|
|
||||||
|
// foreach (Synapse synapse in neuron.synapses) {
|
||||||
|
// List<Neuron> allSynapseNeurons = GetAllInstances(synapse.neuron);
|
||||||
|
|
||||||
|
// Vector3 dSSRdW = Vector3.zero;
|
||||||
|
// for (int clusterIx = 0; clusterIx < this.instances.Length; clusterIx++) {
|
||||||
|
// Cluster clusterInstance = this.instances[clusterIx];
|
||||||
|
// Neuron neuronInstance = allSynapseNeurons[clusterIx];
|
||||||
|
|
||||||
|
// // Simple case, without receivers...
|
||||||
|
// dSSRdW += (Vector3)(-2 * (Observer(clusterInstance) - target) * neuronInstance.activation);
|
||||||
|
// }
|
||||||
|
// synapse.weight += learningRate * dSSRdW.magnitude;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
#endregion ClusterArray
|
#endregion ClusterArray
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -671,7 +512,7 @@ namespace NanoBrain {
|
|||||||
if (_computeOrders == null || _computeOrders.Count == 0) {
|
if (_computeOrders == null || _computeOrders.Count == 0) {
|
||||||
_computeOrders = new();
|
_computeOrders = new();
|
||||||
foreach (Nucleus nucleus in this.nuclei)
|
foreach (Nucleus nucleus in this.nuclei)
|
||||||
_computeOrders[nucleus] = TopologicalSort(nucleus);
|
_computeOrders[nucleus] = TopologicalSort2(nucleus);
|
||||||
}
|
}
|
||||||
return _computeOrders;
|
return _computeOrders;
|
||||||
}
|
}
|
||||||
@ -683,7 +524,7 @@ namespace NanoBrain {
|
|||||||
this._computeOrders = null;
|
this._computeOrders = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Nucleus> TopologicalSort(Nucleus startNode) {
|
private List<Nucleus> TopologicalSort2(Nucleus startNode) {
|
||||||
Dictionary<Nucleus, int> inDegree = new();
|
Dictionary<Nucleus, int> inDegree = new();
|
||||||
//HashSet<Nucleus> visited = new();
|
//HashSet<Nucleus> visited = new();
|
||||||
|
|
||||||
@ -868,18 +709,13 @@ namespace NanoBrain {
|
|||||||
/// <returns>The found neuron or null when it is not found</returns>
|
/// <returns>The found neuron or null when it is not found</returns>
|
||||||
/// The cluster instance mapped to the thing will be neuron.parent if a neuron is found.
|
/// The cluster instance mapped to the thing will be neuron.parent if a neuron is found.
|
||||||
public Neuron GetNeuron(int thingId, string neuronName, string thingName = null) {
|
public Neuron GetNeuron(int thingId, string neuronName, string thingName = null) {
|
||||||
// If this is not an ClusterArray, just take the neuron
|
|
||||||
if (this.instances == null || this.instances.Length <= 1)
|
if (this.instances == null || this.instances.Length <= 1)
|
||||||
return this.GetNeuron(neuronName);
|
return this.GetNeuron(neuronName);
|
||||||
|
|
||||||
// See if we are already using a cluster for thingId
|
// See if we are already using a cluster for thingId
|
||||||
// thingClusters ??= new();
|
thingClusters ??= new();
|
||||||
// if (thingClusters.TryGetValue(thingId, out Cluster cluster))
|
if (thingClusters.TryGetValue(thingId, out Cluster cluster))
|
||||||
// return cluster.GetNeuron(neuronName);
|
return cluster.GetNeuron(neuronName);
|
||||||
foreach (Cluster sibling in this.instances) {
|
|
||||||
if (sibling.thingId == thingId)
|
|
||||||
return sibling.GetNeuron(neuronName);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the cluster with the lowest value neuron
|
// Find the cluster with the lowest value neuron
|
||||||
Neuron lowestNeuron = null;
|
Neuron lowestNeuron = null;
|
||||||
@ -891,8 +727,7 @@ namespace NanoBrain {
|
|||||||
Cluster selectedCluster = lowestNeuron.parent;
|
Cluster selectedCluster = lowestNeuron.parent;
|
||||||
RemoveThingCluster(selectedCluster);
|
RemoveThingCluster(selectedCluster);
|
||||||
selectedCluster.name = baseName + ": " + thingName;
|
selectedCluster.name = baseName + ": " + thingName;
|
||||||
//thingClusters[thingId] = selectedCluster;
|
thingClusters[thingId] = selectedCluster;
|
||||||
selectedCluster.thingId = thingId;
|
|
||||||
return lowestNeuron;
|
return lowestNeuron;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -917,65 +752,6 @@ namespace NanoBrain {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool EqualStructure(Cluster cluster1, Cluster cluster2) {
|
|
||||||
int n1 = cluster1.nuclei.Count;
|
|
||||||
int n2 = cluster2.nuclei.Count;
|
|
||||||
if (n1 != n2)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
for (int i = 0; i < cluster1.nuclei.Count; i++) {
|
|
||||||
if (EqualStructure(cluster1.nuclei[i], cluster2.nuclei[i]) == false)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
public bool CopyWeightsFrom(Cluster source) {
|
|
||||||
int thisNucleiCount = this.nuclei.Count;
|
|
||||||
int sourceNucleiCount = source.nuclei.Count;
|
|
||||||
if (thisNucleiCount != sourceNucleiCount)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
for (int i = 0; i < this.nuclei.Count; i++) {
|
|
||||||
if (this.nuclei[i] is Neuron thisNeuron &&
|
|
||||||
source.nuclei[i] is Neuron sourceNeuron) {
|
|
||||||
|
|
||||||
if (thisNeuron.CopyWeightsFrom(sourceNeuron) == false)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ProcessWeightsFrom(Cluster source, Func<float, float, float> processor) {
|
|
||||||
if (processor is null)
|
|
||||||
throw new ArgumentNullException(nameof(processor));
|
|
||||||
if (source is null)
|
|
||||||
throw new ArgumentNullException(nameof(source));
|
|
||||||
int thisNucleiCount = this.nuclei.Count;
|
|
||||||
int sourceNucleiCount = source.nuclei.Count;
|
|
||||||
if (thisNucleiCount != sourceNucleiCount)
|
|
||||||
throw new ArgumentException("Lists must have the same length.", nameof(source));
|
|
||||||
|
|
||||||
for (int i = 0; i < thisNucleiCount; i++) {
|
|
||||||
if (this.nuclei[i] is Neuron thisNeuron &&
|
|
||||||
source.nuclei[i] is Neuron sourceNeuron) {
|
|
||||||
|
|
||||||
thisNeuron.ProcessWeightsFrom(sourceNeuron, processor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ProcessWeights(Func<float, float> processor) {
|
|
||||||
if (processor is null)
|
|
||||||
throw new ArgumentNullException(nameof(processor));
|
|
||||||
|
|
||||||
for (int i = 0; i < this.nuclei.Count; i++) {
|
|
||||||
if (this.nuclei[i] is Neuron thisNeuron)
|
|
||||||
thisNeuron.ProcessWeight(processor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Receivers
|
#region Receivers
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -1040,9 +816,7 @@ namespace NanoBrain {
|
|||||||
|
|
||||||
List<Nucleus> computeOrder = this.computeOrders[startNucleus];
|
List<Nucleus> computeOrder = this.computeOrders[startNucleus];
|
||||||
foreach (Nucleus nucleus in computeOrder) {
|
foreach (Nucleus nucleus in computeOrder) {
|
||||||
if (nucleus is Cluster)
|
if (nucleus is not Cluster) {
|
||||||
continue;
|
|
||||||
|
|
||||||
nucleus.UpdateStateIsolated();
|
nucleus.UpdateStateIsolated();
|
||||||
if (nucleus is Neuron neuron) {
|
if (nucleus is Neuron neuron) {
|
||||||
foreach (Nucleus receiver in neuron.receivers) {
|
foreach (Nucleus receiver in neuron.receivers) {
|
||||||
@ -1054,6 +828,7 @@ namespace NanoBrain {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// \copydoc NanoBrain::Nucleus::UpdateStateIsolated
|
/// \copydoc NanoBrain::Nucleus::UpdateStateIsolated
|
||||||
public override void UpdateStateIsolated() {
|
public override void UpdateStateIsolated() {
|
||||||
@ -1090,10 +865,10 @@ namespace NanoBrain {
|
|||||||
#region Serialization
|
#region Serialization
|
||||||
|
|
||||||
public void Export(string path) {
|
public void Export(string path) {
|
||||||
ClusterData data = new(this);
|
ClusterData data = new(this); //this.ToJSON();
|
||||||
string json = JsonUtility.ToJson(data, prettyPrint: true);
|
string json = JsonUtility.ToJson(data, prettyPrint: true);
|
||||||
|
|
||||||
Debug.Log($"Exporting {this.name} to {path}");
|
// Debug.Log($"Exporting json to {path}");
|
||||||
File.WriteAllText(path, json);
|
File.WriteAllText(path, json);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1115,41 +890,30 @@ namespace NanoBrain {
|
|||||||
public ClusterData(Cluster cluster) {
|
public ClusterData(Cluster cluster) {
|
||||||
this.name = cluster.name;
|
this.name = cluster.name;
|
||||||
foreach (Nucleus nucleus in cluster.nuclei) {
|
foreach (Nucleus nucleus in cluster.nuclei) {
|
||||||
if (nucleus is not Neuron neuron)
|
if (nucleus is Neuron neuron) {
|
||||||
continue;
|
|
||||||
|
|
||||||
NeuronData neuronData = new(neuron);
|
NeuronData neuronData = new(neuron);
|
||||||
this.neurons.Add(neuronData);
|
this.neurons.Add(neuronData);
|
||||||
foreach (Synapse synapse in neuron.synapses) {
|
foreach (Synapse synapse in neuron.synapses) {
|
||||||
if (synapse.neuron.parent.baseName == cluster.name)
|
if (synapse.neuron.parent.name != cluster.name) {
|
||||||
continue;
|
|
||||||
|
|
||||||
ExternalClusterData clusterData = new(synapse.neuron.parent);
|
ExternalClusterData clusterData = new(synapse.neuron.parent);
|
||||||
if (GetCluster(clusterData.name) == null)
|
if (this.clusters.Find(data => data.name == clusterData.name) == null) {
|
||||||
this.clusters.Add(clusterData);
|
this.clusters.Add(clusterData);
|
||||||
}
|
//Debug.Log("Add cluster");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ExternalClusterData GetCluster(string clusterName) {
|
|
||||||
foreach (ExternalClusterData cluster in this.clusters) {
|
|
||||||
if (cluster.name == clusterName)
|
|
||||||
return cluster;
|
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Serializable]
|
[Serializable]
|
||||||
public class ExternalClusterData {
|
public class ExternalClusterData {
|
||||||
public string name;
|
public string name;
|
||||||
public string prefabName;
|
|
||||||
public uint instanceCount;
|
public uint instanceCount;
|
||||||
|
|
||||||
public ExternalClusterData(Cluster cluster) {
|
public ExternalClusterData(Cluster cluster) {
|
||||||
this.name = cluster.baseName;
|
this.name = cluster.name;
|
||||||
this.prefabName = cluster.prefab.name;
|
|
||||||
this.instanceCount = (uint)cluster.instanceCount;
|
this.instanceCount = (uint)cluster.instanceCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
#if UNITY_MATHEMATICS
|
#if UNITY_MATHEMATICS
|
||||||
using Unity.Mathematics;
|
using Unity.Mathematics;
|
||||||
@ -37,7 +35,6 @@ namespace NanoBrain {
|
|||||||
this.parent.nuclei ??= new();
|
this.parent.nuclei ??= new();
|
||||||
this.parent.nuclei.Add(this);
|
this.parent.nuclei.Add(this);
|
||||||
}
|
}
|
||||||
this.resetStimulus = ResetStimulus;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Serialization
|
#region Serialization
|
||||||
@ -98,12 +95,6 @@ namespace NanoBrain {
|
|||||||
|
|
||||||
return this.GetSynapse(sender);
|
return this.GetSynapse(sender);
|
||||||
}
|
}
|
||||||
public Synapse GetSynapse(Cluster cluster, string senderNeuronName) {
|
|
||||||
Neuron sender = cluster.GetNeuron(senderNeuronName);
|
|
||||||
if (sender == null)
|
|
||||||
return null;
|
|
||||||
return this.GetSynapse(sender);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Remove a synapse from a Nucleus
|
/// Remove a synapse from a Nucleus
|
||||||
@ -121,7 +112,7 @@ namespace NanoBrain {
|
|||||||
/// <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.lastUpdate = Time.time;
|
this.lastUpdate = Time.time;
|
||||||
this.parent?.UpdateFromNucleus(this);
|
this.parent?.UpdateFromNucleus(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,16 +175,11 @@ namespace NanoBrain {
|
|||||||
/// The output value of the neuron
|
/// The output value of the neuron
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual float3 outputValue {
|
public virtual float3 outputValue {
|
||||||
get {
|
get { return _outputValue; }
|
||||||
this.parent.CheckActions();
|
|
||||||
return _outputValue;
|
|
||||||
}
|
|
||||||
set {
|
set {
|
||||||
_outputValue = value;
|
_outputValue = value;
|
||||||
if (this.isFiring)
|
if (this.isFiring)
|
||||||
WhenFiring?.Invoke();
|
WhenFiring?.Invoke();
|
||||||
else
|
|
||||||
WhenNotFiring?.Invoke();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public float3 activation => outputValue;
|
public float3 activation => outputValue;
|
||||||
@ -242,39 +228,37 @@ namespace NanoBrain {
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public Action WhenFiring;
|
public Action WhenFiring;
|
||||||
|
|
||||||
public Action WhenNotFiring;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// When true, the value will not be reset after timeToSleep.
|
/// When true, the value will not be reset after timeToSleep.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
//public bool persistOutput = false;
|
public bool persistOutput = false;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// True when the neuron is not persisting and has not be updated for timeToSleep seconds
|
/// True when the neuron is not persisting and has not be updated for timeToSleep seconds
|
||||||
/// </summary>
|
/// </summary>
|
||||||
//public virtual bool isSleeping => false; //!persistOutput && (Time.time - this.lastUpdate > timeToSleep);
|
public virtual bool isSleeping => !persistOutput && (Time.time - this.lastUpdate > timeToSleep);
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Check if the neuron is sleeping.
|
/// Check if the neuron is sleeping.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// This will reset the output value if it is sleeping
|
/// This will reset the output value if it is sleeping
|
||||||
// public void SleepCheck() {
|
public void SleepCheck() {
|
||||||
// if (this.isSleeping && this.outputSqrMagnitude > 0) {
|
if (this.isSleeping && this.outputSqrMagnitude > 0) {
|
||||||
// #if UNITY_MATHEMATICS
|
#if UNITY_MATHEMATICS
|
||||||
// this._outputValue = new float3(0, 0, 0);
|
this._outputValue = new float3(0, 0, 0);
|
||||||
// #else
|
#else
|
||||||
// this._outputValue = new Vector3(0,0,0);
|
this._outputValue = new Vector3(0,0,0);
|
||||||
// #endif
|
#endif
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The time at which the last update has been done
|
/// The time at which the last update has been done
|
||||||
/// </summary>
|
/// </summary>
|
||||||
// [HideInInspector]
|
[HideInInspector]
|
||||||
// public float lastUpdate = 0;
|
public float lastUpdate = 0;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Time in seconds after the last update the neuron can go to sleep
|
/// Time in seconds after the last update the neuron can go to sleep
|
||||||
/// </summary>
|
/// </summary>
|
||||||
// public static readonly float timeToSleep = 0.5f;
|
public static readonly float timeToSleep = 0.5f;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// When true, Unity will pause exection when this neuron is updated
|
/// When true, Unity will pause exection when this neuron is updated
|
||||||
@ -298,63 +282,12 @@ namespace NanoBrain {
|
|||||||
protected virtual void CloneFields(Neuron clone) {
|
protected virtual void CloneFields(Neuron clone) {
|
||||||
clone.bias = this.bias;
|
clone.bias = this.bias;
|
||||||
clone.trainableBias = this.trainableBias;
|
clone.trainableBias = this.trainableBias;
|
||||||
//clone.persistOutput = this.persistOutput;
|
clone.persistOutput = this.persistOutput;
|
||||||
clone.combinator = this.combinator;
|
clone.combinator = this.combinator;
|
||||||
clone.activator = this.activator;
|
clone.activator = this.activator;
|
||||||
clone.breakOnUpdate = this.breakOnUpdate;
|
clone.breakOnUpdate = this.breakOnUpdate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool EqualStructure(Neuron neuron1, Neuron neuron2) {
|
|
||||||
int synapseCount1 = neuron1.synapses.Count;
|
|
||||||
int synapseCount2 = neuron2.synapses.Count;
|
|
||||||
if (synapseCount1 != synapseCount2)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
for (int i = 0; i < synapseCount1; i++) {
|
|
||||||
if (Synapse.EqualStructure(neuron1.synapses[i], neuron2.synapses[i]) == false)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool CopyWeightsFrom(Neuron source) {
|
|
||||||
int thisSynapseCount = this.synapses.Count;
|
|
||||||
int sourceSynapseCount = source.synapses.Count;
|
|
||||||
if (thisSynapseCount != sourceSynapseCount)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
for (int i = 0; i < thisSynapseCount; i++) {
|
|
||||||
Synapse thisSynapse = this.synapses[i];
|
|
||||||
if (thisSynapse.trainable) {
|
|
||||||
Synapse sourceSynapse = source.synapses[i];
|
|
||||||
thisSynapse.weight = sourceSynapse.weight;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ProcessWeightsFrom(Neuron source, Func<float, float, float> processor) {
|
|
||||||
int thisSynapseCount = this.synapses.Count;
|
|
||||||
int sourceSynapseCount = source.synapses.Count;
|
|
||||||
if (thisSynapseCount != sourceSynapseCount)
|
|
||||||
return;
|
|
||||||
|
|
||||||
for (int i = 0; i < thisSynapseCount; i++) {
|
|
||||||
Synapse thisSynapse = this.synapses[i];
|
|
||||||
if (thisSynapse.trainable) {
|
|
||||||
Synapse sourceSynapse = source.synapses[i];
|
|
||||||
thisSynapse.weight = processor(thisSynapse.weight, sourceSynapse.weight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ProcessWeight(Func<float, float> processor) {
|
|
||||||
foreach (Synapse thisSynapse in this.synapses) {
|
|
||||||
if (thisSynapse.trainable)
|
|
||||||
thisSynapse.weight = processor(thisSynapse.weight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Delete the give neuron
|
/// Delete the give neuron
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -408,7 +341,7 @@ namespace NanoBrain {
|
|||||||
}
|
}
|
||||||
this.combination = Combinator(this.bias, this.synapses);
|
this.combination = Combinator(this.bias, this.synapses);
|
||||||
this.outputValue = Activator(this.combination);
|
this.outputValue = Activator(this.combination);
|
||||||
// this.lastUpdate = Time.time;
|
this.lastUpdate = Time.time;
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Combinator
|
#region Combinator
|
||||||
@ -443,8 +376,8 @@ namespace NanoBrain {
|
|||||||
public static float3 CombinatorSum(float3 bias, List<Synapse> synapses) {
|
public static float3 CombinatorSum(float3 bias, List<Synapse> synapses) {
|
||||||
float3 sum = bias;
|
float3 sum = bias;
|
||||||
foreach (Synapse synapse in synapses) {
|
foreach (Synapse synapse in synapses) {
|
||||||
// synapse.neuron.SleepCheck();
|
synapse.neuron.SleepCheck();
|
||||||
sum += synapse.weight * synapse.neuron._outputValue;
|
sum += synapse.weight * synapse.neuron.outputValue;
|
||||||
}
|
}
|
||||||
return sum;
|
return sum;
|
||||||
}
|
}
|
||||||
@ -458,8 +391,8 @@ namespace NanoBrain {
|
|||||||
public static float3 CombinatorProduct(float3 bias, List<Synapse> synapses) {
|
public static float3 CombinatorProduct(float3 bias, List<Synapse> synapses) {
|
||||||
float3 product = bias;
|
float3 product = bias;
|
||||||
foreach (Synapse synapse in synapses) {
|
foreach (Synapse synapse in synapses) {
|
||||||
// synapse.neuron.SleepCheck();
|
synapse.neuron.SleepCheck();
|
||||||
product *= synapse.weight * synapse.neuron._outputValue;
|
product *= synapse.weight * synapse.neuron.outputValue;
|
||||||
}
|
}
|
||||||
return product;
|
return product;
|
||||||
}
|
}
|
||||||
@ -796,32 +729,14 @@ namespace NanoBrain {
|
|||||||
|
|
||||||
#endregion Back propagation
|
#endregion Back propagation
|
||||||
|
|
||||||
private CancellationTokenSource _cts;
|
|
||||||
private Action resetStimulus;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Process an external stimulus
|
/// Process an external stimulus
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="inputValue">The value of the stimulus</param>
|
/// <param name="inputValue">The value of the stimulus</param>
|
||||||
public virtual void ProcessStimulus(Vector3 inputValue, float autoResetDelay = 0) {
|
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);
|
||||||
this.resetStimulus ??= ResetStimulus;
|
|
||||||
|
|
||||||
if (autoResetDelay > 0) {
|
|
||||||
long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
|
||||||
long resetTime = now + (long)(autoResetDelay * 1000.0f);
|
|
||||||
Cluster.TimedAction.AddTo(this.parent.actions, this.parent.name + "." + this.name, this.resetStimulus, resetTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ResetStimulus() {
|
|
||||||
// Debug.Log($"reset stimulus {this.parent.name + "." +this.name}");
|
|
||||||
this.parent.name = this.parent.baseName;
|
|
||||||
this.bias = Vector3.zero;
|
|
||||||
this.parent.thingId = 0;
|
|
||||||
this.parent?.UpdateFromNucleus(this);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -845,11 +760,6 @@ namespace NanoBrain {
|
|||||||
this.activationType = neuron.activator;
|
this.activationType = neuron.activator;
|
||||||
|
|
||||||
foreach (Synapse synapse in neuron.synapses) {
|
foreach (Synapse synapse in neuron.synapses) {
|
||||||
// Check whether synapse exists already first
|
|
||||||
if (this.synapses.Find(s => s.neuronName == synapse.neuron.name) != null)
|
|
||||||
// But then: what to do with the weights???? :-)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
SynapseData synapseData = new(synapse);
|
SynapseData synapseData = new(synapse);
|
||||||
this.synapses.Add(synapseData);
|
this.synapses.Add(synapseData);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -50,33 +50,6 @@ namespace NanoBrain {
|
|||||||
|
|
||||||
#endregion Update
|
#endregion Update
|
||||||
|
|
||||||
public static bool EqualStructure(Nucleus nucleus1, Nucleus nucleus2) {
|
|
||||||
if (nucleus1.parent != null && nucleus2.parent != null) {
|
|
||||||
if (nucleus1.parent.baseName != nucleus2.parent.baseName)
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
// mainly to check one is null, other is not.
|
|
||||||
if (nucleus1.parent != nucleus2.parent)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nucleus1 is Neuron neuron1) {
|
|
||||||
if (nucleus2 is not Neuron neuron2)
|
|
||||||
return false;
|
|
||||||
if (neuron1.name != neuron2.name)
|
|
||||||
return false;
|
|
||||||
return Neuron.EqualStructure(neuron1, neuron2);
|
|
||||||
}
|
|
||||||
else if (nucleus1 is Cluster cluster1) {
|
|
||||||
if (nucleus2 is not Cluster cluster2)
|
|
||||||
return false;
|
|
||||||
if (cluster1.baseName != cluster2.baseName)
|
|
||||||
return false;
|
|
||||||
return Cluster.EqualStructure(cluster1, cluster2);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -1,185 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace NanoBrain {
|
|
||||||
public class Population {
|
|
||||||
|
|
||||||
# region Members
|
|
||||||
|
|
||||||
public class Member {
|
|
||||||
public Member(Cluster cluster) {
|
|
||||||
this.brain = cluster;
|
|
||||||
this.performance = 0;
|
|
||||||
this.initialized = true;
|
|
||||||
}
|
|
||||||
public Cluster brain;
|
|
||||||
public float performance;
|
|
||||||
public bool initialized;
|
|
||||||
}
|
|
||||||
public List<Member> members = new();
|
|
||||||
|
|
||||||
public Member AddMember(Cluster brain) {
|
|
||||||
Member newMember = new(brain);
|
|
||||||
this.members.Add(newMember);
|
|
||||||
return newMember;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion Members
|
|
||||||
|
|
||||||
#region Saving
|
|
||||||
|
|
||||||
public enum SaveMethod {
|
|
||||||
Average,
|
|
||||||
Mean,
|
|
||||||
First,
|
|
||||||
All
|
|
||||||
}
|
|
||||||
public void Save(string path, SaveMethod saveMethod = SaveMethod.Average) {
|
|
||||||
Cluster cluster = null;
|
|
||||||
switch (saveMethod) {
|
|
||||||
case SaveMethod.First:
|
|
||||||
Member member = this.members[0];
|
|
||||||
if (member == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
cluster = member.brain;
|
|
||||||
break;
|
|
||||||
case SaveMethod.Average:
|
|
||||||
cluster = CalculateAverageCluster();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
cluster?.Export(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Cluster CalculateAverageCluster() {
|
|
||||||
if (members.Count <= 0)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
Cluster result = members[0].brain.Copy();
|
|
||||||
for (int memberIx = 1; memberIx < members.Count; memberIx++) {
|
|
||||||
Member member = members[memberIx];
|
|
||||||
result.ProcessWeightsFrom(member.brain, Sum);
|
|
||||||
}
|
|
||||||
result.ProcessWeights(w => w / members.Count);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion Saving
|
|
||||||
|
|
||||||
#region New Generation
|
|
||||||
|
|
||||||
public void NewGeneration() {
|
|
||||||
foreach (Member member in this.members) {
|
|
||||||
member.initialized = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected List<Member> SelectElite(float percentage) {
|
|
||||||
return SelectElite((int)(members.Count * percentage));
|
|
||||||
}
|
|
||||||
protected List<Member> SelectElite(int count) {
|
|
||||||
List<Member> selectedMembers = new();
|
|
||||||
|
|
||||||
SortMembers();
|
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
Member member = this.members[i];
|
|
||||||
if (!member.initialized) {
|
|
||||||
Debug.Log($"Selected {i}: {member.performance}");
|
|
||||||
member.initialized = true;
|
|
||||||
selectedMembers.Add(member);
|
|
||||||
// LogTrainableWeights(member.brain);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return selectedMembers;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SortMembers() {
|
|
||||||
members.Sort((a, b) => a.performance.CompareTo(b.performance));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected List<Member> GenerateMutants(List<Member> elite, float percentage) {
|
|
||||||
return GenerateMutants(elite, (int)(members.Count * percentage));
|
|
||||||
}
|
|
||||||
protected List<Member> GenerateMutants(List<Member> elite, int count) {
|
|
||||||
List<Member> selectedMembers = new();
|
|
||||||
int i = 0;
|
|
||||||
int n = 0;
|
|
||||||
while (n < count && i < this.members.Count) {
|
|
||||||
Member member = this.members[i];
|
|
||||||
if (member.initialized == false) {
|
|
||||||
GenerateMutant(member, elite);
|
|
||||||
member.initialized = true;
|
|
||||||
n++;
|
|
||||||
selectedMembers.Add(member);
|
|
||||||
}
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
return selectedMembers;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void GenerateMutant(Member member, List<Member> ants) {
|
|
||||||
System.Random randomGenerator = new();
|
|
||||||
int n = ants.Count;
|
|
||||||
int parent1 = randomGenerator.Next(0, n);
|
|
||||||
int parent2 = randomGenerator.Next(0, n);
|
|
||||||
Debug.Log($"Mutate from {members[parent1].performance} and {members[parent2].performance}");
|
|
||||||
|
|
||||||
member.brain.CopyWeightsFrom(ants[parent1].brain);
|
|
||||||
member.brain.ProcessWeightsFrom(ants[parent2].brain, Average);
|
|
||||||
// LogTrainableWeights(member.brain);
|
|
||||||
member.brain.GaussianAdditiveMutation(1e-2f);
|
|
||||||
// LogTrainableWeights(member.brain);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static float Average(float a, float b) {
|
|
||||||
return (a + b) / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static float Sum(float a, float b) {
|
|
||||||
return a + b;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected List<Member> GenerateRandom() {
|
|
||||||
return GenerateRandom(int.MaxValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected List<Member> GenerateRandom(int count) {
|
|
||||||
List<Member> selectedMembers = new();
|
|
||||||
int i = 0;
|
|
||||||
int n = 0;
|
|
||||||
while (n < count && i < this.members.Count) {
|
|
||||||
Member member = this.members[i];
|
|
||||||
if (member.initialized == false) {
|
|
||||||
Debug.Log($"Randomized {i}: {member.performance}");
|
|
||||||
member.brain.InitializeRandom();
|
|
||||||
member.initialized = true;
|
|
||||||
selectedMembers.Add(member);
|
|
||||||
n++;
|
|
||||||
}
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
return selectedMembers;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion New Generation
|
|
||||||
|
|
||||||
private void LogTrainableWeights(Cluster cluster) {
|
|
||||||
string s = "";
|
|
||||||
foreach (Nucleus nucleus in cluster.nuclei) {
|
|
||||||
if (nucleus is Neuron neuron) {
|
|
||||||
foreach (Synapse synapse in neuron.synapses) {
|
|
||||||
if (synapse.trainable) {
|
|
||||||
s += synapse.weight + " ";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Debug.Log(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void Update() { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: a820ab88a63bed6e881dbb82eae0a824
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@ -36,10 +36,6 @@ namespace NanoBrain {
|
|||||||
this.weight = weight;
|
this.weight = weight;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool EqualStructure(Synapse synapse1, Synapse synapse2) {
|
|
||||||
return synapse1.neuron.name == synapse2.neuron.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void BackPropagation(Neuron receiver, float derivative, float learningRate) {
|
public virtual void BackPropagation(Neuron receiver, float derivative, float learningRate) {
|
||||||
switch (receiver.activator) {
|
switch (receiver.activator) {
|
||||||
case Neuron.ActivationType.Linear:
|
case Neuron.ActivationType.Linear:
|
||||||
@ -114,30 +110,6 @@ namespace NanoBrain {
|
|||||||
// Check if the angle between the vectors is > 90 degrees
|
// Check if the angle between the vectors is > 90 degrees
|
||||||
return Vector3.Dot(a, b) < 0f;
|
return Vector3.Dot(a, b) < 0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void GaussianAdditiveMutation(float sigma) {
|
|
||||||
if (this.trainable == false)
|
|
||||||
return;
|
|
||||||
|
|
||||||
float deltaWeight = NormalDistribution.Sample(sigma);
|
|
||||||
this.weight += deltaWeight;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class NormalDistribution {
|
|
||||||
private static readonly System.Random rng = new();
|
|
||||||
|
|
||||||
// Returns a single sample from N(0, sigma^2)
|
|
||||||
public static float Sample(float sigma) {
|
|
||||||
// u1 must be > 0 to avoid log(0)
|
|
||||||
float u1 = 1.0f - (float)rng.NextDouble(); // in (0,1]
|
|
||||||
float u2 = (float) rng.NextDouble(); // in [0,1)
|
|
||||||
|
|
||||||
float stdNormal =
|
|
||||||
Mathf.Sqrt(-2.0f * Mathf.Log(u1)) * Mathf.Cos(2.0f * Mathf.PI * u2);
|
|
||||||
|
|
||||||
return sigma * stdNormal;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Serializable]
|
[Serializable]
|
||||||
@ -148,11 +120,7 @@ namespace NanoBrain {
|
|||||||
public bool trainable;
|
public bool trainable;
|
||||||
|
|
||||||
public SynapseData(Synapse synapse) {
|
public SynapseData(Synapse synapse) {
|
||||||
// if (synapse.neuron.parent.prefab != null)
|
this.clusterName = synapse.neuron.parent.name;
|
||||||
// this.clusterName = synapse.neuron.parent.prefab.name;
|
|
||||||
// else
|
|
||||||
// this.clusterName = synapse.neuron.parent.name;
|
|
||||||
this.clusterName = synapse.neuron.parent.baseName;
|
|
||||||
this.neuronName = synapse.neuron.name;
|
this.neuronName = synapse.neuron.name;
|
||||||
this.weight = synapse.weight;
|
this.weight = synapse.weight;
|
||||||
this.trainable = synapse.trainable;
|
this.trainable = synapse.trainable;
|
||||||
|
|||||||
@ -1,11 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 8ea9c456ab9da37daaa610edaadc38bb
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
Loading…
x
Reference in New Issue
Block a user