diff --git a/Editor/ClusterEditor.cs b/Editor/ClusterEditor.cs index a8478db..30365b3 100644 --- a/Editor/ClusterEditor.cs +++ b/Editor/ClusterEditor.cs @@ -51,8 +51,8 @@ namespace NanoBrain.Unity { private bool showSynapses = true; private bool showActivation = true; - protected bool breakOnWake = false; - protected bool trace = false; + //protected bool breakOnWake = false; + //protected bool trace = false; void InspectorHandler(SerializedObject serializedObject) { bool anythingChanged = false; @@ -172,14 +172,14 @@ namespace NanoBrain.Unity { SynapsesInspector(ref anythingChanged); ActivationInspector(ref anythingChanged); - EditorGUILayout.Space(); - breakOnWake = EditorGUILayout.Toggle("Break on wake", breakOnWake); - if (breakOnWake && this.view.currentNucleus is Neuron currentNeuron) { - if (currentNeuron.isSleeping == false) - Debug.Break(); - // trace = EditorGUILayout.Toggle("Trace", trace); - // currentNeuron.trace = trace; - } + // EditorGUILayout.Space(); + // breakOnWake = EditorGUILayout.Toggle("Break on wake", breakOnWake); + // if (breakOnWake && this.view.currentNucleus is Neuron currentNeuron) { + // if (currentNeuron.isSleeping == false) + // Debug.Break(); + // // trace = EditorGUILayout.Toggle("Trace", trace); + // // currentNeuron.trace = trace; + // } } protected void SynapsesInspector(ref bool anythingChanged) { diff --git a/Editor/ClusterView.cs b/Editor/ClusterView.cs index 3a45ce4..1dcec7a 100644 --- a/Editor/ClusterView.cs +++ b/Editor/ClusterView.cs @@ -170,7 +170,6 @@ namespace NanoBrain.Unity { maxValue = neuron.outputMagnitude; DrawNucleus(this.currentNucleus, position, maxValue); - } } else { @@ -194,8 +193,20 @@ namespace NanoBrain.Unity { #region Full Graph protected void DrawFullGraph() { + if (this.currentNucleus == null) { + Vector3 position = new(150, 210, 0); + DrawAllOutputs(position); + DrawOutputs(position); + return; + } + Dag dag = GenerateGraph(this.selectedOutput); Dag.ComputeLayout(dag); + + Vector3 pos = new(50, 210, 0); + DrawEdge(new Vector3(150, 210, 0), pos); + DrawAllOutputs(pos); + // Draw edges foreach (Dag.Edge e in dag.edges) { Dag.Node from = dag.nodes.FirstOrDefault(x => x.id == e.fromId); @@ -246,7 +257,6 @@ namespace NanoBrain.Unity { int ix = 0; Dag.Node receiver = new() { id = ix, - //title = nucleus.name, nucleus = rootNucleus }; dag.nodes.Add(receiver); @@ -405,7 +415,7 @@ namespace NanoBrain.Unity { Color color = Color.black; if (Application.isPlaying) { //if (maxValue == 0 || !float.IsFinite(maxValue)) - maxValue = 1 * synapse.weight; + maxValue = 1 * synapse.weight; float brightness = synapse.neuron.outputMagnitude * synapse.weight / maxValue; color = new Color(brightness, brightness, brightness, 1f); } @@ -802,6 +812,7 @@ namespace NanoBrain.Unity { } protected void OnAllOutputsClick() { + //this.mode = Mode.Focus; this.currentNucleus = null; this.selectedOutput = null; this.expandArray = false; @@ -810,146 +821,5 @@ namespace NanoBrain.Unity { #endregion Interaction } - public class Dag { - public class Node { - public int id; - public Vector2 position; - public float radius = 20f; // circle radius - public Nucleus nucleus; - } - - public class Edge { - public int fromId; - public int toId; - } - - public List nodes = new(); - public List edges = new(); - - public Node FindNode(string name, bool justBaseName = true) { - if (justBaseName) { - int colonPos = name.IndexOf(":"); - if (colonPos > 0) - name = name[..colonPos]; - } - foreach (Node node in this.nodes) { - string nodeName = node.nucleus.name; - if (justBaseName) { - int colonPos = nodeName.IndexOf(":"); - if (colonPos > 0) - nodeName = nodeName[..colonPos]; - } - if (nodeName == name) - return node; - } - return null; - } - - public static Node GetNodeById(Dag dag, int id) => dag.nodes.FirstOrDefault(x => x.id == id); - - public static void ComputeLayout(Dag dag) { - Dictionary> adjacency = dag.nodes.ToDictionary(n => n.id, n => new List()); - Dictionary outdegree = dag.nodes.ToDictionary(node => node.id, n => 0); - foreach (Edge edge in dag.edges) { - if (!adjacency.ContainsKey(edge.fromId) || !adjacency.ContainsKey(edge.toId)) - continue; - adjacency[edge.fromId].Add(edge.toId); - outdegree[edge.fromId]++; - } - - // Kahn's algorithm to compute topological layers (horizontal layers) - // build parent list (reverse adjacency) and parentIndegree = number of children each parent has - Dictionary> parents = dag.nodes.ToDictionary(n => n.id, _ => new List()); - Dictionary childCount = dag.nodes.ToDictionary(n => n.id, _ => 0); - - foreach (Edge edge in dag.edges) { - if (!adjacency.ContainsKey(edge.fromId) || !adjacency.ContainsKey(edge.toId)) continue; - adjacency[edge.fromId].Add(edge.toId); - parents[edge.toId].Add(edge.fromId); // parent of 'to' is 'from' - childCount[edge.fromId]++; // outdegree - } - - Dictionary column = new(); - Queue queue = new(outdegree.Where(keyValue => keyValue.Value == 0).Select(keyValue => keyValue.Key)); - foreach (int id in queue) - column[id] = 0; - - // process parents (reverse traversal) - while (queue.Count > 0) { - int nodeId = queue.Dequeue(); - int col = column[nodeId]; - foreach (int parentIx in parents[nodeId]) { - if (!column.ContainsKey(parentIx) || column[parentIx] < col + 1) - column[parentIx] = col + 1; - childCount[parentIx]--; // decrement remaining unprocessed children - if (childCount[parentIx] == 0) - queue.Enqueue(parentIx); - } - } - - // Any unreachable nodes -> assign next layers - int maxColumn = column.Count > 0 ? column.Values.Max() : 0; - foreach (Node node in dag.nodes) { - if (!column.ContainsKey(node.id)) { - maxColumn++; - column[node.id] = maxColumn; - } - } - - // Group nodes by column (left to right) - List> columns = - column. - GroupBy(kv => kv.Value). - OrderBy(g => g.Key). - Select(g => g.Select(x => x.Key).ToList()). - ToList(); - - // Same code without using Linq - // Build layers dictionary: layerIndex -> List nodeIds - // Dictionary> layersDict = new(); - // foreach (KeyValuePair kv in layer) { - // int nodeId = kv.Key; - // int layerIndex = kv.Value; - // if (!layersDict.TryGetValue(layerIndex, out List list)) { - // list = new List(); - // layersDict[layerIndex] = list; - // } - // list.Add(nodeId); - // } - - // // Determine sorted layer indices - // List layerIndices = new(layersDict.Keys); - // layerIndices.Sort(); // ascending order - - // // Build final List> in sorted order - // List> layers = new(); - // foreach (int idx in layerIndices) { - // layers.Add(layersDict[idx]); - // } - - float hSpacing = 100f; - float totalHeight = 400f; - - // Place nodes: x increases with column index, y spaced within column - for (int columnIx = 0; columnIx < columns.Count; columnIx++) { - List nodeList = columns[columnIx]; - float spacing = totalHeight / nodeList.Count; - float margin = 10 + spacing / 2; - for (int i = 0; i < nodeList.Count; i++) { - int index = nodeList[i]; - Node node = GetNodeById(dag, index); - if (node == null) - continue; - float x = hSpacing + columnIx * hSpacing; - //float y = 400 - totalHeight / 2f + i * vSpacing; - float y = margin + i * spacing; - // Debug.Log($"({li}, {i}) -> {x}, {y}"); - node.position = new Vector2(x, y); - } - } - - //Repaint(); - } - } } \ No newline at end of file diff --git a/Editor/Dag.cs b/Editor/Dag.cs new file mode 100644 index 0000000..2435fed --- /dev/null +++ b/Editor/Dag.cs @@ -0,0 +1,149 @@ +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using UnityEditor; + +namespace NanoBrain.Unity { + public class Dag { + + public class Node { + public int id; + public Vector2 position; + public float radius = 20f; // circle radius + public Nucleus nucleus; + } + + public class Edge { + public int fromId; + public int toId; + } + + public List nodes = new(); + public List edges = new(); + + public Node FindNode(string name, bool justBaseName = true) { + if (justBaseName) { + int colonPos = name.IndexOf(":"); + if (colonPos > 0) + name = name[..colonPos]; + } + foreach (Node node in this.nodes) { + string nodeName = node.nucleus.name; + if (justBaseName) { + int colonPos = nodeName.IndexOf(":"); + if (colonPos > 0) + nodeName = nodeName[..colonPos]; + } + if (nodeName == name) + return node; + } + return null; + } + + public static Node GetNodeById(Dag dag, int id) => dag.nodes.FirstOrDefault(x => x.id == id); + + public static void ComputeLayout(Dag dag) { + Dictionary> adjacency = dag.nodes.ToDictionary(n => n.id, n => new List()); + Dictionary outdegree = dag.nodes.ToDictionary(node => node.id, n => 0); + foreach (Edge edge in dag.edges) { + if (!adjacency.ContainsKey(edge.fromId) || !adjacency.ContainsKey(edge.toId)) + continue; + adjacency[edge.fromId].Add(edge.toId); + outdegree[edge.fromId]++; + } + + // Kahn's algorithm to compute topological layers (horizontal layers) + // build parent list (reverse adjacency) and parentIndegree = number of children each parent has + Dictionary> parents = dag.nodes.ToDictionary(n => n.id, _ => new List()); + Dictionary childCount = dag.nodes.ToDictionary(n => n.id, _ => 0); + + foreach (Edge edge in dag.edges) { + if (!adjacency.ContainsKey(edge.fromId) || !adjacency.ContainsKey(edge.toId)) continue; + adjacency[edge.fromId].Add(edge.toId); + parents[edge.toId].Add(edge.fromId); // parent of 'to' is 'from' + childCount[edge.fromId]++; // outdegree + } + + Dictionary column = new(); + Queue queue = new(outdegree.Where(keyValue => keyValue.Value == 0).Select(keyValue => keyValue.Key)); + foreach (int id in queue) + column[id] = 0; + + // process parents (reverse traversal) + while (queue.Count > 0) { + int nodeId = queue.Dequeue(); + int col = column[nodeId]; + foreach (int parentIx in parents[nodeId]) { + if (!column.ContainsKey(parentIx) || column[parentIx] < col + 1) + column[parentIx] = col + 1; + childCount[parentIx]--; // decrement remaining unprocessed children + if (childCount[parentIx] == 0) + queue.Enqueue(parentIx); + } + } + + // Any unreachable nodes -> assign next layers + int maxColumn = column.Count > 0 ? column.Values.Max() : 0; + foreach (Node node in dag.nodes) { + if (!column.ContainsKey(node.id)) { + maxColumn++; + column[node.id] = maxColumn; + } + } + + // Group nodes by column (left to right) + List> columns = + column. + GroupBy(kv => kv.Value). + OrderBy(g => g.Key). + Select(g => g.Select(x => x.Key).ToList()). + ToList(); + + // Same code without using Linq + // Build layers dictionary: layerIndex -> List nodeIds + // Dictionary> layersDict = new(); + // foreach (KeyValuePair kv in layer) { + // int nodeId = kv.Key; + // int layerIndex = kv.Value; + // if (!layersDict.TryGetValue(layerIndex, out List list)) { + // list = new List(); + // layersDict[layerIndex] = list; + // } + // list.Add(nodeId); + // } + + // // Determine sorted layer indices + // List layerIndices = new(layersDict.Keys); + // layerIndices.Sort(); // ascending order + + // // Build final List> in sorted order + // List> layers = new(); + // foreach (int idx in layerIndices) { + // layers.Add(layersDict[idx]); + // } + + float hSpacing = 100f; + float totalHeight = 400f; + + // Place nodes: x increases with column index, y spaced within column + for (int columnIx = 0; columnIx < columns.Count; columnIx++) { + List nodeList = columns[columnIx]; + float spacing = totalHeight / nodeList.Count; + float margin = 10 + spacing / 2; + for (int i = 0; i < nodeList.Count; i++) { + int index = nodeList[i]; + Node node = GetNodeById(dag, index); + if (node == null) + continue; + float x = (hSpacing * 1.5f) + columnIx * hSpacing; + //float y = 400 - totalHeight / 2f + i * vSpacing; + float y = margin + i * spacing; + // Debug.Log($"({li}, {i}) -> {x}, {y}"); + node.position = new Vector2(x, y); + } + } + + //Repaint(); + } + } +} \ No newline at end of file diff --git a/Editor/Dag.cs.meta b/Editor/Dag.cs.meta new file mode 100644 index 0000000..03066bc --- /dev/null +++ b/Editor/Dag.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a755ac8461bd0c714a852df47331048e \ No newline at end of file diff --git a/Samples/Braitenberg/Brains/Braitenberg 1.asset b/Samples/Braitenberg/Brains/Braitenberg 1.asset index be3434a..d9c138c 100644 --- a/Samples/Braitenberg/Brains/Braitenberg 1.asset +++ b/Samples/Braitenberg/Brains/Braitenberg 1.asset @@ -65,7 +65,7 @@ MonoBehaviour: m_RotationOrder: 4 curveMax: 1 persistOutput: 0 - lastUpdate: 14.822748 + lastUpdate: 19.870863 _receivers: [] - rid: 4201949899492425817 type: {class: Neuron, ns: NanoBrain, asm: Assembly-CSharp} @@ -73,7 +73,7 @@ MonoBehaviour: name: Sensor parent: rid: 4201950148723474519 - bias: {x: 0.062121756, y: 0.062121756, z: 0.062121756} + bias: {x: 0, y: 0, z: 0} _synapses: [] combinator: 0 _activator: 0 @@ -103,7 +103,7 @@ MonoBehaviour: m_RotationOrder: 4 curveMax: 1 persistOutput: 0 - lastUpdate: 14.822748 + lastUpdate: 19.870863 _receivers: - rid: 4201949899492425781 - rid: 4201950148723474519