Compare commits

...

21 Commits

Author SHA1 Message Date
b3423b99a7 AnimatedWalkingAnt 2026-03-06 17:03:41 +01:00
4805e81f09 synapse weight change updates nanobrain 2026-03-05 17:17:38 +01:00
28ef70c773 synapses connect to neurons 2026-03-05 17:04:17 +01:00
ccb7a41577 Cleanup 2026-03-05 16:04:28 +01:00
8a9700981b They are swarming! 2026-03-04 15:46:55 +01:00
28ffae8ce7 Fix set weight on receptor arrays 2026-03-04 15:22:36 +01:00
1dfe65eefa Cluster no longer has output 2026-03-04 14:20:23 +01:00
172d7ca8e8 Gaze includes velocity 2026-03-03 17:24:28 +01:00
9c730709f1 gaze with clusterreceptor 2026-03-03 17:05:16 +01:00
cbc8296e55 Fix velocity not updating 2026-03-03 15:52:35 +01:00
f8b487cef7 starting to work with 6 cluster receptors 2026-03-03 14:47:24 +01:00
abbce40992 Process clusterreceptor per thingId 2026-03-03 12:19:41 +01:00
3cdca017d6 process signal for clsuter receptor 2026-03-03 11:48:16 +01:00
bd24e6e19b Get subcluster nucleus 2026-03-03 08:51:09 +01:00
96b240ad6c Fix clusterreceptor output connections 2026-03-02 16:04:09 +01:00
463bef0868 Improved ClusterReceptor 2026-03-02 15:46:05 +01:00
b0ee3add3a Fix cluster receivers 2026-03-02 12:09:00 +01:00
83e8bd70f1 Improved UI 2026-03-02 11:07:40 +01:00
97ea988cca Fix changing weight to receptors 2026-02-19 17:30:56 +01:00
f9ce73fd7a Fix max combinator and connecting to receptors 2026-02-19 17:19:15 +01:00
2ef9629e4d Enable stimulus processing in any nucleus 2026-02-19 16:35:57 +01:00
38 changed files with 2683 additions and 1151 deletions

View File

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Unity.Mathematics;
using static Unity.Mathematics.math;
@ -8,6 +7,15 @@ using static Unity.Mathematics.math;
[Serializable]
public class Cluster : Nucleus {
public string baseName {
get {
int colonPositon = this.name.IndexOf(':');
if (colonPositon < 0)
return this.name;
return this.name[..colonPositon];
}
}
#region Init
public Cluster(ClusterPrefab prefab, Cluster parent) {
@ -39,7 +47,6 @@ public class Cluster : Nucleus {
Nucleus[] prefabNuclei = this.prefab.nuclei.ToArray();
// first clone the nuclei without their connections
foreach (Nucleus nucleus in this.prefab.nuclei) {
// Debug.Log($"prefab clone {nucleus.name}");
nucleus.ShallowCloneTo(this);
}
Nucleus[] clonedNuclei = this.clusterNuclei.ToArray();
@ -68,7 +75,7 @@ public class Cluster : Nucleus {
float weight = 1;
foreach (Synapse synapse in receiver.synapses) {
// Find the weight for this synapse
if (synapse.nucleus == prefabNucleus) {
if (synapse.neuron == prefabNucleus) {
weight = synapse.weight;
break;
}
@ -78,40 +85,46 @@ public class Cluster : Nucleus {
}
}
// Copy nucleus arrays
// Copy nucleus arrays for receptors
for (int nucleusIx = 0; nucleusIx < prefabNuclei.Length; nucleusIx++) {
Nucleus prefabReceptor = prefabNuclei[nucleusIx];
if (prefabReceptor is not Receptor prefabNucleus)
Nucleus prefabNucleus = prefabNuclei[nucleusIx];
if (prefabNucleus is not IReceptor prefabReceptor)
continue;
if (prefabNucleus.nucleiArray == null || prefabNucleus.nucleiArray.Length == 0)
if (prefabReceptor.nucleiArray == null || prefabReceptor.nucleiArray.Length == 0)
continue;
Receptor clonedNucleus = clonedNuclei[nucleusIx] as Receptor;
if (prefabNucleus == prefabNucleus.nucleiArray[0]) {
IReceptor clonedNucleus = clonedNuclei[nucleusIx] as IReceptor;
if (prefabReceptor == prefabReceptor.nucleiArray[0]) {
// We clone the array only for the first entry
NucleusArray clonedArray = new(prefabNucleus.nucleiArray.Length, "array");
NucleusArray clonedArray = new(prefabReceptor.nucleiArray.Length, "array");
int arrayIx = 0;
foreach (Nucleus prefabArrayNucleus in prefabNucleus.nucleiArray) {
foreach (Nucleus prefabArrayNucleus in prefabReceptor.nucleiArray) {
int arrayNucleusIx = GetNucleusIndex(prefabNuclei, prefabArrayNucleus);
if (arrayNucleusIx >= 0) {
Nucleus clonedArrayNucleus = clonedNuclei[arrayNucleusIx];
clonedArray.nuclei[arrayIx] = clonedArrayNucleus;
}
else {
Debug.LogError($" Could not find prefab nuclues {prefabNucleus.name} in the clones");
Debug.LogError($" Could not find prefab nucleus {prefabNucleus.name} in the clones");
}
arrayIx++;
}
clonedNucleus.array = clonedArray;
//clonedNucleus.array = clonedArray;
clonedNucleus.nucleiArray = clonedArray.nuclei;
}
else {
// The others will refer to the array created for the first nucleus in the array
int firstNucleusIx = GetNucleusIndex(prefabNuclei, prefabNucleus.nucleiArray[0]);
Receptor clonedFirstNucleus = clonedNuclei[firstNucleusIx] as Receptor;
int firstNucleusIx = GetNucleusIndex(prefabNuclei, prefabReceptor.nucleiArray[0]);
IReceptor clonedFirstNucleus = clonedNuclei[firstNucleusIx] as IReceptor;
clonedNucleus.nucleiArray = clonedFirstNucleus.nucleiArray;
}
}
foreach (Nucleus nucleus in this.clusterNuclei) {
if (nucleus is Cluster clonedSubCluster)
RestoreAllExternalReceivers(clonedSubCluster, this.prefab, this);
}
}
// Sort the nuclei in a correct evaluation order
@ -122,7 +135,11 @@ public class Cluster : Nucleus {
// Calculate in-degrees
foreach (Nucleus node in nodes) {
if (node is Neuron neuron) {
if (node is Cluster cluster) {
foreach (Nucleus receiver in cluster.CollectReceivers())
inDegree[receiver]++;
}
else if (node is Neuron neuron) {
foreach (Nucleus receiver in neuron.receivers)
inDegree[receiver]++;
}
@ -147,6 +164,13 @@ public class Cluster : Nucleus {
queue.Enqueue(receiver);
}
}
else if (current is Cluster cluster) {
foreach (Nucleus receiver in cluster.CollectReceivers()) {
inDegree[receiver]--;
if (inDegree[receiver] == 0) // If all dependencies resolved
queue.Enqueue(receiver);
}
}
}
// Check for cycles in the graph
@ -160,7 +184,7 @@ public class Cluster : Nucleus {
Cluster clone = new(this.prefab, parent);
foreach (Synapse synapse in this.synapses) {
Synapse clonedSynapse = clone.AddSynapse(synapse.nucleus);
Synapse clonedSynapse = clone.AddSynapse(synapse.neuron);
clonedSynapse.weight = synapse.weight;
}
@ -185,48 +209,45 @@ public class Cluster : Nucleus {
name = this.name,
clusterPrefab = this.clusterPrefab,
};
// This cloned the prefab with the clusternuclei,
// but did not clone the receivers outside the cluster
RestoreExternalReceivers(clone, this.clusterPrefab, parent);
return clone;
}
protected void RestoreExternalReceivers(Cluster clone, ClusterPrefab prefabParent, Cluster clonedParent) {
for (int nucleusIx = 0; nucleusIx < this.clusterNuclei.Count; nucleusIx++) {
Nucleus prefabNucleus = this.clusterNuclei[nucleusIx];
if (prefabNucleus is not Neuron prefabNeuron)
private static void RestoreAllExternalReceivers(Cluster clonedCluster, ClusterPrefab prefabParent, Cluster clonedParent) {
int clonedClusterIx = GetNucleusIndex(clonedParent.clusterNuclei, clonedCluster);
if (prefabParent.nuclei[clonedClusterIx] is not Cluster sourceCluster)
return;
for (int nucleusIx = 0; nucleusIx < sourceCluster.clusterNuclei.Count; nucleusIx++) {
Nucleus sourceNucleus = sourceCluster.clusterNuclei[nucleusIx];
if (sourceNucleus is not Neuron sourceNeuron)
continue;
Nucleus clonedNucleus = clone.clusterNuclei[nucleusIx];
if (clonedNucleus == null || clonedNucleus is not Neuron clonedNeuron)
if (clonedCluster.clusterNuclei[nucleusIx] is not Neuron clonedNeuron)
continue;
// Copy the receivers, which will also create the synapses
foreach (Nucleus receiver in prefabNeuron.receivers) {
// copy the receivers (and thus synapses) from the source to the clone
foreach (Nucleus receiver in sourceNeuron.receivers) {
int ix = GetNucleusIndex(prefabParent.nuclei, receiver);
if (ix < 0)
if (ix < 0 || ix >= clonedParent.clusterNuclei.Count)
continue;
//if (clone.clusterNuclei[ix] is not Nucleus clonedReceiver)
if (clonedParent.clusterNuclei[ix] is not Nucleus clonedReceiver)
continue;
Nucleus clonedReceiver = clonedParent.clusterNuclei[ix];
// Find the synapse for the weight
float weight = 1;
foreach (Synapse synapse in receiver.synapses) {
// Find the weight for this synapse
if (synapse.nucleus == prefabNucleus) {
if (synapse.neuron == sourceNucleus) {
weight = synapse.weight;
break;
}
}
clonedNeuron.AddReceiver(clonedReceiver, weight);
// Debug.Log($"external: {clonedReceiver.name} receives from {clonedNeuron.name} {clonedNeuron.GetHashCode()}");
}
}
}
protected int GetNucleusIndex(Nucleus[] nuclei, Nucleus nucleus) {
@ -237,12 +258,13 @@ public class Cluster : Nucleus {
return -1;
}
protected int GetNucleusIndex(List<Nucleus> nuclei, Nucleus nucleus) {
public static int GetNucleusIndex(List<Nucleus> nuclei, Nucleus nucleus) {
int i = 0;
foreach (Nucleus nucleiElement in nuclei) {
//for (int i = 0; i < nuclei.Length; i++) {
if (nucleus == nucleiElement)
return i;
i++;
}
return -1;
}
@ -277,19 +299,17 @@ public class Cluster : Nucleus {
public Dictionary<Nucleus, List<Nucleus>> computeOrders = new();
private void ComputeOrders() {
foreach (Nucleus input in this._inputs) {
foreach (Nucleus input in this._inputs)
computeOrders[input] = TopologicalSort2(input);
}
}
private List<Nucleus> TopologicalSort2(Nucleus startNode) {
Dictionary<Nucleus, int> inDegree = new Dictionary<Nucleus, int>();
HashSet<Nucleus> visited = new HashSet<Nucleus>();
Dictionary<Nucleus, int> inDegree = new();
HashSet<Nucleus> visited = new();
// Initialize in-degrees and mark all nodes as unvisited
foreach (Nucleus node in this.clusterNuclei) {
foreach (Nucleus node in this.clusterNuclei)
inDegree[node] = 0;
}
// Calculate in-degrees for all nodes reachable from the start node
Queue<Nucleus> queue = new Queue<Nucleus>();
@ -298,23 +318,28 @@ public class Cluster : Nucleus {
while (queue.Count > 0) {
Nucleus current = queue.Dequeue();
if (current is Neuron neuron) {
foreach (Nucleus receiver in neuron.receivers) {
if (!visited.Contains(receiver)) {
visited.Add(receiver);
queue.Enqueue(receiver);
}
inDegree[receiver]++;
List<Nucleus> receivers = null;
if (current is Neuron neuron)
receivers = neuron.receivers;
else if (current is Cluster cluster)
receivers = cluster.CollectReceivers();
// if (current is Neuron neuron) {
foreach (Nucleus receiver in receivers) {
if (!visited.Contains(receiver)) {
visited.Add(receiver);
queue.Enqueue(receiver);
}
inDegree[receiver]++;
}
// }
}
// Perform topological sort on all reachable nodes
queue.Clear();
foreach (var node in visited) {
if (inDegree[node] == 0) {
foreach (Nucleus node in visited) {
if (inDegree[node] == 0)
queue.Enqueue(node);
}
}
List<Nucleus> sortedOrder = new List<Nucleus>();
@ -322,58 +347,27 @@ public class Cluster : Nucleus {
Nucleus current = queue.Dequeue();
sortedOrder.Add(current); // Process the node
if (current is Neuron neuron) {
foreach (Nucleus receiver in neuron.receivers) {
if (visited.Contains(receiver)) {
inDegree[receiver]--;
if (inDegree[receiver] == 0) // If all dependencies resolved
queue.Enqueue(receiver);
}
}
}
}
List<Nucleus> receivers = null;
if (current is Neuron neuron)
receivers = neuron.receivers;
else if (current is Cluster cluster)
receivers = cluster.CollectReceivers();
// Check for cycles in the graph
if (sortedOrder.Count != visited.Count)
throw new InvalidOperationException("Graph is not a DAG; a cycle exists.");
//if (current is Neuron neuron) {
return sortedOrder;
}
private List<Nucleus> TopologicalSort3(Nucleus startNode) {
Dictionary<Nucleus, int> inDegree = new();
foreach (Nucleus node in this.clusterNuclei)
inDegree[node] = 0; // Initialize in-degree to zero
// Calculate in-degrees
foreach (Nucleus node in this.clusterNuclei) {
if (node is Neuron neuron) {
foreach (Nucleus receiver in neuron.receivers)
inDegree[receiver]++;
}
}
Queue<Nucleus> queue = new();
queue.Enqueue(startNode);
List<Nucleus> sortedOrder = new();
while (queue.Count > 0) {
Nucleus current = queue.Dequeue();
sortedOrder.Add(current); // Process the node
if (current is Neuron neuron) {
foreach (Nucleus receiver in neuron.receivers) {
foreach (Nucleus receiver in receivers) {
if (visited.Contains(receiver)) {
inDegree[receiver]--;
if (inDegree[receiver] == 0) // If all dependencies resolved
queue.Enqueue(receiver);
}
}
//}
}
Debug.Log($"Compute order for {startNode.name} length = {sortedOrder.Count}");
// Check for cycles in the graph
// if (sortedOrder.Count != this.nuclei.Count)
// throw new InvalidOperationException("Graph is not a DAG; a cycle exists.");
if (sortedOrder.Count != visited.Count)
throw new InvalidOperationException("Graph is not a DAG; a cycle exists.");
return sortedOrder;
}
@ -412,31 +406,50 @@ public class Cluster : Nucleus {
}
public Nucleus GetNucleus(string nucleusName) {
foreach (Nucleus nucleus in this.clusterNuclei) {
if (nucleus.name == nucleusName)
return nucleus;
int dotPosition = nucleusName.IndexOf('.');
if (dotPosition >= 0) {
string clusterName = nucleusName[..dotPosition];
string clusterName0 = clusterName + ": 0";
foreach (Nucleus nucleus in this.clusterNuclei) {
if (nucleus is Cluster cluster) {
if (cluster.name == clusterName || cluster.name == clusterName0) {
string subNucleusName = nucleusName[(dotPosition + 1)..];
return cluster.GetNucleus(subNucleusName);
}
}
}
return null;
}
else {
string nucleusName0 = nucleusName + ": 0";
foreach (Nucleus nucleus in this.clusterNuclei) {
if (nucleus is IReceptor receptor) {
if (nucleus.name == nucleusName | nucleus.name == nucleusName0)
return nucleus;
}
else if (nucleus.name == nucleusName)
return nucleus;
}
return null;
}
return null;
}
public IReceptor GetReceptor(string receptorName) {
string receptorName0 = receptorName + ": 0";
foreach (Nucleus nucleus in this.clusterNuclei) {
if (nucleus is IReceptor receptor) {
if (nucleus.name == receptorName | nucleus.name == receptorName0)
//if (receptor.GetName() == receptorName)
return receptor;
}
}
return null;
}
// [Obsolete("Use GetNucleus instead")]
// public IReceptor GetReceptor(string receptorName) {
// return GetNucleus(receptorName) as IReceptor;
// }
#region Receivers
public virtual List<Nucleus> CollectReceivers() {
List<Nucleus> receivers = new();
foreach (Neuron output in this.outputs) {
receivers.AddRange(output.receivers);
foreach (Nucleus receiver in output.receivers) {
// Only add receivers outside this cluster
if (receiver.clusterPrefab != this.prefab)
receivers.Add(receiver);
//receivers.AddRange(output.receivers);
}
}
return receivers;
}
@ -448,39 +461,41 @@ public class Cluster : Nucleus {
public void UpdateFromNucleus(Nucleus startNucleus) {
// no bias+synapse input state calculation for now...
if (this.computeOrders.ContainsKey(startNucleus) == false) {
//Debug.LogError($"{this.name} compute orders does not contain an order for {startNucleus.name}");
return;
}
List<Nucleus> computeOrder = this.computeOrders[startNucleus];
if (startNucleus.trace)
Debug.Log($"Update from {startNucleus.name}");
foreach (Nucleus nucleus in computeOrder) {
nucleus.UpdateStateIsolated();
if (startNucleus.trace)
Debug.Log($" {nucleus.name} = {nucleus.outputValue}");
if (startNucleus.trace && nucleus is Neuron neuron)
Debug.Log($" {nucleus.name}[{nucleus.GetHashCode()}] = {neuron.outputValue}");
}
this.outputValue = this.defaultOutput.outputValue;
this.stale = 0;
// continue in parent
this.parent?.UpdateFromNucleus(this);
UpdateNuclei();
}
public override void UpdateStateIsolated() {
float3 sum = this.bias;
throw new Exception("Cluster should not be updated!");
// float3 sum = this.bias;
//Applying the weight factors
foreach (Synapse synapse in this.synapses) {
if (lengthsq(synapse.nucleus.outputValue) > 0) {
sum += synapse.weight * synapse.nucleus.outputValue;
this.stale = 0;
}
}
// //Applying the weight factors
// foreach (Synapse synapse in this.synapses) {
// if (lengthsq(synapse.neuron.outputValue) > 0) {
// sum += synapse.weight * synapse.neuron.outputValue;
// }
// }
foreach (Nucleus nucleus in this.sortedNuclei)
nucleus.UpdateStateIsolated();
// foreach (Nucleus nucleus in this.sortedNuclei)
// nucleus.UpdateStateIsolated();
this.outputValue = this.defaultOutput.outputValue;
this.stale = 0;
UpdateNuclei();
// UpdateNuclei();
}
public override void UpdateNuclei() {

View File

@ -64,7 +64,6 @@ public class ClusterPrefab : ScriptableObject {
HashSet<Nucleus> visitedNuclei = new();
foreach (Nucleus output in this.outputs)
MarkNuclei(visitedNuclei, output);
//MarkNuclei(visitedNuclei, this.output);
//Debug.Log($"Garbage collection found {visitedNuclei.Count} Nuclei");
this.nuclei.RemoveAll(nucleus => visitedNuclei.Contains(nucleus) == false);
}
@ -80,9 +79,9 @@ public class ClusterPrefab : ScriptableObject {
if (nucleus.synapses != null) {
HashSet<Synapse> visitedSynapses = new();
foreach (Synapse synapse in nucleus.synapses) {
if (synapse != null && synapse.nucleus != null) {
if (synapse != null && synapse.neuron != null) {
visitedSynapses.Add(synapse);
if (synapse.nucleus is Nucleus synapse_nucleus)
if (synapse.neuron is Nucleus synapse_nucleus)
MarkNuclei(visitedNuclei, synapse_nucleus);
}
}

View File

@ -3,14 +3,18 @@ using System.Collections.Generic;
using UnityEngine;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using System.Linq;
[Serializable]
public class ClusterReceptor : Cluster, IReceptor {
public ClusterReceptor(ClusterPrefab prefab, Cluster parent, string name) : base(prefab, parent) {
this.name = name;
this.array ??= new NucleusArray(this);
this.array = new NucleusArray(this);
if (this.name.IndexOf(":") < 0)
this.name += ": 0";
}
public ClusterReceptor(ClusterPrefab prefabToInstantiate, ClusterPrefab parent, string name) : base(prefabToInstantiate, parent) {
public ClusterReceptor(ClusterPrefab prefab, ClusterPrefab parent, string name) : base(prefab, parent) {
this.name = name;
this.array = new NucleusArray(this);
}
@ -22,30 +26,21 @@ public class ClusterReceptor : Cluster, IReceptor {
public override Nucleus ShallowCloneTo(Cluster parent) {
ClusterReceptor clone = new(this.prefab, parent, this.name) {
clusterPrefab = this.clusterPrefab,
array = this.array
};
// This cloned the prefab with the clusternuclei,
// but did not clone the receivers outside the cluster
RestoreExternalReceivers(clone, this.clusterPrefab, parent);
return clone;
}
public override Nucleus Clone(ClusterPrefab parent) {
ClusterReceptor clone = new(prefab, parent, this.name) {
array = this.array
array = this._array
};
foreach (Synapse synapse in this.synapses) {
Synapse clonedSynapse = clone.AddSynapse(synapse.nucleus);
Synapse clonedSynapse = clone.AddSynapse(synapse.neuron);
clonedSynapse.weight = synapse.weight;
}
// foreach (Nucleus receiver in this.receivers) {
// clone.AddReceiver(receiver);
// }
this._outputs = null; // Make sure the output are regenerated
foreach (Neuron output in this.outputs) {
int ix = GetNucleusIndex(this.clusterNuclei, output);
@ -58,53 +53,162 @@ public class ClusterReceptor : Cluster, IReceptor {
return clone;
}
public override List<Nucleus> CollectReceivers() {
List<Nucleus> receivers = new();
foreach (Nucleus element in this.nucleiArray) {
if (element is not Cluster clusterElement)
continue;
foreach (Nucleus outputNucleus in clusterElement.clusterNuclei) {
if (outputNucleus is not Neuron output)
continue;
// this should be clusterElement.outputs,
// but outputs is not updated when correctly and may contain old data...
foreach (Nucleus receiver in output.receivers) {
// Only add receivers outside clusterElement cluster
if (receiver.clusterPrefab != clusterElement.prefab &&
receivers.Contains(receiver) == false)
receivers.Add(receiver);
}
}
}
return receivers;
}
[SerializeReference]
private NucleusArray _array;
public NucleusArray array {
get { return _array; }
set { _array = value; }
}
//[SerializeReference]
//private Nucleus[] _nucleusArray;
public Nucleus[] nucleiArray {
get { return _array.nuclei; }
set { _array.nuclei = value; }
}
public void AddReceptorElement(ClusterPrefab prefab) {
this.nucleiArray = IReceptorHelpers.AddReceptorElement(this.nucleiArray, prefab);
IReceptorHelpers.AddReceptorElement(this, prefab);
}
public void RemoveReceptorElement() {
this.nucleiArray = IReceptorHelpers.RemoveReceptorElement(this.nucleiArray);
IReceptorHelpers.RemoveReceptorElement(this);
}
public void AddArrayReceiver(Nucleus receiverToAdd, float weight = 1) {
IReceptorHelpers.AddArrayReceiver(this, receiverToAdd, weight);
}
public override void UpdateStateIsolated() {
float3 sum = this.bias;
foreach (Nucleus nucleus in this.sortedNuclei)
nucleus.UpdateStateIsolated();
this.outputValue = this.defaultOutput.outputValue;
this.stale = 0;
UpdateNuclei();
// Clusters don't do anything,
// The nuclei in them do the work
// and should be called directly, not from the cluster
}
public override void UpdateNuclei() {
this.stale++;
if (this.stale > staleValueForSleep && lengthsq(this.bias) > 0) {
this.bias = new float3(0, 0, 0);
this.parent.UpdateFromNucleus(this);
}
foreach (Nucleus nucleus in this.clusterNuclei)
nucleus.UpdateNuclei();
}
public virtual void ProcessStimulus(Vector3 inputValue, int thingId = 0, string thingName = null) {
this.array ??= new NucleusArray(this.parent);
this.array.ProcessStimulus(thingId, inputValue, thingName);
public override void ProcessStimulus(Vector3 inputValue, int thingId = 0, string thingName = null) {
Debug.LogError("Process Stimulus was called on clusterreceptor without a neuron specified");
}
private readonly Dictionary<int, ClusterReceptor> thingReceivers = new();
public virtual void ProcessStimulus(Neuron input, Vector3 inputValue, int thingId = 0, string thingName = null) {
CleanupReceivers();
if (!thingReceivers.TryGetValue(thingId, out ClusterReceptor selectedReceiver))
selectedReceiver = FindReceiver2(thingId, inputValue, input);
if (selectedReceiver == null)
return;
if (thingName != null) {
string baseName = selectedReceiver.name;
int colonPos = selectedReceiver.name.IndexOf(":");
if (colonPos > 0)
baseName = selectedReceiver.name[..colonPos];
selectedReceiver.name = baseName + ": " + thingName;
}
int inputIx = GetNucleusIndex(this.clusterNuclei, input);
if (inputIx < 0)
return;
if (selectedReceiver.clusterNuclei[inputIx] is Neuron selectedNeuron)
selectedNeuron.ProcessStimulusDirect(inputValue);
}
private ClusterReceptor FindReceiver2(int thingId, float3 inputValue, Neuron input) {
// No existing nucleus for this thing
ClusterReceptor selectedReceiver = null;
float selectedMagnitude = 0;
foreach (ClusterReceptor receiver in this.nucleiArray.Cast<ClusterReceptor>()) {
if (thingReceivers.ContainsValue(receiver) == false) {
// We found an unusued receiver
thingReceivers.Add(thingId, receiver);
return receiver;
}
else if (receiver.defaultOutput.isSleeping) {
// A sleeping receiver is not active and can therefore always be used
thingReceivers.Add(thingId, receiver);
receiver.bias = float3(0, 0, 0);
return receiver;
}
else if (selectedReceiver == null) {
// If we haven't found a receiver yet, just start by taking the first
selectedReceiver = receiver;
selectedMagnitude = length(selectedReceiver.defaultOutput.outputValue);
}
// Look for the receiver with the lowest output magnitude
else {
float magnitude = length(receiver.defaultOutput.outputValue);
if (length(receiver.defaultOutput.outputValue) < selectedMagnitude) {
selectedReceiver = receiver;
selectedMagnitude = length(selectedReceiver.defaultOutput.outputValue);
}
}
}
if (selectedReceiver != null) {
// To re-initialize the cluster (esp. memory cells)
// we update the cluster neuron twice.
// Bit of a hack.....
int inputIx = GetNucleusIndex(this.clusterNuclei, input);
if (inputIx >= 0) {
if (selectedReceiver.clusterNuclei[inputIx] is Neuron selectedNeuron)
selectedNeuron.ProcessStimulusDirect(inputValue);
}
// Replace the receiver
// Find the thingId current associated with the receiver
int keyToRemove = thingReceivers.FirstOrDefault(r => r.Value.Equals(selectedReceiver)).Key;
if (keyToRemove != 0 || thingReceivers.ContainsKey(keyToRemove))
thingReceivers.Remove(keyToRemove);
// And add the new association
thingReceivers.Add(thingId, selectedReceiver);
}
return selectedReceiver;
}
private void CleanupReceivers() {
// Remove a thing-receiver connection when the nucleus is inactive
List<int> receiversToRemove = new();
foreach (KeyValuePair<int, ClusterReceptor> item in thingReceivers) {
if (item.Value != null && item.Value.defaultOutput.isSleeping)
receiversToRemove.Add(item.Key);
}
foreach (int thingId in receiversToRemove) {
Nucleus selectedReceiver = thingReceivers[thingId];
thingReceivers.Remove(thingId);
int colonPos = selectedReceiver.name.IndexOf(":");
if (colonPos > 0)
selectedReceiver.name = selectedReceiver.name[..colonPos];
}
}
}

View File

@ -19,12 +19,6 @@ public class ClusterInspector : Editor {
public override VisualElement CreateInspectorGUI() {
ClusterPrefab prefab = target as ClusterPrefab;
// string path = AssetDatabase.GetAssetPath(prefab); // or known path
// Debug.Log($"{path}");
// ClusterPrefab currentWrapper = AssetDatabase.LoadAssetAtPath<ClusterPrefab>(path);
// if (currentWrapper == null)
// Debug.LogError("CreateInspectorGUI: Cluster Prefab is not found on disk");
if (prefab != null)
prefab.EnsureInitialization();
@ -182,7 +176,7 @@ public class ClusterInspector : Editor {
if (this.prefabAsset == null) {
// create in memory save if it doesn't exist
this.prefabAsset = CreateInstance<ClusterPrefab>();
Debug.LogError("Cluster Prefab is not found on disk");
//Debug.LogError("Cluster Prefab is not found on disk");
}
DrawInspector(inspectorContainer);
}
@ -221,7 +215,7 @@ public class ClusterInspector : Editor {
if (selectedNucleus.synapses != null) {
foreach (Synapse synapse in selectedNucleus.synapses) {
Nucleus input = synapse.nucleus;
Nucleus input = synapse.neuron;
AddToLayer(currentLayer, input);
// Debug.Log($"layer {layerIx} nucleus {input.name}");
}
@ -265,45 +259,14 @@ public class ClusterInspector : Editor {
// Draw selected Nucleus
if (expandArray) {
// if (this.currentNucleus is ReceptorArray receptor) {
// float maxValue = 0;
// foreach (Nucleus nucleus in receptor.instances) {
// float value = length(nucleus.outputValue);
// if (value > maxValue)
// maxValue = value;
// }
// float spacing = 400f / receptor.instances.Count();
// float margin = 10 + spacing / 2;
// float xMin = 150 - size;
// float xMax = 150 + size;
// float yMin = 10 + margin - size / 2;
// float yMax = 400 - margin + size;
// Vector3[] verts = new Vector3[4] {
// new(xMin, yMin, 0),
// new(xMax, yMin, 0),
// new(xMax, yMax, 0),
// new(xMin, yMax, 0)
// };
// Handles.color = Color.black;
// Handles.DrawAAConvexPolygon(verts);
// int row = 0;
// foreach (Nucleus nucleus in receptor.instances) {
// Vector3 pos = new(150, margin + row * spacing, 0.0f);
// Handles.color = Color.white;
// // The selected nucleus highlight ring
// Handles.DrawSolidDisc(pos, Vector3.forward, size + 2);
// DrawNucleus(nucleus, pos, maxValue, size);
// row++;
// }
// }
// else
if (this.currentNucleus is IReceptor receptor1) {
float maxValue = 0;
foreach (Nucleus nucleus in receptor1.nucleiArray) {
float value = length(nucleus.outputValue);
if (value > maxValue)
maxValue = value;
if (nucleus is Neuron neuron) {
float value = length(neuron.outputValue);
if (value > maxValue)
maxValue = value;
}
}
float spacing = 400f / receptor1.nucleiArray.Count();
@ -348,7 +311,13 @@ public class ClusterInspector : Editor {
Handles.color = Color.white;
// The selected nucleus highlight ring
Handles.DrawSolidDisc(position, Vector3.forward, size + 2);
DrawNucleus(this.currentNucleus, position, length(this.currentNucleus.outputValue), 20);
float maxValue = 1;
if (this.currentNucleus is Neuron neuron)
maxValue = length(neuron.outputValue);
else if (this.currentNucleus is Cluster cluster)
maxValue = length(cluster.defaultOutput.outputValue);
DrawNucleus(this.currentNucleus, position, maxValue, 20);
}
}
@ -356,12 +325,17 @@ public class ClusterInspector : Editor {
Handles.color = Color.white;
// The selected nucleus highlight ring
Handles.DrawSolidDisc(position, Vector3.forward, size + 2);
DrawNucleus(this.currentNucleus, position, length(this.currentNucleus.outputValue), 20);
float maxValue = 1;
if (this.currentNucleus is Neuron neuron)
maxValue = length(neuron.outputValue);
else if (this.currentNucleus is Cluster cluster)
maxValue = length(cluster.defaultOutput.outputValue);
DrawNucleus(this.currentNucleus, position, maxValue, 20);
}
}
private void DrawReceivers(Nucleus nucleus, Vector3 parentPos, float size) {
List<Nucleus> receivers = null;
List<Nucleus> receivers;
if (nucleus is Neuron neuron)
receivers = neuron.receivers;
else if (nucleus is Cluster cluster)
@ -417,20 +391,25 @@ public class ClusterInspector : Editor {
int neuronCount = 0;
List<Nucleus[]> drawnArrays = new();
foreach (Synapse synapse in nucleus.synapses) {
if (synapse.nucleus is Receptor receptor) {
if (synapse.neuron == null)
continue;
if (synapse.neuron is Receptor receptor) {
if (drawnArrays.Contains(receptor.nucleiArray))
continue;
drawnArrays.Add(receptor.nucleiArray);
}
else if (synapse.nucleus.parent is ClusterReceptor clusterReceptor) {
else if (synapse.neuron.parent is ClusterReceptor clusterReceptor) {
if (drawnArrays.Contains(clusterReceptor.nucleiArray))
continue;
drawnArrays.Add(clusterReceptor.nucleiArray);
}
float value = length(synapse.nucleus.outputValue) * synapse.weight;
// Debug.Log($"{synapse.nucleus.name}: {value} {length(synapse.nucleus.outputValue)} {synapse.weight}");
if (value > maxValue)
maxValue = value;
if (synapse.neuron is Neuron synapseNeuron) {
float value = length(synapseNeuron.outputValue) * synapse.weight;
// Debug.Log($"{synapse.nucleus.name}: {value} {length(synapse.nucleus.outputValue)} {synapse.weight}");
if (value > maxValue)
maxValue = value;
}
neuronCount++;
}
@ -441,12 +420,15 @@ public class ClusterInspector : Editor {
int row = 0;
drawnArrays = new();
foreach (Synapse synapse in nucleus.synapses) {
if (synapse.nucleus is Receptor neuron) {
if (synapse.neuron is null)
continue;
if (synapse.neuron is Receptor neuron) {
if (drawnArrays.Contains(neuron.nucleiArray))
continue;
drawnArrays.Add(neuron.nucleiArray);
}
else if (synapse.nucleus.parent is ClusterReceptor clusterReceptor) {
else if (synapse.neuron.parent is ClusterReceptor clusterReceptor) {
if (drawnArrays.Contains(clusterReceptor.nucleiArray))
continue;
drawnArrays.Add(clusterReceptor.nucleiArray);
@ -458,18 +440,20 @@ public class ClusterInspector : Editor {
if (Application.isPlaying) {
if (maxValue == 0 || !float.IsFinite(maxValue))
maxValue = 1;
float brightness = length(synapse.nucleus.outputValue * synapse.weight) / maxValue;
float brightness = 0;
if (synapse.neuron is Neuron synapseNeuron)
brightness = length(synapseNeuron.outputValue * synapse.weight) / maxValue;
color = new Color(brightness, brightness, brightness, 1f);
}
if (synapse.nucleus.parent != null && synapse.nucleus.parent != this.currentNucleus.parent) {
if (synapse.neuron.parent != null && synapse.neuron.parent != this.currentNucleus.parent) {
// the synapse nucleus is part of a subcluster
DrawNucleus(synapse.nucleus.parent, pos, maxValue, size, color);
DrawNucleus(synapse.neuron.parent, pos, maxValue, size, color);
}
// else if (synapse.nucleus.cluster != null && synapse.nucleus.cluster != this.currentNucleus.cluster) {
// DrawNucleus(synapse.nucleus.parent, pos, maxValue, size, color);
// }
else {
DrawNucleus(synapse.nucleus, pos, maxValue, size, color);
DrawNucleus(synapse.neuron, pos, maxValue, size, color);
}
row++;
}
@ -478,7 +462,9 @@ public class ClusterInspector : Editor {
private void DrawNucleus(Nucleus nucleus, Vector3 position, float maxValue, float size) {
Color color;
if (Application.isPlaying) {
float brightness = length(nucleus.outputValue) / maxValue;
float brightness = 0;
if (nucleus is Neuron neuron)
brightness = length(neuron.outputValue) / maxValue;
color = new Color(brightness, brightness, brightness, 1f);
}
else
@ -505,11 +491,11 @@ public class ClusterInspector : Editor {
fontStyle = FontStyle.Bold,
};
if (nucleus is Receptor receptor1) {
// draw the array size label
if (expandArray) { //} && receptor1.array.nuclei.First() == this.currentNucleus) {
if (nucleus is IReceptor receptor1) {
if (expandArray) {
// Put array indices above elements
style.alignment = TextAnchor.LowerCenter;
Vector3 labelPos1 = position + Vector3.down * (size + 5); // below disc along up axis
Vector3 labelPos1 = position + Vector3.down * (size + 5); // below disc
int colonPos1 = nucleus.name.IndexOf(":");
if (colonPos1 > 0) {
string extName = nucleus.name[(colonPos1 + 2)..];
@ -517,6 +503,7 @@ public class ClusterInspector : Editor {
}
}
else {
// draw the array size label
if (color.grayscale > 0.5f)
style.normal.textColor = Color.black;
else
@ -525,59 +512,27 @@ public class ClusterInspector : Editor {
style.normal.textColor = Color.white;
}
}
// if (nucleus is ReceptorArray receptor) {
// if (color.grayscale > 0.5f)
// style.normal.textColor = Color.black;
// else
// style.normal.textColor = Color.white;
// Handles.Label(labelPosition, receptor.instances.Count().ToString(), style);
// }
if (nucleus is ClusterReceptor clusterReceptor) {
// draw the array size label
if (expandArray && clusterReceptor.array.nuclei.First() == this.currentNucleus) {
style.alignment = TextAnchor.LowerCenter;
Vector3 labelPos2 = position + Vector3.down * (size + 5); // below disc along up axis
int colonPos2 = nucleus.name.IndexOf(":");
if (colonPos2 > 0) {
string extName = nucleus.name[(colonPos2 + 2)..];
Handles.Label(labelPos2, extName, style);
}
}
else {
if (color.grayscale > 0.5f)
style.normal.textColor = Color.black;
else
style.normal.textColor = Color.white;
Handles.Label(labelPosition, clusterReceptor.array.nuclei.Length.ToString(), style);
style.normal.textColor = Color.white;
if (expandArray == false || nucleus is not IReceptor) {
// put name below nucleus
Vector3 labelPos = position - Vector3.down * (size + 5); // below neuron
style.alignment = TextAnchor.UpperCenter;
int colonPos = nucleus.name.IndexOf(":");
if (colonPos > 0 && colonPos < nucleus.name.Length - 2) {
// if it is an array, we should not show the :0 of the first element
string baseName = nucleus.name[..colonPos];
Handles.Label(labelPos, baseName, style);
}
else
Handles.Label(labelPos, nucleus.name, style);
}
// if (expandArray && nucleus is Receptor receptor2 && receptor2.array.nuclei.First() == this.currentNucleus) {
// style.alignment = TextAnchor.LowerCenter;
// Vector3 labelPos = position + Vector3.down * (size + 5); // below disc along up axis
// int colonPos = nucleus.name.IndexOf(":");
// if (colonPos > 0) {
// string extName = nucleus.name[(colonPos + 2)..];
// Handles.Label(labelPos, extName, style);
// }
// }
// else {
style.alignment = TextAnchor.UpperCenter;
Vector3 labelPos = position - Vector3.down * (size + 5); // below disc along up axis
int colonPos = nucleus.name.IndexOf(":");
if (expandArray && nucleus is Receptor) { //} || (colonPos > 0 && colonPos < nucleus.name.Length - 2)) {
// string baseName = nucleus.name[..colonPos];
// Handles.Label(labelPos, baseName, style);
}
else
Handles.Label(labelPos, nucleus.name, style);
// }
// Draw Cluster ring
if (nucleus is Cluster) {
Handles.color = Color.white;
Handles.DrawWireDisc(position, Vector3.forward, size + 10);
Handles.DrawWireDisc(position, Vector3.forward, size + 5);
}
// Tooltip
@ -599,9 +554,13 @@ public class ClusterInspector : Editor {
private void HandleMouseHover(Nucleus nucleus, Rect rect) {
GUIContent tooltip;
tooltip = new(
$"{nucleus.name}" +
$"\nValue: {length(nucleus.outputValue)}");
if (nucleus is Neuron neuron) {
tooltip = new(
$"{nucleus.name}" +
$"\nValue: {length(neuron.outputValue)}");
}
else
tooltip = new($"{nucleus.name}");
Vector2 mousePosition = Event.current.mousePosition;
@ -619,11 +578,11 @@ public class ClusterInspector : Editor {
else
expandArray = false;
}
else if (nucleus is ReceptorInstance receptor) {
this.currentNucleus = receptor.receptor;
expandArray = false;
BuildLayers();
}
// else if (nucleus is ReceptorInstance receptor) {
// this.currentNucleus = receptor.receptor;
// expandArray = false;
// BuildLayers();
// }
else {
this.currentNucleus = nucleus;
expandArray = false;
@ -631,6 +590,7 @@ public class ClusterInspector : Editor {
}
}
private VisualElement inspectorIMGUIContainer;
private bool showSynapses = true;
private bool showActivation = true;
protected bool breakOnWake = false;
@ -645,9 +605,9 @@ public class ClusterInspector : Editor {
// create a SerializedObject wrapper so Unity inspector controls work (and Undo)
SerializedObject so = new(prefabAsset);
IMGUIContainer container = new(() => InspectorHandler(so));
this.inspectorIMGUIContainer = new IMGUIContainer(() => InspectorHandler(so));
inspectorContainer.Add(container);
inspectorContainer.Add(inspectorIMGUIContainer);
}
void InspectorHandler(SerializedObject serializedObject) {
@ -675,11 +635,16 @@ public class ClusterInspector : Editor {
this.currentNucleus.name = newName;
this.prefab.RefreshOutputs();
outputsField.choices = this.prefab.outputs.Select(output => output.name).ToList();
anythingChanged = true;
}
if (Application.isPlaying) {
GUIContent nameLabel = new("Output", this.currentNucleus.outputValue.ToString());
EditorGUILayout.FloatField(nameLabel, length(this.currentNucleus.outputValue));
if (currentNucleus is Neuron currentNeuron1) {
GUIContent nameLabel = new("Output", currentNeuron1.outputValue.ToString());
EditorGUILayout.FloatField(nameLabel, length(currentNeuron1.outputValue));
}
else
EditorGUILayout.LabelField(" ");
}
else
EditorGUILayout.LabelField(" ");
@ -687,162 +652,164 @@ public class ClusterInspector : Editor {
if (this.currentNucleus is MemoryCell memory) {
memory.staticMemory = EditorGUILayout.Toggle("Static Memory", memory.staticMemory);
}
if (this.currentNucleus is ReceptorArray receptor) {
EditorGUILayout.BeginHorizontal();
EditorGUILayout.IntField("Receptor size", receptor.instances.Count());
if (GUILayout.Button("Add")) {
Undo.RecordObject(prefabAsset, "Receptor add " + prefabAsset.name);
receptor.AddReceptor(this.prefab);
anythingChanged = true;
}
if (GUILayout.Button("Del")) {
Undo.RecordObject(prefabAsset, "Receptor delete " + prefabAsset.name);
receptor.RemoveReceptor();
anythingChanged = true;
}
EditorGUILayout.EndHorizontal();
}
if (this.currentNucleus is Receptor receptor1) {
if (this.currentNucleus is IReceptor receptor1) {
EditorGUILayout.BeginHorizontal();
EditorGUILayout.IntField("Array size", receptor1.nucleiArray.Count());
if (GUILayout.Button("Add")) {
Undo.RecordObject(prefabAsset, "Array add " + prefabAsset.name);
//receptor1.array.AddNucleus(this.prefab);
receptor1.AddReceptorElement(this.prefab);
anythingChanged = true;
}
if (GUILayout.Button("Del")) {
Undo.RecordObject(prefabAsset, "Array delete " + prefabAsset.name);
//receptor1.array.RemoveNucleus();
receptor1.RemoveReceptorElement();
anythingChanged = true;
}
EditorGUILayout.EndHorizontal();
}
else if (this.currentNucleus is ClusterReceptor receptor2) {
EditorGUILayout.BeginHorizontal();
EditorGUILayout.IntField("Array size", receptor2.array.nuclei.Count());
if (GUILayout.Button("Add")) {
Undo.RecordObject(prefabAsset, "Array add " + prefabAsset.name);
receptor2.array.AddNucleus(this.prefab);
anythingChanged = true;
}
if (GUILayout.Button("Del")) {
Undo.RecordObject(prefabAsset, "Array delete " + prefabAsset.name);
receptor2.array.RemoveNucleus();
anythingChanged = true;
}
EditorGUILayout.EndHorizontal();
}
// Synapses
if (this.currentNucleus is not Receptor && this.currentNucleus is not ClusterReceptor) {
showSynapses = EditorGUILayout.BeginFoldoutHeaderGroup(showSynapses, "Synapses");
if (showSynapses) {
anythingChanged = ConnectNucleus(this.prefab, this.currentNucleus);
anythingChanged = AddSynapse(this.prefab, this.currentNucleus);
EditorGUILayout.Space();
if (this.currentNucleus is Neuron neuron2) {
Neuron.CombinatorType newCombinator = (Neuron.CombinatorType)EditorGUILayout.EnumPopup("Combinator", neuron2.combinator);
anythingChanged |= newCombinator != neuron2.combinator;
neuron2.combinator = newCombinator;
}
EditorGUIUtility.wideMode = true;
EditorGUIUtility.labelWidth = 100;
Vector3 newBias = EditorGUILayout.Vector3Field("Bias", this.currentNucleus.bias);
anythingChanged |= newBias != this.currentNucleus.bias;
this.currentNucleus.bias = newBias;
Nucleus[] array = null;
int elementIx = -1;
if (this.currentNucleus.synapses.Count > 0) {
Synapse[] synapses = this.currentNucleus.synapses.ToArray();
foreach (Synapse synapse in synapses) {
if (synapse.nucleus == null)
if (synapse.neuron == null)
continue;
if (array != null) {
if (array.Contains(synapse.nucleus))
continue;
if (array.Contains(synapse.nucleus.parent))
if (synapse.neuron.parent is Cluster iCluster && elementIx > 0) {
int thisElementIx = Cluster.GetNucleusIndex(iCluster.clusterNuclei, synapse.neuron);
if (thisElementIx == elementIx)
continue;
else
elementIx = thisElementIx;
}
// if (array.Contains(synapse.nucleus))
// continue;
else if (array.Contains(synapse.neuron.parent))
continue;
}
else {
if (synapse.nucleus.parent is ClusterReceptor clusterReceptor)
array = clusterReceptor.nucleiArray;
else if (synapse.nucleus is Receptor receptor2) // && receptor2.array != null && receptor2.array.nuclei.Length > 1)
array = receptor2.nucleiArray;
if (synapse.neuron.parent is IReceptor iReceptor) {
array = iReceptor.nucleiArray;
if (iReceptor is Cluster iCluster)
elementIx = Cluster.GetNucleusIndex(iCluster.clusterNuclei, synapse.neuron);
}
// else if (synapse.nucleus is Receptor receptor2) // && receptor2.array != null && receptor2.array.nuclei.Length > 1)
// array = receptor2.nucleiArray;
}
EditorGUILayout.Space();
if (Application.isPlaying) {
Vector3 value = synapse.nucleus.outputValue * synapse.weight;
GUIContent synapseValueLabel = new(synapse.nucleus.name, synapse.nucleus.outputValue.ToString());
EditorGUILayout.FloatField(synapseValueLabel, length(synapse.nucleus.outputValue));
if (synapse.neuron is Neuron synapseNeuron) {
Vector3 value = synapseNeuron.outputValue * synapse.weight;
GUIContent synapseValueLabel = new(synapse.neuron.name, synapseNeuron.outputValue.ToString());
EditorGUILayout.FloatField(synapseValueLabel, length(synapseNeuron.outputValue));
}
}
else {
EditorGUILayout.BeginHorizontal();
if (synapse.nucleus.parent != null && synapse.nucleus.parent != this.currentNucleus) {
if (synapse.neuron.parent != null && synapse.neuron.parent != this.currentNucleus) {
// If it is a cluster
GUIStyle labelStyle = new(GUI.skin.label);
float labelWidth = labelStyle.CalcSize(new GUIContent($"{synapse.nucleus.clusterPrefab.name}.")).x;
EditorGUILayout.LabelField($"{synapse.nucleus.clusterPrefab.name}", GUILayout.Width(labelWidth));
string[] options = synapse.nucleus.parent.clusterNuclei.Select(n => n.name).ToArray();
int selectedIndex = System.Array.IndexOf(options, synapse.nucleus.name);
float labelWidth = 200;
if (synapse.neuron.clusterPrefab != null) {
labelWidth = labelStyle.CalcSize(new GUIContent($"{synapse.neuron.parent.baseName}.")).x;
GUILayout.Label($"{synapse.neuron.parent.baseName}", GUILayout.Width(labelWidth));
}
string[] options = synapse.neuron.parent.clusterNuclei.Select(n => n.name).ToArray();
int selectedIndex = System.Array.IndexOf(options, synapse.neuron.name);
int newIndex = EditorGUILayout.Popup(selectedIndex, options);
if (newIndex != selectedIndex && synapse.nucleus.parent.clusterNuclei[newIndex] is Neuron newNeuron)
if (newIndex != selectedIndex && synapse.neuron.parent.clusterNuclei[newIndex] is Neuron newNeuron)
ChangeSynapse(synapse, newNeuron);
}
else
EditorGUILayout.LabelField(synapse.nucleus.name);
if (GUILayout.Button("Disconnect") && synapse.nucleus is Neuron synapseNeuron) {
GUILayout.Label(synapse.neuron.name);
bool disconnecting = GUILayout.Button("Disconnect", GUILayout.Width(80));
if (disconnecting && synapse.neuron is Neuron synapseNeuron) {
synapseNeuron.RemoveReceiver(this.currentNucleus);
this.prefab.GarbageCollection();
anythingChanged = true;
}
EditorGUILayout.EndHorizontal();
}
EditorGUI.indentLevel++;
synapse.weight = EditorGUILayout.FloatField("Weight", synapse.weight);
float newWeight = EditorGUILayout.FloatField("Weight", synapse.weight);
if (newWeight != synapse.weight) {
if (synapse.neuron.parent is IReceptor receptor) {
Nucleus[] receptorArray = receptor.nucleiArray;
foreach (Synapse s in this.currentNucleus.synapses) {
if (s.neuron.parent is IReceptor r && r.nucleiArray == receptorArray)
s.weight = newWeight;
}
}
else
synapse.weight = newWeight;
anythingChanged = true;
}
EditorGUI.indentLevel--;
}
}
EditorGUILayout.Space();
anythingChanged |= ConnectNucleus(this.prefab, this.currentNucleus);
anythingChanged |= AddSynapse(this.prefab, this.currentNucleus);
}
EditorGUILayout.EndFoldoutHeaderGroup();
}
// Activation
EditorGUILayout.Space();
showActivation = EditorGUILayout.BeginFoldoutHeaderGroup(showActivation, "Activation");
if (showActivation) {
if (this.currentNucleus is Neuron neuron) {
if (this.currentNucleus is not MemoryCell) {
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("Activation Curve", GUILayout.Width(150));
if (neuron.curveMax > 0)
EditorGUILayout.CurveField(neuron.curve, Color.cyan, new Rect(0, 0, 1, neuron.curveMax));
else
EditorGUILayout.CurveField(neuron.curve, Color.cyan, new Rect(0, neuron.curveMax, 1, -neuron.curveMax));
Neuron.CurvePresets newPreset = (Neuron.CurvePresets)EditorGUILayout.EnumPopup(neuron.curvePreset, GUILayout.Width(100));
anythingChanged |= newPreset != neuron.curvePreset;
neuron.curvePreset = newPreset;
EditorGUILayout.EndHorizontal();
}
if (neuron is Receptor receptor2) {
if (receptor2.nucleiArray == null || receptor2.nucleiArray.Count() == 0)
receptor2.array = new NucleusArray(neuron);
}
}
if (this.currentNucleus is not Cluster) {
EditorGUILayout.Space();
}
EditorGUILayout.EndFoldoutHeaderGroup();
showActivation = EditorGUILayout.BeginFoldoutHeaderGroup(showActivation, "Activation");
if (showActivation) {
if (this.currentNucleus is Neuron neuron) {
if (this.currentNucleus is not MemoryCell) {
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("Activation Curve", GUILayout.Width(150));
if (neuron.curveMax > 0)
EditorGUILayout.CurveField(neuron.curve, Color.cyan, new Rect(0, 0, 1, neuron.curveMax));
else
EditorGUILayout.CurveField(neuron.curve, Color.cyan, new Rect(0, neuron.curveMax, 1, -neuron.curveMax));
Neuron.CurvePresets newPreset = (Neuron.CurvePresets)EditorGUILayout.EnumPopup(neuron.curvePreset, GUILayout.Width(100));
anythingChanged |= newPreset != neuron.curvePreset;
neuron.curvePreset = newPreset;
EditorGUILayout.EndHorizontal();
}
if (neuron is Receptor receptor2) {
if (receptor2.nucleiArray == null || receptor2.nucleiArray.Count() == 0)
receptor2.array = new NucleusArray(neuron);
}
}
EditorGUILayout.Space();
}
EditorGUILayout.EndFoldoutHeaderGroup();
}
if (GUILayout.Button("Delete this neuron"))
DeleteNucleus(this.currentNucleus);
@ -854,8 +821,8 @@ public class ClusterInspector : Editor {
EditorGUILayout.Space();
breakOnWake = EditorGUILayout.Toggle("Break on wake", breakOnWake);
if (breakOnWake) {
if (this.currentNucleus.isSleeping == false)
if (breakOnWake && this.currentNucleus is Neuron currentNeuron) {
if (currentNeuron.isSleeping == false)
Debug.Break();
}
trace = EditorGUILayout.Toggle("Trace", trace);
@ -870,26 +837,20 @@ public class ClusterInspector : Editor {
void OnSceneGUI(SceneView sceneView) {
if (this.gameObject != null) {
if (this.currentNucleus is ReceptorArray receptor && expandArray) {
foreach (Nucleus nucleus in receptor.instances) {
Vector3 worldVector = this.gameObject.transform.TransformVector(nucleus.outputValue);
Handles.color = Color.yellow;
Handles.DrawLine(this.gameObject.transform.position, this.gameObject.transform.position + worldVector);
}
}
else {
if (this.currentNucleus is Receptor receptor1) {
foreach (Nucleus nucleus in receptor1.nucleiArray) {
Vector3 worldVector = this.gameObject.transform.TransformVector(nucleus.outputValue);
if (this.currentNucleus is IReceptor receptor) {
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 {
Vector3 worldVector = this.gameObject.transform.TransformVector(this.currentNucleus.outputValue);
}
else {
if (this.currentNucleus is Neuron currentNeuron) {
Vector3 worldVector = this.gameObject.transform.TransformVector(currentNeuron.outputValue);
Handles.color = Color.yellow;
Handles.DrawLine(this.gameObject.transform.position, this.gameObject.transform.position + worldVector);
}
}
}
@ -905,21 +866,21 @@ public class ClusterInspector : Editor {
case Nucleus.Type.MemoryCell:
AddMemoryCellInput(nucleus);
break;
case Nucleus.Type.Selector:
AddSelectorInput(nucleus);
break;
// case Nucleus.Type.Selector:
// AddSelectorInput(nucleus);
// break;
case Nucleus.Type.Cluster:
AddClusterInput(nucleus);
break;
case Nucleus.Type.Pulsar:
AddPulsarInput(nucleus);
break;
// case Nucleus.Type.Pulsar:
// AddPulsarInput(nucleus);
// break;
case Nucleus.Type.Receptor:
AddReceptorInput(nucleus);
break;
case Nucleus.Type.ReceptorArray:
AddReceptorArrayInput(nucleus);
break;
// case Nucleus.Type.ReceptorArray:
// AddReceptorArrayInput(nucleus);
// break;
case Nucleus.Type.ClusterReceptor:
AddClusterReceptorInput(nucleus);
break;
@ -935,19 +896,19 @@ public class ClusterInspector : Editor {
BuildLayers();
}
protected void AddSelectorInput(Nucleus nucleus) {
Selector newSelector = new(this.prefab, "New Selector");
newSelector.AddReceiver(nucleus);
this.currentNucleus = newSelector;
BuildLayers();
}
// protected void AddSelectorInput(Nucleus nucleus) {
// Selector newSelector = new(this.prefab, "New Selector");
// newSelector.AddReceiver(nucleus);
// this.currentNucleus = newSelector;
// BuildLayers();
// }
protected void AddPulsarInput(Nucleus nucleus) {
Pulsar newPulsar = new(this.prefab, "New Pulsar");
newPulsar.AddReceiver(nucleus);
this.currentNucleus = newPulsar;
BuildLayers();
}
// protected void AddPulsarInput(Nucleus nucleus) {
// Pulsar newPulsar = new(this.prefab, "New Pulsar");
// newPulsar.AddReceiver(nucleus);
// this.currentNucleus = newPulsar;
// BuildLayers();
// }
protected virtual void AddMemoryCellInput(Nucleus nucleus) {
MemoryCell newMemory = new(this.prefab, "New memory cell");
@ -959,6 +920,10 @@ public class ClusterInspector : Editor {
protected virtual void AddClusterInput(Nucleus nucleus) {
ClusterPickerWindow.ShowPicker(brain => OnClusterPicked(nucleus, brain), "Select Cluster");
}
private void OnClusterPicked(Nucleus nucleus, ClusterPrefab prefab) {
Cluster subclusterInstance = new(prefab, this.prefab);
subclusterInstance.defaultOutput.AddReceiver(nucleus);
}
protected virtual void AddReceptorInput(Nucleus nucleus) {
Receptor newReceptor = new(this.prefab, "New Receptor");
@ -967,25 +932,14 @@ public class ClusterInspector : Editor {
BuildLayers();
}
protected virtual void AddReceptorArrayInput(Nucleus nucleus) {
// ReceptorArray newReceptor = new(this.prefab, "New Receptor");
// newReceptor.AddReceiver(nucleus);
// this.currentNucleus = newReceptor;
// BuildLayers();
}
protected virtual void AddClusterReceptorInput(Nucleus nucleus) {
ClusterPickerWindow.ShowPicker(prefab => OnClusterReceptorPicked(nucleus, prefab), "Select Cluster");
}
private void OnClusterPicked(Nucleus nucleus, ClusterPrefab prefab) {
Cluster subclusterInstance = new(prefab, this.prefab);
subclusterInstance.defaultOutput.AddReceiver(nucleus);
}
private void OnClusterReceptorPicked(Nucleus nucleus, ClusterPrefab selectedPrefab) {
ClusterReceptor clusterInstance = new(selectedPrefab, this.prefab, "New " + selectedPrefab.name);
clusterInstance.defaultOutput.AddReceiver(nucleus);
this.currentNucleus = clusterInstance;
BuildLayers();
}
private void EditCluster(Cluster subCluster) {
@ -995,32 +949,41 @@ public class ClusterInspector : Editor {
var editor = Editor.CreateEditor(subCluster.prefab);
}
int selectedConnectNucleus = -1;
// Connect to another nucleus in the same cluster
protected virtual bool ConnectNucleus(ClusterPrefab cluster, Nucleus nucleusToConnect) {
if (cluster == null)
return false;
IEnumerable<Nucleus> synapseNuclei = this.currentNucleus.synapses
.Where(synapse => synapse.nucleus != null)
.Select(synapse => synapse.nucleus);
.Where(synapse => synapse.neuron != null)
.Select(synapse => synapse.neuron);
IEnumerable<Nucleus> nuclei = cluster.nuclei
.Except(synapseNuclei);
IEnumerable<string> nucleiNames = nuclei.Select(n => n.name);
IEnumerable<string> nucleiNames = nuclei
.Select(n => {
int idx = n.name.IndexOf(':');
return idx < 0 ? n.name : n.name[..idx];
})
.Distinct();
string[] names = nucleiNames.ToArray();
int selectedIndex = -1;
selectedIndex = EditorGUILayout.Popup("Connect", selectedIndex, names);
if (selectedIndex < 0)
return false;
EditorGUILayout.BeginHorizontal();
selectedConnectNucleus = EditorGUILayout.Popup(selectedConnectNucleus, names);
bool connecting = GUILayout.Button("Connect", GUILayout.Width(80));
EditorGUILayout.EndHorizontal();
if (connecting) {
Nucleus nucleus = nuclei.ElementAt(selectedConnectNucleus);
if (nucleus is IReceptor receptor)
receptor.AddArrayReceiver(this.currentNucleus);
else if (nucleus is Neuron neuron)
neuron.AddReceiver(this.currentNucleus);
else if (nucleus is Cluster subCluster)
subCluster.defaultOutput.AddReceiver(this.currentNucleus);
Nucleus nucleus = nuclei.ElementAt(selectedIndex);
if (nucleus is Neuron neuron)
neuron.AddReceiver(this.currentNucleus);
else if (nucleus is Cluster subCluster)
subCluster.defaultOutput.AddReceiver(this.currentNucleus);
return true;
}
return connecting;
}
protected virtual void DeleteNucleus(Nucleus nucleus) {
@ -1049,24 +1012,61 @@ public class ClusterInspector : Editor {
BuildLayers();
}
Nucleus.Type selectedType = Nucleus.Type.None;
protected virtual bool AddSynapse(ClusterPrefab cluster, Nucleus nucleus) {
if (cluster == null)
return false;
Nucleus.Type selectedType = (Nucleus.Type)EditorGUILayout.EnumPopup("Add", Nucleus.Type.None);
if (selectedType == Nucleus.Type.None)
return false;
EditorGUILayout.BeginHorizontal();
selectedType = (Nucleus.Type)EditorGUILayout.EnumPopup(selectedType);
bool connecting = GUILayout.Button("Add", GUILayout.Width(80));
EditorGUILayout.EndHorizontal();
AddInput(selectedType, this.currentNucleus);
return true;
if (connecting) {
AddInput(selectedType, this.currentNucleus);
}
return connecting;
// if (selectedType == Nucleus.Type.None)
// return false;
// AddInput(selectedType, this.currentNucleus);
// return true;
}
protected virtual void ChangeSynapse(Synapse synapse, Neuron newNucleus) {
Neuron synapseNeuron = synapse.nucleus as Neuron;
if (synapse.nucleus.parent is Cluster subCluster && subCluster.prefab != this.prefab) {
// it is a neuron in a subcluster
synapseNeuron.RemoveReceiver(this.currentNucleus);
newNucleus.AddReceiver(this.currentNucleus);
Neuron synapseNeuron = synapse.neuron as Neuron;
if (synapse.neuron.parent is Cluster subCluster && subCluster.prefab != this.prefab) {
if (synapse.neuron.parent is ClusterReceptor receptor) {
// the new nucleus is part of a (cluster) receptor,
// so we have to change all synapses to this nucleus array elements
int oldNucleusIx = Cluster.GetNucleusIndex(subCluster.clusterNuclei, synapse.neuron);
int newNucleusIx = Cluster.GetNucleusIndex(subCluster.clusterNuclei, newNucleus);
foreach (Nucleus element in receptor.nucleiArray) {
if (element is not ClusterReceptor clusterReceptor)
continue;
// Get the same neuron as the synapse.nucleus in a different element
// of the ClusterReceptor array
Nucleus oldElementNucleus = clusterReceptor.clusterNuclei[oldNucleusIx];
if (oldElementNucleus is not Neuron oldElementNeuron)
continue;
// Get the same neuron as newNucleus in a different element
// of the ClusterReceptor array
Nucleus newElementNucleus = clusterReceptor.clusterNuclei[newNucleusIx];
if (newElementNucleus is not Neuron newElementNeuron)
continue;
oldElementNeuron.RemoveReceiver(this.currentNucleus);
newElementNeuron.AddReceiver(this.currentNucleus);
// Now find the synapse which pointed to the old Neuron
// Synapse synapseForUpdate = this.currentNucleus.GetSynapse(oldElementNeuron);
// synapseForUpdate.nucleus = newElementNeuron;
}
}
else {
// it is a neuron in a subcluster
synapseNeuron.RemoveReceiver(this.currentNucleus);
newNucleus.AddReceiver(this.currentNucleus);
}
}
else {
synapseNeuron.RemoveReceiver(this.currentNucleus);
@ -1077,12 +1077,12 @@ public class ClusterInspector : Editor {
protected virtual void DisconnectNucleus(Neuron nucleus) {
if (this.currentNucleus.clusterPrefab == null)
return;
string[] names = this.currentNucleus.synapses.Select(synapse => synapse.nucleus.name).ToArray();
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.nucleus as Neuron;
Neuron synapseNeuron = synapse.neuron as Neuron;
synapseNeuron.RemoveReceiver(this.currentNucleus);
}
}

View File

@ -17,8 +17,10 @@ public class NanoBrainComponent_Editor : Editor {
public void OnEnable() {
component = target as NanoBrain;
if (Application.isPlaying == false)
brainProp = serializedObject.FindProperty(nameof(NanoBrain.defaultBrain));
if (Application.isPlaying == false && serializedObject != null) {
string propertyName = nameof(NanoBrain.defaultBrain);
brainProp = serializedObject.FindProperty(propertyName);
}
}
public override VisualElement CreateInspectorGUI() {

View File

@ -3,49 +3,71 @@ using UnityEngine;
public interface IReceptor {
public string GetName();
// public NucleusArray array {
// get; set;
// }
public Nucleus[] nucleiArray { get; set; }
public void AddReceptorElement(ClusterPrefab prefab);
public void RemoveReceptorElement();
public void AddArrayReceiver(Nucleus receiverToAdd, float weight = 1);
public void ProcessStimulus(Vector3 inputValue, int thingId = 0, string thingName = null);
}
public static class IReceptorHelpers {
public static Nucleus[] AddReceptorElement(Nucleus[] nucleiArray, ClusterPrefab prefab) {
if (nucleiArray.Length == 0) {
public static void AddReceptorElement(IReceptor receptor, ClusterPrefab prefab) {
if (receptor.nucleiArray.Length == 0) {
Debug.LogError("Empty perceptoid array, cannot add");
return null;
}
int newLength = nucleiArray.Length + 1;
int newLength = receptor.nucleiArray.Length + 1;
Nucleus[] newArray = new Nucleus[newLength];
for (int i = 0; i < nucleiArray.Length; i++)
newArray[i] = nucleiArray[i];
if (nucleiArray[0] is Nucleus nucleus) {
string baseName = receptor.GetName();
int colonPos = baseName.IndexOf(":");
if (colonPos > 0)
baseName = baseName[..colonPos];
for (int i = 0; i < receptor.nucleiArray.Length; i++)
newArray[i] = receptor.nucleiArray[i];
if (receptor.nucleiArray[0] is Nucleus nucleus) {
newArray[newLength - 1] = nucleus.Clone(prefab);
newArray[newLength - 1].name += $": {newLength - 1}";
newArray[newLength - 1].name = $"{baseName}: {newLength - 1}";
}
return newArray;
foreach (Nucleus element in receptor.nucleiArray) {
if (element is IReceptor receptorElement) {
receptorElement.nucleiArray = newArray;
}
}
}
public static Nucleus[] RemoveReceptorElement(Nucleus[] nucleiArray) {
int newLength = nucleiArray.Length - 1;
public static void RemoveReceptorElement(IReceptor receptor) {
int newLength = receptor.nucleiArray.Length - 1;
if (newLength == 0) {
Debug.LogWarning("Perceptoid array cannot be empty");
return null;
}
Nucleus[] newPerceptei = new Nucleus[newLength];
Nucleus[] newArray = new Nucleus[newLength];
for (int i = 0; i < newLength; i++)
newPerceptei[i] = nucleiArray[i];
newArray[i] = receptor.nucleiArray[i];
// Delete the last perception
if (nucleiArray[newLength] is Nucleus nucleus)
Neuron.Delete(nucleus); //this._nuclei[newLength]);
if (receptor.nucleiArray[newLength] is Nucleus nucleus)
Neuron.Delete(nucleus);
foreach (Nucleus element in receptor.nucleiArray) {
if (element is IReceptor receptorElement) {
receptorElement.nucleiArray = newArray;
}
}
}
public static void AddArrayReceiver(IReceptor receptor, Nucleus receiverToAdd, float weight = 1) {
foreach (Nucleus element in receptor.nucleiArray) {
if (element is Cluster cluster)
cluster.defaultOutput.AddReceiver(receiverToAdd, weight);
if (element is Neuron neuron)
neuron.AddReceiver(receiverToAdd, weight);
}
return newPerceptei;
}
}

8
Icons.meta Normal file
View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 885c5a70637820322b07e023ce18fdd5
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

View File

@ -0,0 +1,117 @@
fileFormatVersion: 2
guid: 288088fdc016525a59f83f1c608e514d
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 0
wrapV: 0
wrapW: 0
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@ -0,0 +1,117 @@
fileFormatVersion: 2
guid: e16264b4b7305e5c5b5b1389d6b2f13e
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 0
wrapV: 0
wrapW: 0
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

@ -0,0 +1,117 @@
fileFormatVersion: 2
guid: 948c13386d926b7bbbca85239a974d85
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 0
wrapV: 0
wrapW: 0
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@ -11,20 +11,25 @@ MonoBehaviour:
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 60a957541c24c57e78018c202ebb1d9b, type: 3}
m_Name: Identity
m_EditorClassIdentifier: Assembly-CSharp::Cluster
m_EditorClassIdentifier: Assembly-CSharp::ClusterPrefab
nuclei:
- rid: 2243601383627161705
- rid: 2262690531574022216
references:
version: 2
RefIds:
- rid: 2243601383627161705
- rid: -2
type: {class: , ns: , asm: }
- rid: 2262690531574022216
type: {class: Neuron, ns: , asm: Assembly-CSharp}
data:
_name: Output
name: Output
clusterPrefab: {fileID: 11400000}
parent:
rid: -2
trace: 0
bias: {x: 0, y: 0, z: 0}
_synapses: []
_receivers: []
_array:
rid: 2243601383627161706
combinator: 0
_curvePreset: 0
curve:
serializedVersion: 2
@ -51,10 +56,4 @@ MonoBehaviour:
m_PostInfinity: 2
m_RotationOrder: 4
curveMax: 1
average: 0
- rid: 2243601383627161706
type: {class: NucleusArray, ns: , asm: Assembly-CSharp}
data:
_nuclei:
- rid: 2243601383627161705
name: Output
_receivers: []

View File

@ -1,7 +1,5 @@
using System;
using UnityEngine;
using Unity.Mathematics;
using static Unity.Mathematics.math;
[Serializable]
public class MemoryCell : Neuron {

View File

@ -20,7 +20,7 @@ public class NanoBrain : MonoBehaviour {
public static void UpdateWeight(Cluster brain, string name, float weight) {
Nucleus root = brain.defaultOutput;
foreach (Synapse synapse in root.synapses) {
if (synapse.nucleus.name == name) {
if (synapse.neuron.name == name) {
if (synapse.weight != weight) {
synapse.weight = weight;
// Debug.Log($"Updated weight for {name}");

View File

@ -1,82 +0,0 @@
/*
using UnityEngine;
using Unity.Mathematics;
using static Unity.Mathematics.math;
[System.Serializable]
public class Neuroid : Neuron {
public bool average = false;
public Neuroid(Cluster brain, string name) : base(name) {
this.cluster = brain;
if (this.cluster != null) {
this.cluster.nuclei.Add(this);
}
else
Debug.LogError("No neuroid network");
}
public Neuroid(string name) : base(name) { }
public override INucleus Clone() {
Neuroid clone = new(this.name) {
cluster = this.cluster,
array = this.array,
curve = this.curve,
curvePreset = this.curvePreset,
curveMax = this.curveMax,
average = this.average
};
if (clone.cluster != null)
clone.cluster.nuclei.Add(clone);
foreach (Synapse synapse in this.synapses) {
Synapse clonedSynapse = clone.AddSynapse(synapse.nucleus);
clonedSynapse.weight = synapse.weight;
}
foreach (INucleus receiver in this.receivers) {
clone.AddReceiver(receiver);
}
return clone;
}
public override void UpdateState() {
float3 sum = new(0, 0, 0);
int n = 0;
//Applying the weight factgors
foreach (Synapse synapse in this.synapses) {
sum = sum + (synapse.weight * synapse.nucleus.outputValue);
if (lengthsq(synapse.nucleus.outputValue) != 0)
n++;
}
if (average)
sum /= n;
// Activation function
Vector3 result;
switch (this.curvePreset) {
case CurvePresets.Linear:
result = sum;
break;
case CurvePresets.Sqrt:
result = normalize(sum) * System.MathF.Sqrt(length(sum));
break;
case CurvePresets.Power:
result = normalize(sum) * System.MathF.Pow(length(sum), 2);
break;
case CurvePresets.Reciprocal:
result = normalize(sum) * (1 / length(sum));
break;
default:
float activatedValue = this.curve.Evaluate(length(sum));
result = normalize(sum) * activatedValue;
break;
}
UpdateResult(result);
}
}
*/

View File

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 771f64aec709af240a39b1d918bbc829

View File

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEditor;
using Unity.Mathematics;
@ -117,6 +116,23 @@ public class Neuron : Nucleus {
#endregion Serialization
protected float3 _outputValue;
public virtual float3 outputValue {
get { return _outputValue; }
set {
_outputValue = value;
if (this.isFiring)
WhenFiring?.Invoke();
}
}
public bool isFiring => length(_outputValue) > 0.5f;
public Action WhenFiring;
public virtual bool isSleeping => lengthsq(this.outputValue) == 0;
[NonSerialized]
public int stale = 1000;
public readonly int staleValueForSleep = 20;
// this clone the nucleus without the synapses and receivers
public override Nucleus ShallowCloneTo(Cluster newParent) {
Neuron clone = new(newParent, this.name);
@ -128,7 +144,7 @@ public class Neuron : Nucleus {
Neuron clone = new(prefab, this.name);
CloneFields(clone);
foreach (Synapse synapse in this.synapses) {
Synapse clonedSynapse = clone.AddSynapse(synapse.nucleus);
Synapse clonedSynapse = clone.AddSynapse(synapse.neuron);
clonedSynapse.weight = synapse.weight;
}
foreach (Nucleus receiver in this.receivers) {
@ -148,7 +164,7 @@ public class Neuron : Nucleus {
public static void Delete(Nucleus nucleus) {
foreach (Synapse synapse in nucleus.synapses) {
if (synapse.nucleus is Neuron synapse_nucleus) {
if (synapse.neuron is Neuron synapse_nucleus) {
if (synapse_nucleus.receivers.Count > 1) {
// there is another nucleus feeding into this input nucleus
synapse_nucleus.receivers.RemoveAll(r => r == nucleus);
@ -162,13 +178,14 @@ public class Neuron : Nucleus {
if (nucleus is Neuron neuron) {
foreach (Nucleus receiver in neuron.receivers) {
if (receiver != null && receiver.synapses != null)
receiver.synapses.RemoveAll(s => s.nucleus == nucleus);
receiver.synapses.RemoveAll(s => s.neuron == nucleus);
}
} else if (nucleus is Cluster cluster) {
}
else if (nucleus is Cluster cluster) {
// remove all receivers for this cluster
foreach (Neuron output in cluster.outputs) {
foreach (Nucleus receiver in output.receivers) {
receiver.synapses.RemoveAll(s => s.nucleus == output);
receiver.synapses.RemoveAll(s => s.neuron == output);
}
}
}
@ -198,29 +215,30 @@ public class Neuron : Nucleus {
public float3 CombinatorSum() {
float3 sum = this.bias;
foreach (Synapse synapse in this.synapses)
sum += synapse.weight * synapse.nucleus.outputValue;
sum += synapse.weight * synapse.neuron.outputValue;
return sum;
}
public float3 CombinatorProduct() {
float3 product = this.bias;
foreach (Synapse synapse in this.synapses)
product *= synapse.weight * synapse.nucleus.outputValue;
foreach (Synapse synapse in this.synapses) {
product *= synapse.weight * synapse.neuron.outputValue;
}
return product;
}
public float3 CombinatorMax() {
float3 max = this.bias;
float maxSqrLength = lengthsq(max);
float maxLength = length(max);
//Applying the weight factors
foreach (Synapse synapse in this.synapses) {
float3 input = synapse.weight * synapse.nucleus.outputValue;
float3 input = synapse.weight * synapse.neuron.outputValue;
float inputSqrlength = lengthsq(input);
if (inputSqrlength > maxSqrLength) {
float inputLength = length(input);
if (inputLength > maxLength) {
max = input;
maxSqrLength = inputSqrlength;
maxLength = inputLength;
}
}
return max;
@ -230,7 +248,7 @@ public class Neuron : Nucleus {
#region Activator
protected Func<float3, float3> Activator => this.curvePreset switch {
public Func<float3, float3> Activator => this.curvePreset switch {
CurvePresets.Linear => ActivatorLinear,
CurvePresets.Sqrt => ActivatorSqrt,
CurvePresets.Power => ActivatorPower,
@ -279,18 +297,8 @@ public class Neuron : Nucleus {
}
public virtual void AddReceiver(Nucleus receiverToAdd, float weight = 1) {
// if (this is IReceptor receptor) {
// foreach (Nucleus element in receptor.array.nuclei) {
// if (element is Neuron neuron) {
// neuron._receivers.Add(receiverToAdd);
// receiverToAdd.AddSynapse(element, weight);
// }
// }
// }
// else {
this._receivers.Add(receiverToAdd);
receiverToAdd.AddSynapse(this, weight);
// }
this._receivers.Add(receiverToAdd);
receiverToAdd.AddSynapse(this, weight);
}
public virtual void RemoveReceiver(Nucleus receiverToRemove) {
@ -298,23 +306,29 @@ public class Neuron : Nucleus {
foreach (Nucleus element in receptor.nucleiArray) {
if (element is Neuron neuron) {
neuron._receivers.RemoveAll(receiver => receiver == receiverToRemove);
receiverToRemove.synapses.RemoveAll(synapse => synapse.nucleus == neuron);
receiverToRemove.synapses.RemoveAll(synapse => synapse.neuron == neuron);
}
}
}
else {
this._receivers.RemoveAll(receiver => receiver == receiverToRemove);
receiverToRemove.synapses.RemoveAll(synapse => synapse.nucleus == this);
receiverToRemove.synapses.RemoveAll(synapse => synapse.neuron == this);
}
}
#endregion Receivers
public virtual void ProcessStimulus(Vector3 inputValue, string thingName = null) {
public override void ProcessStimulus(Vector3 inputValue, int thingId = 0, string thingName = null) {
if (this.parent is ClusterReceptor clusterReceptor)
clusterReceptor.ProcessStimulus(this, inputValue, thingId, thingName);
else
ProcessStimulusDirect(inputValue, thingId, thingName);
}
public void ProcessStimulusDirect(Vector3 inputValue, int thingId = 0, string thingName = null) {
this.stale = 0;
this.bias = inputValue;
this.parent.UpdateFromNucleus(this);
}
}

1305
NewVelocity.asset Normal file

File diff suppressed because it is too large Load Diff

8
NewVelocity.asset.meta Normal file
View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 61eea9f818639ec20b7a7bf4e86fff66
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,8 +1,6 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using Unity.Mathematics;
using static Unity.Mathematics.math;
[Serializable]
public abstract class Nucleus {
@ -13,22 +11,6 @@ public abstract class Nucleus {
[SerializeReference]
public Cluster parent;
protected float3 _outputValue;
public virtual float3 outputValue {
get { return _outputValue; }
set {
_outputValue = value;
if (this.isFiring)
WhenFiring?.Invoke();
}
}
public bool isFiring => length(_outputValue) > 0.5f;
public Action WhenFiring;
public virtual bool isSleeping => lengthsq(this.outputValue) == 0;
[NonSerialized]
public int stale = 1000;
public readonly int staleValueForSleep = 20;
public bool trace = false;
public abstract Nucleus ShallowCloneTo(Cluster parent);
@ -38,11 +20,8 @@ public abstract class Nucleus {
None,
Neuron,
MemoryCell,
Selector,
Cluster,
Pulsar,
Receptor,
ReceptorArray,
ClusterReceptor,
}
@ -54,7 +33,7 @@ public abstract class Nucleus {
private List<Synapse> _synapses = new();
public List<Synapse> synapses => _synapses;
public Synapse AddSynapse(Nucleus sendingNucleus, float weight = 1.0f) {
public Synapse AddSynapse(Neuron sendingNucleus, float weight = 1.0f) {
Synapse synapse = new(sendingNucleus, weight);
this.synapses.Add(synapse);
return synapse;
@ -62,19 +41,17 @@ public abstract class Nucleus {
public Synapse GetSynapse(Nucleus sender) {
foreach (Synapse synapse in this.synapses)
if (synapse.nucleus == sender)
if (synapse.neuron == sender)
return synapse;
return null;
}
public void RemoveSynapse(Nucleus sendingNucleus) {
this.synapses.RemoveAll(synapse => synapse.nucleus == sendingNucleus);
this.synapses.RemoveAll(synapse => synapse.neuron == sendingNucleus);
}
#endregion Synapses
#region Update
public abstract void UpdateStateIsolated();
@ -83,11 +60,13 @@ public abstract class Nucleus {
}
public virtual void SetBias(Vector3 inputValue) {
this.stale = 0;
this.bias = inputValue;
this.parent.UpdateFromNucleus(this);
}
public virtual void ProcessStimulus(Vector3 inputValue, int thingId = 0, string thingName = "") {
}
#endregion Update
}

View File

@ -69,9 +69,11 @@ public class NucleusArray {
private Nucleus FindReceiver(int thingId, float3 inputValue) {
// No existing nucleus for this thing
float inputMagnitude = length(inputValue);
Nucleus selectedReceiver = null;
Neuron selectedReceiver = null;
float selectedMagnitude = 0;
foreach (Nucleus receiver in this._nuclei) {
foreach (Nucleus nucleusReceiver in this._nuclei) {
if (nucleusReceiver is not Neuron receiver)
continue;
if (thingReceivers.ContainsValue(receiver) == false) {
// We found an unusued receiver
thingReceivers.Add(thingId, receiver);
@ -111,6 +113,10 @@ public class NucleusArray {
public virtual void ProcessStimulus(int thingId, Vector3 inputValue, string thingName = null) {
CleanupReceivers();
if (this._nuclei[0] is Neuron neuron)
inputValue = neuron.Activator(inputValue);
if (!thingReceivers.TryGetValue(thingId, out Nucleus selectedReceiver)) {
// No existing nucleus for this thing
selectedReceiver = FindReceiver(thingId, inputValue);
@ -127,15 +133,14 @@ public class NucleusArray {
}
if (selectedReceiver is Neuron selectedNucleus)
selectedNucleus.ProcessStimulus(inputValue);
//selectedReceiver.parent.UpdateFromNucleus(selectedReceiver);
selectedNucleus.ProcessStimulusDirect(inputValue);
}
private void CleanupReceivers() {
// Remove a thing-receiver connection when the nucleus is inactive
List<int> receiversToRemove = new();
foreach (KeyValuePair<int, Nucleus> item in thingReceivers) {
if (item.Value != null && item.Value.isSleeping)
if (item.Value != null && item.Value is Neuron neuron && neuron.isSleeping)
receiversToRemove.Add(item.Key);
}
foreach (int thingId in receiversToRemove) {

View File

@ -1,105 +0,0 @@
/*
using UnityEngine;
[System.Serializable]
public class Perceptoid : Neuroid {
// A neuroid which has no neurons as input
// But receives value from a receptor
public NanoBrain brain;
public Receptor receptor;
public string baseName;
public int thingId;
//[SerializeField]
// Needs serialization!!!!
[SerializeReference]
public PercepteiArray array;
#region Serialization
[SerializeField]
public int thingType;
public override void Rebuild(NanoBrain brain) {
base.Rebuild(brain);
this.receptor = Receptor.GetReceptor(brain, thingType);
this.receptor.perceptei.Add(this);
if (string.IsNullOrEmpty(this.baseName))
this.baseName = this.name;
}
public override void Deserialize(Nucleus nucleus) {
base.Deserialize(nucleus);
if (nucleus is Perceptoid perceptoid)
this.receptor.thingType = perceptoid.thingType;
// Point all receivers to this perceptoid instead of the default nucleus
foreach (INucleus receiver in nucleus.receivers) {
foreach (Synapse synapse in receiver.synapses) {
if (synapse.nucleus == nucleus)
synapse.nucleus = this;
}
}
// Point all synapses to this perceptoid instead of the default nucleus
// foreach (Synapse synapse in nucleus.synapses) {
// foreach (INucleus r in synapse.nucleus.receivers) {
// if (r == nucleus)
// this.receiver = this;
// }
// }
// Copying disabled for now
// // Copy all the synapses
// this.synapses = nucleus.synapses;
// // Copy all receivers
// this.receivers = nucleus.receivers;
}
#endregion Serialization
public Perceptoid(NanoBrain brain, int thingType, string name = "sensor") : base(name) {
this.brain = brain;
this.cluster = brain.cluster;
if (this.cluster != null) {
brain.perceptei.Add(this);
}
else
Debug.LogError("No neuroid network");
this.nucleusType = nameof(Perceptoid);
this.name = name;
this.baseName = name;
this.thingType = thingType;
this.receptor = Receptor.GetReceptor(brain, thingType);
this.receptor.perceptei.Add(this);
this.array = new PercepteiArray(this);
}
public Perceptoid(PercepteiArray array) : base(array.name) {
this.array = array;
Perceptoid source = array.perceptei[0];
this.brain = source.brain;
this.cluster = source.cluster;
if (this.brain != null) {
this.brain.perceptei.Add(this);
}
else
Debug.LogError("No neuroid network");
this.nucleusType = nameof(Perceptoid);
this.name = source.baseName;
this.baseName = source.baseName;
this.thingType = source.thingType;
this.receptor = Receptor.GetReceptor(this.brain, this.thingType);
this.receptor.perceptei.Add(this);
}
public override void UpdateState() {
Vector3 result = this.receptor.localPosition;
UpdateResult(result);
}
}
*/

View File

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 702f634001a21a9d7ae1057c8ce356e9

View File

@ -1,54 +0,0 @@
using System;
using Unity.Mathematics;
/// <summary>
/// The Pulsar represents a type of neuron that operates based on
/// the product of its weighted inputs rather than the traditional summation.
/// Drawing inspiration from the concept of pulsars in astrophysics
/// —highly magnetized rotating neutron stars that emit beams of radiation—
/// the Pulsar could symbolize dynamic, focused output based on the interaction of multiple factors.
/// </summary>
/// Multiplicative Functionality:
/// Instead of summing inputs, the Pulsar takes the weighted product of its inputs.
/// This means that all inputs must be active (non-zero) for the neuron to "pulse" or activate.
/// Output Behavior:
/// The output could amplify or diminish depending on the magnitude of the inputs.
/// The product would be sensitive to small values,
/// which means that even a small input could significantly lower the overall output if multiplied.
/// Activation Mechanism:
/// The activation function can further refine the output from the product result.
/// For instance, a certain threshold could be used to determine if a pulse occurs.
/// Modeling Complex Interactions:
/// The Pulsar could be particularly beneficial for modeling situations where interactions multiply rather than add.
/// This is useful in fields such as economics (e.g., compound growth models),
/// biology (e.g., interaction of hormones), and machine learning where multiplicative relationships exist.
[Serializable]
public class Pulsar : Neuron {
public Pulsar(Cluster parent, string name) : base(parent, name) {
// To prevent mistakes, bias one (instead of zero for standard neurons)
this.bias = new float3(1, 1, 1);
}
public Pulsar(ClusterPrefab parent, string name) : base(parent, name) {
// To prevent mistakes, bias one (instead of zero for standard neurons)
this.bias = new float3(1, 1, 1);
}
public override Nucleus ShallowCloneTo(Cluster newParent) {
Pulsar clone = new(newParent, this.name);
CloneFields(clone);
return clone;
}
public override void UpdateStateIsolated() {
float3 product = this.bias;
//Applying the weight factors
foreach (Synapse synapse in this.synapses) {
float3 input = synapse.weight * synapse.nucleus.outputValue;
product *= input;
}
// Activation function
this.outputValue = Activator(product);
}
}

View File

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 46bd155173053a01585411c3e07f85d4

View File

@ -48,24 +48,19 @@ public class Receptor : Neuron, IReceptor {
}
public void AddReceptorElement(ClusterPrefab prefab) {
this.nucleiArray = IReceptorHelpers.AddReceptorElement(this.nucleiArray, prefab);
IReceptorHelpers.AddReceptorElement(this, prefab);
}
public void RemoveReceptorElement() {
this.nucleiArray = IReceptorHelpers.RemoveReceptorElement(this.nucleiArray);
IReceptorHelpers.RemoveReceptorElement(this);
}
public virtual void AddArrayReceiver(Nucleus receiverToAdd, float weight = 1) {
foreach (Nucleus element in this._array.nuclei) {
if (element is Neuron neuron) {
neuron.AddReceiver(receiverToAdd, weight);
}
}
IReceptorHelpers.AddArrayReceiver(this, receiverToAdd, weight);
}
public override void UpdateStateIsolated() {
this.outputValue = this.bias;
//Debug.Log($"Receptor {this.name} outputvalue = {this.outputValue}");
}
public override void UpdateNuclei() {
@ -76,7 +71,7 @@ public class Receptor : Neuron, IReceptor {
}
}
public virtual void ProcessStimulus(Vector3 inputValue, int thingId = 0, string thingName = null) {
public override void ProcessStimulus(Vector3 inputValue, int thingId = 0, string thingName = null) {
this._array ??= new NucleusArray(this.parent);
this._array.ProcessStimulus(thingId, inputValue, thingName);
}

View File

@ -1,261 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Unity.Mathematics;
using static Unity.Mathematics.math;
[Serializable]
public class ReceptorInstance : Nucleus {
public ReceptorInstance(Cluster parent, string name) {
this.parent = parent;
this.name = name;
// We explicitly do not add this to the parent, as it is serialized in the ReceptorArray
}
public ReceptorInstance(ClusterPrefab prefab, string name) {
this.clusterPrefab = prefab;
this.name = name;
// We explicitly do not add this to the prefab, as it is serialized in the ReceptorArray
}
public override Nucleus ShallowCloneTo(Cluster parent) {
ReceptorInstance clone = new(parent, name + " +1") {
receptor = this.receptor
};
return clone;
}
public override Nucleus Clone(ClusterPrefab prefab) {
ReceptorInstance clone = new(prefab, name) {
receptor = this.receptor
};
return clone;
}
[SerializeReference]
public ReceptorArray receptor;
public override void UpdateStateIsolated() {
}
}
[Serializable]
public class ReceptorArray : Nucleus {
public ReceptorArray(Cluster parent, string name) {
this.parent = parent;
this.name = name;
this._instances = new ReceptorInstance[1];
this._instances[0] = new ReceptorInstance(parent, this.name + "[0]") {
receptor = this
};
this.parent?.clusterNuclei.Add(this);
}
public ReceptorArray(ClusterPrefab prefab, string name) {
this.clusterPrefab = prefab;
this.name = name;
this._instances = new ReceptorInstance[1];
this._instances[0] = new ReceptorInstance(prefab, this.name + "[0]") {
receptor = this
};
if (this.clusterPrefab != null)
this.clusterPrefab.nuclei.Add(this);
else
Debug.LogError("No prefab when adding receptor to prefab");
}
public override Nucleus ShallowCloneTo(Cluster parent) {
ReceptorArray clone = new(parent, name) {
_instances = new ReceptorInstance[this.instances.Length]
};
for (int ix = 0; ix < this.instances.Length; ix++) {
clone._instances[ix] = new ReceptorInstance(parent, $"{this.name} [{ix}]") {
receptor = clone
};
}
return clone;
}
public override Nucleus Clone(ClusterPrefab prefab) {
ReceptorArray clone = new(prefab, this.name) {
_instances = new ReceptorInstance[this.instances.Length]
};
for (int ix = 0; ix < this.instances.Length; ix++) {
clone._instances[ix] = new ReceptorInstance(prefab, this.name) {
receptor = this
};
}
foreach (Synapse synapse in this.synapses) {
Synapse clonedSynapse = clone.AddSynapse(synapse.nucleus);
clonedSynapse.weight = synapse.weight;
}
// foreach (Nucleus receiver in this.receivers) {
// clone.AddReceiver(receiver);
// }
return clone;
}
[SerializeReference]
private ReceptorInstance[] _instances = new ReceptorInstance[0];
public ReceptorInstance[] instances {
get {
return _instances;
}
}
public void AddReceptor(ClusterPrefab prefab) {
if (this._instances.Length == 0) {
Debug.LogError("Empty receptor array, cannot add");
return;
}
int newLength = this._instances.Length + 1;
ReceptorInstance[] newArray = new ReceptorInstance[newLength];
for (int i = 0; i < this._instances.Length; i++)
newArray[i] = this._instances[i];
ReceptorInstance newReceptor = (ReceptorInstance)this._instances[0].Clone(prefab);
newReceptor.name = $"{this.name} [{this._instances.Length}]";
newArray[newLength - 1] = newReceptor;
this._instances = newArray;
}
public void RemoveReceptor() {
int newLength = this._instances.Length - 1;
if (newLength == 0) {
Debug.LogWarning("Receptor array cannot be empty");
return;
}
ReceptorInstance[] newPerceptei = new ReceptorInstance[newLength];
for (int i = 0; i < newLength; i++)
newPerceptei[i] = this._instances[i];
// Delete the last perception
if (this._instances[newLength] is Nucleus nucleus)
Neuron.Delete(nucleus); //this._nuclei[newLength]);
this._instances = newPerceptei;
}
private Dictionary<int, Nucleus> thingReceivers = new();
// public override void ProcessStimulus(int thingId, Vector3 inputValue, string thingName = null) {
// ProcessStimulus(inputValue, thingId, thingName);
// }
public virtual void ProcessStimulus(Vector3 inputValue, int thingId = 0, string thingName = null) {
CleanupReceivers();
if (!thingReceivers.TryGetValue(thingId, out Nucleus selectedReceiver)) {
//Debug.Log($" no receiver found for {thingId}");
// No existing nucleus for this thing
selectedReceiver = SelectReceptor(thingId, inputValue);
}
if (selectedReceiver == null)
return;
if (thingName != null) {
string baseName = selectedReceiver.name;
int colonPos = selectedReceiver.name.IndexOf(":");
if (colonPos > 0)
baseName = selectedReceiver.name[..colonPos];
selectedReceiver.name = baseName + ": " + thingName;
}
//if (selectedReceiver is Neuron selectedNucleus) {
selectedReceiver.stale = 0;
selectedReceiver.outputValue = inputValue;
this.parent.UpdateFromNucleus(this);
//selectedNucleus.ProcessStimulus(inputValue);
//}
}
private void CleanupReceivers() {
// Remove a thing-receiver connection when the nucleus is inactive
List<int> receiversToRemove = new();
thingReceivers ??= new();
foreach (KeyValuePair<int, Nucleus> item in thingReceivers) {
if (item.Value.isSleeping)
receiversToRemove.Add(item.Key);
}
foreach (int thingId in receiversToRemove) {
Nucleus selectedReceiver = thingReceivers[thingId];
// Debug.Log($" removed receiver for {thingId}");
thingReceivers.Remove(thingId);
int colonPos = selectedReceiver.name.IndexOf(":");
if (colonPos > 0)
selectedReceiver.name = selectedReceiver.name[..colonPos];
}
}
private Nucleus SelectReceptor(int thingId, float3 inputValue) {
// No existing nucleus for this thing
float inputMagnitude = length(inputValue);
Nucleus selectedReceiver = null;
float selectedMagnitude = 0;
this._instances ??= new ReceptorInstance[0];
foreach (Nucleus receiver in this._instances) {
if (thingReceivers.ContainsValue(receiver) == false) {
// We found an unusued receiver
// Debug.Log($"{thingId} -> [{receiver.name}]");
thingReceivers.Add(thingId, receiver);
return receiver;
}
else if (receiver.isSleeping) {
// A sleeping receiver is not active and can therefore always be used
thingReceivers.Add(thingId, receiver);
Debug.Log($"{thingId} -> [{selectedReceiver.name}]");
return receiver;
}
else if (selectedReceiver == null) {
// If we haven't found a receiver yet, just start by taking the first
selectedReceiver = receiver;
selectedMagnitude = length(selectedReceiver.outputValue);
}
// Look for the receiver with the lowest magnitude
else {
float magnitude = length(receiver.outputValue);
if (magnitude < inputMagnitude && length(receiver.outputValue) < selectedMagnitude) {
selectedReceiver = receiver;
selectedMagnitude = length(selectedReceiver.outputValue);
}
}
}
if (selectedReceiver != null) {
// Replace the receiver
// Find the thingId current associated with the receiver
int keyToRemove = thingReceivers.FirstOrDefault(r => r.Value.Equals(selectedReceiver)).Key;
if (keyToRemove != 0 || thingReceivers.ContainsKey(keyToRemove))
thingReceivers.Remove(keyToRemove);
// And add the new association
thingReceivers.Add(thingId, selectedReceiver);
}
return selectedReceiver;
}
public override void UpdateStateIsolated() {
float3 sum = this.bias;
// Receptors do not have inputs, so we ignore the synapses
foreach (Nucleus nucleus in this._instances)
sum += nucleus.outputValue;
this.outputValue = sum / _instances.Length;
this.stale = 0;
UpdateNuclei();
}
public override void UpdateNuclei() {
foreach (Nucleus nucleus in this.instances) {
nucleus.stale++;
if (nucleus.stale > staleValueForSleep && lengthsq(nucleus.outputValue) > 0) {
nucleus.outputValue = Vector3.zero;
//this.UpdateStateIsolated();
this.parent.UpdateFromNucleus(this);
}
}
}
}

View File

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 9e915c8563642f23891b20522b3589cf

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

View File

@ -0,0 +1,117 @@
fileFormatVersion: 2
guid: cad48149d984d2eddae5808eb1517cb5
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 0
wrapV: 0
wrapW: 0
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@ -0,0 +1,117 @@
fileFormatVersion: 2
guid: 2e644ed036e8939bf94586314a4f4607
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 0
wrapV: 0
wrapW: 0
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,62 +0,0 @@
using System;
using Unity.Mathematics;
using static Unity.Mathematics.math;
[Serializable]
public class Selector : Neuron {
public Selector(Cluster parent, string name) : base(parent, name) { }
public Selector(ClusterPrefab parent, string name) : base(parent, name) {}
public override Nucleus ShallowCloneTo(Cluster newParent) {
Selector clone = new(newParent, this.name) {
// array = this.array,
curve = this.curve,
curvePreset = this.curvePreset,
curveMax = this.curveMax,
};
return clone;
}
public override void UpdateStateIsolated() { //float3 bias) {
float3 max = this.bias;
float maxSqrLength = lengthsq(max);
//Applying the weight factors
foreach (Synapse synapse in this.synapses) {
float3 input = synapse.weight * synapse.nucleus.outputValue;
float inputSqrlength = lengthsq(input);
if (inputSqrlength > maxSqrLength) {
max = input;
maxSqrLength = inputSqrlength;
}
}
// Activation function
float3 result;
switch (this.curvePreset) {
case CurvePresets.Linear:
result = max;
break;
case CurvePresets.Sqrt:
result = normalize(max) * System.MathF.Sqrt(length(max));
break;
case CurvePresets.Power:
result = normalize(max) * System.MathF.Pow(length(max), 2);
break;
case CurvePresets.Reciprocal: {
float magnitude = length(max);
if (magnitude > 0)
result = normalize(max) * (1 / magnitude);
else
result = float3(0, 0, 0);
break;
}
default:
float activatedValue = this.curve.Evaluate(length(max));
result = normalize(max) * activatedValue;
break;
}
this.outputValue = result;
}
}

View File

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 4218c2f3f15af944db0eadd6e1500d17

View File

@ -4,12 +4,12 @@ using UnityEngine;
[Serializable]
public class Synapse {
[SerializeReference]
public Nucleus nucleus;
public Neuron neuron;
public float weight;
public Synapse(Nucleus nucleus, float weight = 1.0f) {
this.nucleus = nucleus;
public Synapse(Neuron nucleus, float weight = 1.0f) {
this.neuron = nucleus;
this.weight = weight;
}
}

View File

@ -13,18 +13,31 @@ MonoBehaviour:
m_Name: Velocity
m_EditorClassIdentifier: Assembly-CSharp::ClusterPrefab
nuclei:
- rid: 2243601425629446539
- rid: 2262690551513219315
- rid: 2262690551513219316
- rid: 2262690551513219317
references:
version: 2
RefIds:
- rid: 2243601425629446539
- rid: -2
type: {class: , ns: , asm: }
- rid: 2262690551513219315
type: {class: Neuron, ns: , asm: Assembly-CSharp}
data:
_name: Output
_synapses: []
_receivers: []
_array:
rid: 2243601425629446540
name: Velocity
clusterPrefab: {fileID: 11400000}
parent:
rid: -2
trace: 0
bias: {x: 0, y: 0, z: 0}
_synapses:
- nucleus:
rid: 2262690551513219316
weight: 1
- nucleus:
rid: 2262690551513219317
weight: 1
combinator: 0
_curvePreset: 0
curve:
serializedVersion: 2
@ -51,10 +64,65 @@ MonoBehaviour:
m_PostInfinity: 2
m_RotationOrder: 4
curveMax: 1
average: 0
- rid: 2243601425629446540
type: {class: NucleusArray, ns: , asm: Assembly-CSharp}
_receivers: []
- rid: 2262690551513219316
type: {class: Neuron, ns: , asm: Assembly-CSharp}
data:
_nuclei:
- rid: 2243601425629446539
name: Output
name: Position
clusterPrefab: {fileID: 11400000}
parent:
rid: -2
trace: 0
bias: {x: 0, y: 0, z: 0}
_synapses: []
combinator: 0
_curvePreset: 0
curve:
serializedVersion: 2
m_Curve:
- serializedVersion: 3
time: 0
value: 0
inSlope: 0
outSlope: 1
tangentMode: 0
weightedMode: 0
inWeight: 0
outWeight: 0
- serializedVersion: 3
time: 1000
value: 1000
inSlope: 1
outSlope: 0
tangentMode: 0
weightedMode: 0
inWeight: 0
outWeight: 0
m_PreInfinity: 2
m_PostInfinity: 2
m_RotationOrder: 4
curveMax: 1
_receivers:
- rid: 2262690551513219315
- rid: 2262690551513219317
type: {class: MemoryCell, ns: , asm: Assembly-CSharp}
data:
name: New memory cell
clusterPrefab: {fileID: 11400000}
parent:
rid: -2
trace: 0
bias: {x: 0, y: 0, z: 0}
_synapses: []
combinator: 0
_curvePreset: 0
curve:
serializedVersion: 2
m_Curve: []
m_PreInfinity: 2
m_PostInfinity: 2
m_RotationOrder: 4
curveMax: 1
_receivers:
- rid: 2262690551513219315
staticMemory: 0