diff --git a/BrainSimMAC/BrainSimMACMain.cs b/BrainSimMAC/BrainSimMACMain.cs
index f7f1655..0d3d04e 100644
--- a/BrainSimMAC/BrainSimMACMain.cs
+++ b/BrainSimMAC/BrainSimMACMain.cs
@@ -5,12 +5,13 @@
*
* This file is part of Brain Simulator Thought and is licensed under
* the MIT License. You may use, copy, modify, merge, publish, distribute,
- * sublicense, and/or sell copies of this software under the terms of
+ * sublicense, and/or sell copies of the software under the terms of
* the MIT License.
*
* See the LICENSE file in the project root for full license information.
*/
using BrainSimulator;
+using System.Diagnostics;
using UKS;
@@ -67,15 +68,22 @@
//force the MainWindow to always be activated
moduleHandler.ActivateModule("MainWindow.py");
+const int loopDelayMs = 50;
while (true)
{
+ activeModulesRoot = moduleHandler.theUKS.Labeled("ActiveModule");
+ if (activeModulesRoot is null)
+ {
+ Thread.Sleep(loopDelayMs);
+ continue;
+ }
+
foreach (var module in activeModulesRoot.Children)
{
if (module.Label.Contains(".py"))
moduleHandler.RunScript(module.Label);
}
- activeModulesRoot = moduleHandler.theUKS.Labeled("ActiveModule");
for (int i = 0; i < moduleHandler.activePythonModules.Count; i++)
{
@@ -88,7 +96,12 @@
moduleHandler.activePythonModules.RemoveAt(i);
i--;
}
- catch { }
+ catch (Exception ex)
+ {
+ Debug.WriteLine($"BrainSimMAC: Close failed for {module.Item1}: {ex.Message}");
+ }
}
}
-}
+
+ Thread.Sleep(loopDelayMs);
+}
\ No newline at end of file
diff --git a/BrainSimulator/AssemblyInfo.cs b/BrainSimulator/AssemblyInfo.cs
index 8d0dca9..41a0b5f 100644
--- a/BrainSimulator/AssemblyInfo.cs
+++ b/BrainSimulator/AssemblyInfo.cs
@@ -10,8 +10,11 @@
*
* See the LICENSE file in the project root for full license information.
*/
+using System.Runtime.CompilerServices;
using System.Windows;
+[assembly: InternalsVisibleTo("Tests")]
+
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
diff --git a/BrainSimulator/BrainSim Thought.csproj b/BrainSimulator/BrainSim Thought.csproj
index 94e72b8..aa8d629 100644
--- a/BrainSimulator/BrainSim Thought.csproj
+++ b/BrainSimulator/BrainSim Thought.csproj
@@ -12,7 +12,7 @@
1.6.4
Iconsmall.ico
build$([System.DateTime]::UtcNow.ToString("yyyyMMddHHmmss"))
- AllEnabledByDefault
+ Default
true
@@ -25,9 +25,13 @@
preview
+
+
+
+
-
+
@@ -49,7 +53,7 @@
-
+
@@ -123,6 +127,11 @@
PreserveNewest
+
+
+ PreserveNewest
+
+
PreserveNewest
@@ -131,11 +140,6 @@
PreserveNewest
-
-
-
-
-
-
+
\ No newline at end of file
diff --git a/BrainSimulator/GlobalUsings.cs b/BrainSimulator/GlobalUsings.cs
new file mode 100644
index 0000000..ff7e145
--- /dev/null
+++ b/BrainSimulator/GlobalUsings.cs
@@ -0,0 +1,2 @@
+// Legacy BrainSim3 "Thing" alias removed 2026-07 with old Vision code.
+// Native code uses Thought / Link directly. Python interop updated separately if needed.
\ No newline at end of file
diff --git a/BrainSimulator/MainWindow.xaml.cs b/BrainSimulator/MainWindow.xaml.cs
index d8fa77e..f4e6e00 100644
--- a/BrainSimulator/MainWindow.xaml.cs
+++ b/BrainSimulator/MainWindow.xaml.cs
@@ -51,40 +51,6 @@ private void MainWindow_Loaded(object sender, RoutedEventArgs e)
//setup the python support
pythonPath = (string)Environment.GetEnvironmentVariable("PythonPath", EnvironmentVariableTarget.User);
- if (false)
- //if (string.IsNullOrEmpty(pythonPath))
- {
- var result1 = MessageBox.Show("Do you want to use Python Modules?", "Python?", MessageBoxButton.YesNo);
- if (result1 == MessageBoxResult.Yes)
- {
- string likeliPath = (string)Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
- likeliPath += @"\Programs\Python";
- System.Windows.Forms.OpenFileDialog openFileDialog = new()
- {
- Title = "SELECT path to Python .dll (or cancel for no Python support)",
- InitialDirectory = likeliPath,
- };
-
- // Show the file Dialog.
- System.Windows.Forms.DialogResult result = openFileDialog.ShowDialog();
- // If the user clicked OK in the dialog and
- if (result == System.Windows.Forms.DialogResult.OK)
- {
- pythonPath = openFileDialog.FileName;
- Environment.SetEnvironmentVariable("PythonPath", pythonPath, EnvironmentVariableTarget.User);
- }
- else
- {
- Environment.SetEnvironmentVariable("PythonPath", "", EnvironmentVariableTarget.User);
- }
- openFileDialog.Dispose();
- }
- else
- {
- pythonPath = "no";
- Environment.SetEnvironmentVariable("PythonPath", pythonPath, EnvironmentVariableTarget.User);
- }
- }
moduleHandler.PythonPath = pythonPath;
if (pythonPath != "no")
{
@@ -112,7 +78,7 @@ private void MainWindow_Loaded(object sender, RoutedEventArgs e)
CreateEmptyUKS();
}
}
- catch (Exception ex)
+ catch (Exception)
{
System.Windows.MessageBox.Show("UKS Content not loaded");
}
@@ -327,8 +293,9 @@ public static void ResumeEngine()
}
//THIS IS THE MAIN ENGINE LOOP
- private void Dt_Tick(object? sender, EventArgs e)
+ private void Dt_Tick(object sender, EventArgs e)
{
+ theUKS.BeginTraversalCycle();
Thought activeModuleParent = theUKS.Labeled("ActiveModule");
if (activeModuleParent is null) return;
foreach (Thought module in activeModuleParent.Children)
diff --git a/BrainSimulator/MainWindowFiles.cs b/BrainSimulator/MainWindowFiles.cs
index 4e7b79a..e5438bc 100644
--- a/BrainSimulator/MainWindowFiles.cs
+++ b/BrainSimulator/MainWindowFiles.cs
@@ -29,7 +29,6 @@ namespace BrainSimulator
///
public partial class MainWindow : Window
{
- private static StackPanel loadedModulesSP;
private bool LoadFile(string fileName)
{
SuspendEngine();
@@ -63,10 +62,10 @@ public void ReloadActiveModulesSP()
ActiveModuleSP.Children.Clear();
Thought activeModuleParent = theUKS.Labeled("ActiveModule");
+ if (activeModuleParent is null) { return; }
+
//TODO: Remove
activeModuleParent.AddParent("BrainSim");
-
- if (activeModuleParent is null) { return; }
var activeModules1 = activeModuleParent.Children;
activeModules1 = activeModules1.OrderBy(x => x.Label).ToList();
diff --git a/BrainSimulator/MainWindowPythonModules.cs b/BrainSimulator/MainWindowPythonModules.cs
index 7462c7f..367f105 100644
--- a/BrainSimulator/MainWindowPythonModules.cs
+++ b/BrainSimulator/MainWindowPythonModules.cs
@@ -53,9 +53,9 @@ static void RunScript(string moduleLabel)
{
bool firstTime = false;
//get the ModuleType
- Thing tModule = theUKS.Labeled(moduleLabel);
+ Thought tModule = theUKS.Labeled(moduleLabel);
if (tModule == null) { return; }
- Thing tModuleType = tModule.Parents.FindFirst(x => x.HasAncestorLabeled("AvailableModule"));
+ Thought tModuleType = tModule.Parents.FindFirst(x => x.HasAncestor("AvailableModule"));
if (tModuleType == null) return;
string moduleType = tModuleType.Label;
moduleType = moduleType.Replace(".py", "");
diff --git a/BrainSimulator/ModuleDescriptions.xml b/BrainSimulator/ModuleDescriptions.xml
index 127ebf2..bc9d944 100644
--- a/BrainSimulator/ModuleDescriptions.xml
+++ b/BrainSimulator/ModuleDescriptions.xml
@@ -172,14 +172,6 @@ Works together with SpeechParser, PhraseRecognizer, QueryResolution and SpeechOu
ModuleUKSClause
This dialog creates a Link between two Relastionships connected by a Clause Type. This can be used to create conditioinan links by using the clause type "if". That is, enter Mary can play outside if weather is sunny. This link will only be added to search results if the condition, weather is sunny, is true.
-
- ModuleVision
- This module iintended to provide input/test data for visual recognition. It performs generic CV algorithms to locate corners in a simple visual input.
-
-
- ModuleShape
- This module allows the system to save shapes and retrieve them by content.
-
ModuleAttributeBubble
This Agent scans the UKS Object tree looking for object Children which have common attributes which can be "bubbled" up to the parent.
diff --git a/BrainSimulator/ModuleHandler.cs b/BrainSimulator/ModuleHandler.cs
index f0cf3ef..7585b94 100644
--- a/BrainSimulator/ModuleHandler.cs
+++ b/BrainSimulator/ModuleHandler.cs
@@ -69,14 +69,7 @@ public void DeactivateModule(string moduleLabel)
{
Thought t = theUKS.Labeled(moduleLabel);
if (t is null) return;
- for (int i = 0; i < t.LinksTo.Count; i++)
- {
- Link r = t.LinksTo[i];
- r.To.Delete();
- }
t.Delete();
-
- return;
}
@@ -141,7 +134,7 @@ public void Close(string moduleLabel)
{
theModuleEntry.Item2.Close();
}
- catch { }
+ catch (Exception ex) { Debug.WriteLine($"Python Close failed for {theModuleEntry.Item1}: {ex.Message}"); }
}
}
}
@@ -221,9 +214,10 @@ public void RunScript(string moduleLabel)
catch (Exception ex)
{
activePythonModules.Remove(theModuleEntry);
- DeactivateModule(moduleLabel);
#if !CONSOLE_APP
- MainWindow.theWindow.ReloadActiveModulesSP();
+ MainWindow.theWindow?.DeactivateModule(moduleLabel);
+#else
+ DeactivateModule(moduleLabel);
#endif
Console.WriteLine("Fire method call failed for module: " + moduleLabel + " Reason: " + ex.Message);
}
diff --git a/BrainSimulator/ModuleViewMenu.cs b/BrainSimulator/ModuleViewMenu.cs
index 20ded0d..4b8b156 100644
--- a/BrainSimulator/ModuleViewMenu.cs
+++ b/BrainSimulator/ModuleViewMenu.cs
@@ -33,7 +33,6 @@ public void CreateContextMenu(ModuleBase nr, FrameworkElement r, ContextMenu cm
cm = new ContextMenu();
cm.SetValue(moduleNameProperty, nr.Label);
- StackPanel sp;
MenuItem mi = new MenuItem();
mi = new MenuItem();
mi.Header = "Delete";
@@ -100,17 +99,16 @@ private void Mi_Click(object sender, RoutedEventArgs e)
if ((string)mi.Header == "View Dialog Source")
theModuleType += "Dlg.xaml";
- string cwd = System.IO.Directory.GetCurrentDirectory();
- if (cwd.Contains("bin\\"))
- cwd = cwd.ToLower().Substring(0, cwd.IndexOf("bin\\"));
- string fileName = cwd + @"modules\" + theModuleType + ".cs";
+ string cwd = Directory.GetCurrentDirectory();
+ string binSegment = "bin" + Path.DirectorySeparatorChar;
+ int binIndex = cwd.IndexOf(binSegment, StringComparison.OrdinalIgnoreCase);
+ if (binIndex >= 0)
+ cwd = cwd.Substring(0, binIndex);
+ string fileName = Path.Combine(cwd, "modules", theModuleType + ".cs");
+ if (!File.Exists(fileName))
+ fileName = Path.Combine(cwd, "BrainSim2modules", theModuleType + ".cs");
if (File.Exists(fileName))
OpenSource(fileName);
- else
- {
- fileName = cwd + @"BrainSim2modules\" + theModuleType + ".cs";
- OpenSource(fileName);
- }
}
if ((string)mi.Header == "Delete")
{
@@ -176,5 +174,20 @@ public void DeleteModule(string moduleName)
ReloadActiveModulesSP();
}
+
+ public void DeactivateModule(string moduleLabel)
+ {
+ ModuleBase mb = activeModules.FindFirst(x => x.Label == moduleLabel);
+ if (mb is not null)
+ {
+ mb.CloseDlg();
+ mb.Closing();
+ activeModules.Remove(mb);
+ }
+ pythonModules.Remove(moduleLabel);
+ moduleHandler.pythonModules.Remove(moduleLabel);
+ moduleHandler.DeactivateModule(moduleLabel);
+ ReloadActiveModulesSP();
+ }
}
}
diff --git a/BrainSimulator/Modules/Agents/ModuleAddCounts.cs b/BrainSimulator/Modules/Agents/ModuleAddCounts.cs
index 5d480f8..7a0476b 100644
--- a/BrainSimulator/Modules/Agents/ModuleAddCounts.cs
+++ b/BrainSimulator/Modules/Agents/ModuleAddCounts.cs
@@ -33,7 +33,7 @@ public override void Fire()
UpdateDialog();
}
- public bool isEnabled { get; set; }
+ public new bool isEnabled { get; set; }
private Timer timer;
//private UKS.UKS theUKS1;
diff --git a/BrainSimulator/Modules/Agents/ModuleAttributeBubble.cs b/BrainSimulator/Modules/Agents/ModuleAttributeBubble.cs
index d0f782b..7dffa25 100644
--- a/BrainSimulator/Modules/Agents/ModuleAttributeBubble.cs
+++ b/BrainSimulator/Modules/Agents/ModuleAttributeBubble.cs
@@ -34,16 +34,46 @@ public override void Fire()
UpdateDialog();
}
- public bool isEnabled { get; set; }
+ public new bool isEnabled { get; set; }
private Timer timer;
- //private UKS.UKS theUKS1;
+ private Timer? debounceTimer;
+ private readonly HashSet pendingBubbleParents = new();
+ private readonly object debounceLock = new();
public string debugString = "Initialized\n";
private void Setup()
{
if (timer is null)
{
timer = new Timer(SameThreadCallback, null, 0, 10000);
+ debounceTimer = new Timer(FlushDebouncedBubbles, null, Timeout.Infinite, Timeout.Infinite);
+ theUKS.LinkAdded += OnLinkAdded;
+ }
+ }
+
+ private void OnLinkAdded(Link lnk)
+ {
+ if (!isEnabled || lnk.From is null) return;
+ lock (debounceLock)
+ {
+ foreach (Thought parent in lnk.From.Parents)
+ pendingBubbleParents.Add(parent);
+ }
+ debounceTimer?.Change(250, Timeout.Infinite);
+ }
+
+ private void FlushDebouncedBubbles(object? _)
+ {
+ HashSet batch;
+ lock (debounceLock)
+ {
+ batch = new HashSet(pendingBubbleParents);
+ pendingBubbleParents.Clear();
+ }
+ foreach (Thought parent in batch)
+ {
+ if (parent.HasAncestor("Object") || parent.HasAncestor("Unknown"))
+ BubbleChildAttributes(parent);
}
}
private void SameThreadCallback(object state)
@@ -89,123 +119,8 @@ public void DoTheWork()
}
void BubbleChildAttributes(Thought t)
{
- if (t.Children.Count == 0) return;
- if (t.Label == "Unknown") return;
-
- //build a List of all the Links which this thought's children have
- List itemCounts = new();
- foreach (Thought t1 in t.ChildrenWithSubclasses)
- {
- foreach (Link r in t1.LinksTo)
- {
- if (r.LinkType == Thought.IsA) continue;
- Thought useLinkType = GetInstanceType(r.LinkType);
-
- LinkDest foundItem = itemCounts.FindFirst(x => x.linkType == useLinkType && x.target == r.To);
- if (foundItem is null)
- {
- foundItem = new LinkDest { linkType = useLinkType, target = r.To };
- itemCounts.Add(foundItem);
- }
- foundItem.links.Add(r);
- }
- }
- if (itemCounts.Count == 0) return;
- var sortedItems = itemCounts.OrderByDescending(x => x.links.Count).ToList();
-
- List excludeTypes = new List() { "hasProperty", "isTransitive", "isCommutative", "inverseOf", "hasAttribute", "hasDigit" };
- //bubble the links
- for (int i = 0; i < sortedItems.Count; i++)
- {
- LinkDest rr = sortedItems[i];
- if (excludeTypes.Contains(rr.linkType.Label, comparer: StringComparer.OrdinalIgnoreCase)) continue;
-
- //find an existing link
- Link r = theUKS.GetLink(t, rr.linkType, rr.target);
- float currentWeight = (r is not null) ? r.Weight : 0f;
-
- //We need 1) count for this Thought, 2) count for any conflicting, 3) count without a reference
- float totalCount = t.Children.Count;
- float positiveCount = rr.links.FindAll(x => x.Weight > .5f).Count;
- float positiveWeight = rr.links.Sum(x => x.Weight);
- float negativeCount = 0;
- float negativeWeight = 0;
- //are there any conflicting links
- for (int j = 0; j < sortedItems.Count; j++)
- {
- if (j == i) continue;
- if (LinksConflict(rr, sortedItems[j]))
- {
- negativeCount += sortedItems[j].links.Count; //? why not += 1
- negativeWeight += sortedItems[j].links.Sum(x => x.Weight);
- }
- }
- float noInfoCount = totalCount - (positiveCount + negativeCount);
- positiveWeight += currentWeight + noInfoCount * 0.51f;
- if (noInfoCount < 0) noInfoCount = 0;
-
- if (negativeCount >= positiveCount)
- {
- if (r is not null)
- {
- t.RemoveLink(r);
- debugString += $"Removed {r} \n";
- }
- continue;
- }
-
-
- //calculate the new weight
- //If there is an existing weight, it is increased/decreased by a small amound and removed if it drops below .5
- //If there is no existing weight, it is assumed to start at 0.5.
- //TODO, replace this hardcoded "lookup table" with a formula
- float targetWeight = 0;
- float deltaWeight = positiveWeight - negativeWeight;
- if (deltaWeight < .8) targetWeight = -.1f;
- else if (deltaWeight < 1.7) targetWeight = .01f;
- else if (deltaWeight < 2.7) targetWeight = .2f;
- else targetWeight = .3f;
- if (currentWeight == 0) currentWeight = 0.5f;
- float newWeight = currentWeight + targetWeight;
- if (newWeight > 0.99f) newWeight = 0.99f;
-
- if (positiveCount > totalCount / 2)
- if (newWeight != currentWeight || r is null)
- {
- if (newWeight < .5)
- {
- if (r is not null)
- {
- t.RemoveLink(r);
- debugString += $"Removed {r.ToString()} \n";
- }
- }
- else
- {
- //bubble the property
- r = t.AddLink(rr.linkType, rr.target);
- r.Weight = newWeight;
- r.Fire();
- debugString += $"Added {r.ToString()} {r.Weight.ToString(".0")} \n";
-
- foreach (Thought t1 in t.Children)
- {
- Thought rrr = t1.RemoveLink(rr.linkType,rr.target);
- debugString += $"Removed {rrr.ToString()} \n";
- }
- //if there is a conflicting link, delete it
- for (int j = 0; j < t.LinksTo.Count; j++)
- {
- if (LinksConflict(new LinkDest(r), new LinkDest(t.LinksTo[j])))
- {
- t.RemoveLink(t.LinksTo[j]);
- j--;
- }
- }
- }
- }
- }
-
+ if (theUKS.BubbleSharedAttributes(t))
+ debugString += $"Bubbled attributes on {t.Label}\n";
}
@@ -280,19 +195,7 @@ bool BubbleNeeded()
//if the given thought is an instance of its parent, get the parent
- public static Thought GetInstanceType(Thought t)
- {
- bool EndsInInteger(string input)
- {
- // Regular expression to check if the string ends with a sequence of digits
- return Regex.IsMatch(input, @"\d+$");
- }
- Thought useLinkType = t;
- while (useLinkType.Parents.Count > 0 && EndsInInteger(useLinkType.Label) &&
- !t.Label.Contains(".") && useLinkType.Label.StartsWith(useLinkType.Parents[0].Label))
- useLinkType = useLinkType.Parents[0];
- return useLinkType;
- }
+ public static Thought GetInstanceType(Thought t) => UKS.UKS.GetBubbleInstanceType(t);
// Fill this method in with code which will execute once
// when the module is added, when "initialize" is selected from the context menu,
diff --git a/BrainSimulator/Modules/Agents/ModuleBalanceTree.cs b/BrainSimulator/Modules/Agents/ModuleBalanceTree.cs
index 899df25..3717c39 100644
--- a/BrainSimulator/Modules/Agents/ModuleBalanceTree.cs
+++ b/BrainSimulator/Modules/Agents/ModuleBalanceTree.cs
@@ -35,13 +35,12 @@ public override void Fire()
UpdateDialog();
}
- public bool isEnabled { get; set; }
+ public new bool isEnabled { get; set; }
private Timer timer;
//private UKS.UKS theUKS1;
public string debugString = "Initialized\n";
private int maxChildren = 6;
- private int minCommonAttributes = 3;
public int MaxChildren { get => maxChildren; set => maxChildren = value; }
private void Setup()
diff --git a/BrainSimulator/Modules/Agents/ModuleBalanceTreeDlg.xml.cs b/BrainSimulator/Modules/Agents/ModuleBalanceTreeDlg.xaml.cs
similarity index 99%
rename from BrainSimulator/Modules/Agents/ModuleBalanceTreeDlg.xml.cs
rename to BrainSimulator/Modules/Agents/ModuleBalanceTreeDlg.xaml.cs
index ac8a5c1..8dbeb30 100644
--- a/BrainSimulator/Modules/Agents/ModuleBalanceTreeDlg.xml.cs
+++ b/BrainSimulator/Modules/Agents/ModuleBalanceTreeDlg.xaml.cs
@@ -56,4 +56,4 @@ private void Enable_Checked(object sender, RoutedEventArgs e)
}
}
}
-}
+}
\ No newline at end of file
diff --git a/BrainSimulator/Modules/Agents/ModuleClassCreate.cs b/BrainSimulator/Modules/Agents/ModuleClassCreate.cs
index 402049c..b901966 100644
--- a/BrainSimulator/Modules/Agents/ModuleClassCreate.cs
+++ b/BrainSimulator/Modules/Agents/ModuleClassCreate.cs
@@ -32,7 +32,7 @@ public override void Fire()
UpdateDialog();
}
- public bool isEnabled { get; set; }
+ public new bool isEnabled { get; set; }
private Timer timer;
//private UKS.UKS theUKS1;
diff --git a/BrainSimulator/Modules/Agents/ModuleRemoveRedundancy.cs b/BrainSimulator/Modules/Agents/ModuleRemoveRedundancy.cs
index 6cc2aa2..cd86ac3 100644
--- a/BrainSimulator/Modules/Agents/ModuleRemoveRedundancy.cs
+++ b/BrainSimulator/Modules/Agents/ModuleRemoveRedundancy.cs
@@ -31,7 +31,7 @@ public override void Fire()
UpdateDialog();
}
- public bool isEnabled { get; set; }
+ public new bool isEnabled { get; set; }
private Timer timer;
//private UKS.UKS theUKS1;
diff --git a/BrainSimulator/Modules/CH4-GATING-MATRIX.md b/BrainSimulator/Modules/CH4-GATING-MATRIX.md
new file mode 100644
index 0000000..c78f54e
--- /dev/null
+++ b/BrainSimulator/Modules/CH4-GATING-MATRIX.md
@@ -0,0 +1,26 @@
+# Ch.4 relationship gating matrix
+
+Simon Ch.4: traversal requires **source thought AND relationship type** active together (biological AND-gate). Ch.2: sparse activation — only active subgraph participates per engine tick.
+
+## Runtime
+
+- `UKS.BeginTraversalCycle()` — called at start of `MainWindow.Dt_Tick`; clears `CurrentTraversal`.
+- Modules call `CurrentTraversal.Activate(thought)` and `ActivateRelationship(linkType)` before gated queries.
+- Activation marks thoughts in `TraversalContext` only — it does **not** call `Thought.Fire()` (that would re-enter `ModuleAlgorithm`'s interpreter queue).
+
+
+## Module status (Phase B)
+
+| Module | Gating | Notes |
+|--------|--------|-------|
+| `ModuleAlgorithm` | **Reference** | `EvaluateContext` activates context root + `has` |
+| `ModuleUKSQuery` | Ungated | Future: relationship filter → `ActivateRelationship` |
+| Other modules | No change | Default `Fire()` only |
+
+> **Note (2026-07):** Legacy `ModuleVision` (old CV pipeline for corners/segments + discrete color) removed. Discrete sensory ingress now handled via direct Thought attribute injection (see DiscreteAttributeCh4 work and future modules). See [[Work-Log/2026-07-10-research-loop-brainsim-legacy-vision-removal]].
+
+## API
+
+- `GetGatedLinks(source, linkType, ctx?)` — inherited `has` links when both active; direct `is-a` when both active.
+- `Traverse(source, linkType, ctx?)` — gated target thoughts.
+
diff --git a/BrainSimulator/Modules/GPT.cs b/BrainSimulator/Modules/GPT.cs
index 0daf626..68a5bca 100644
--- a/BrainSimulator/Modules/GPT.cs
+++ b/BrainSimulator/Modules/GPT.cs
@@ -118,9 +118,9 @@ public static async Task GetGPTResult(string userText, string systemText
else
if (completionResult.error is not null) answerString = "ERROR: " + completionResult.error.message;
}
- catch (Exception ex)
+ catch (Exception)
{
- throw ex;
+ throw;
}
// Deserialize the response body to a CompletionResult object
diff --git a/BrainSimulator/Modules/ModuleAlgorithm.cs b/BrainSimulator/Modules/ModuleAlgorithm.cs
index d937cf9..2c4f28e 100644
--- a/BrainSimulator/Modules/ModuleAlgorithm.cs
+++ b/BrainSimulator/Modules/ModuleAlgorithm.cs
@@ -221,6 +221,7 @@ public bool ExecuteTask(string taskName, string param1Value = "", string param2V
LastLinkWritten = null;
CycleCount = 0;
LastAction = "";
+ Thought.ClearRecentlyFiredQueue();
if (theUKS == null) return false;
@@ -276,7 +277,11 @@ public bool ExecuteTask(string taskName, string param1Value = "", string param2V
Thought mainTaskThought = theUKS.Labeled(mainTaskName);
if (mainTaskThought is not null)
{
- mainTaskThought.Fire();
+ SeqElement mainSteps = mainTaskThought.GetTargetOfFirstLinkOfType("steps") as SeqElement;
+ if (mainSteps is not null)
+ mainSteps.Fire();
+ else
+ mainTaskThought.Fire();
}
}
@@ -311,11 +316,17 @@ private void CreateSpellingSequence(string word, Thought wordThought)
private bool ExecuteAllSteps()
{
- //Run the whole program
- while (HandleFiringNeurons()) { }
- ;
+ const int maxCycles = 50_000;
+ while (HandleFiringNeurons())
+ {
+ if (CycleCount > maxCycles)
+ {
+ LastAction = $"TASK ABORTED (>{maxCycles} cycles): possible interpreter loop";
+ return false;
+ }
+ }
- LastAction = $"TASK COMPLETE ({CycleCount} cycles): {LastLinkWritten.ToString()}";
+ LastAction = $"TASK COMPLETE ({CycleCount} cycles): {LastLinkWritten?.ToString()}";
LastExecutedStep = null;
return true;
}
@@ -337,48 +348,14 @@ private SeqElement GetReturn(Thought current)
private Thought EvaluateContext(Thought contextRoot)
{
- Thought bestResponse = null;
- float bestWeight = 0;
- foreach (Thought t in contextRoot.Children)
- {
- float weight = 0;
- foreach (Link l in t.LinksTo.Where(x => x.LinkType.Label == "has"))
- {
- Link test = (Link)l.To;
- if (test.LinkType.HasAncestor("exist"))
- {
- bool not = false;
- if (test.LinkType.HasAncestor("not")) not = true;
- //Thought testType = test.LinkType.GetTargetOfFirstLinkOfType("is");
- Thought testType = test.LinkType.LinksTo.FindFirst(x => x.LinkType.Label.ToLower() == "is" && x.To.Label != "EXIST")?.To;
- var src = HandleIndirection(test.From);
- if (src is null) continue;
- if (test.LinkType.HasAncestor("same") || test.LinkType.Label.ToLower().Contains("same")) //hack if ancestor not set properly
- {
- Thought target = HandleIndirection(test.To);
- if (!not && src == target) weight += l.Weight;
- if (not && src != target) weight += l.Weight;
- }
- else if (test.To.Label == "??")
- {
- if (!not && src.HasLink(testType) is not null) weight += l.Weight * test.Weight;
- if (not && src.HasLink(testType) is null) weight += l.Weight * test.Weight;
- }
- else
- {
- Thought target = HandleIndirection(test.To);
- if (!not && src.HasLink(testType, target) is not null) weight += l.Weight * test.Weight;
- if (not && src.HasLink(testType, target) is null) weight += l.Weight * test.Weight;
- }
- }
- }
- Debug.WriteLine($"Case: {t.Label} Weight: {weight}");
- if (weight > bestWeight)
- {
- bestResponse = t.GetTargetOfFirstLinkOfType("response");
- bestWeight = weight;
- }
- }
+ // Ch.4 AND-gate: activate context root + has relationship before attribute matching.
+ theUKS.CurrentTraversal.Activate(contextRoot);
+ Thought? hasType = theUKS.Labeled("has");
+ if (hasType is not null)
+ theUKS.CurrentTraversal.ActivateRelationship(hasType);
+
+ ContextCaseResult? selected = theUKS.SelectBestContextCase(contextRoot, HandleIndirection);
+ Thought bestResponse = selected?.Response;
Debug.WriteLine($"Context: {contextRoot} returned {bestResponse}");
bestResponse?.Fire();
diff --git a/BrainSimulator/Modules/ModuleAttention.cs b/BrainSimulator/Modules/ModuleAttention.cs
index d111f34..225339a 100644
--- a/BrainSimulator/Modules/ModuleAttention.cs
+++ b/BrainSimulator/Modules/ModuleAttention.cs
@@ -33,7 +33,6 @@ public class ModuleAttention : ModuleBase
private Thought _queueRoot;
private Thought _predRoot;
private Thought _ltQueued;
- private Thought _ltQueueFor;
private Thought _ltPredItem;
private Thought _ltPredFrom;
private Thought _ltPredTo;
@@ -226,9 +225,6 @@ private void PruneAttentionQueue(DateTime now, ModuleMentalModel mm)
private void TouchQueueItem(Thought focus)
{
return;
- var item = FindQueueItemFor(focus);
- if (item is null) return;
- item.LastFiredTime = DateTime.Now;
}
private IEnumerable<(Thought item, Thought target)> EnumerateQueueItems()
@@ -263,68 +259,26 @@ private void RemoveQueueItem(Thought item)
private float ComputeSalience(Thought t, ModuleMentalModel mm, double surprise, int seenCount, DateTime lastSeen)
{
return .5f;
- if (t is null || mm is null) return 0;
- double novelty = 1.0 / (1.0 + t.UseCount);
- double activation = ComputeActivation(t);
- double proximity = mm.ComputeProximity(t);
- double habituation = seenCount <= 0 ? 0 : seenCount / (seenCount + HabituationDenominator);
- double recencyPenalty = (DateTime.Now - lastSeen).TotalSeconds / AttentionStaleSeconds;
- recencyPenalty = Math.Max(0, recencyPenalty);
-
- double sal = wNovelty * novelty
- + wActivation * activation
- + wSurprise * surprise
- + wProximity * proximity
- - wHabituation * habituation
- - 0.05 * recencyPenalty;
- return (float)Math.Max(0, sal);
}
private static float ComputeActivation(Thought t)
{
return 0.5f;
- double seconds = Math.Max(0, (DateTime.Now - t.LastFiredTime).TotalSeconds);
- return (float)Math.Exp(-seconds / ActivationHalfLifeSeconds);
}
private void BoostActivation(Thought t)
{
return;
- float boosted = t.Weight + ActivationBoost;
- t.Weight = (float)Math.Min(boosted, ActivationBoostMax);
}
private void ActivateRelated(Thought focus)
{
return;
- foreach (var link in focus.LinksTo.Where(x =>
- x.LinkType?.Label is "is-a" or "hasAttribute" or "is" or "part-of" or "means"))
- link.To?.Fire();
-
- foreach (var link in focus.LinksFrom.Where(x =>
- x.LinkType?.Label is "is-a" or "part-of" or "means"))
- link.From?.Fire();
}
private void BuildPredictions(Thought focus)
{
return;
- EnsureLinkTypes();
- ClearPredictions();
-
- foreach (var link in focus.LinksFrom.Where(x => x.LinkType?.Label == "VLU"))
- {
- if (link.From is not SeqElement elem) continue;
- SeqElement nxt = elem.NXT;
- Thought nextValue = nxt?.VLU;
- if (nextValue is null) continue;
-
- Thought predItem = theUKS.GetOrAddThought("_attn:pred:*", "attention");
- _predRoot.AddLink(_ltPredItem, predItem);
- predItem.AddLink(_ltPredFrom, elem);
- predItem.AddLink(_ltPredTo, nextValue);
- predItem.TimeToLive = TimeSpan.FromSeconds(5);
- }
}
private IEnumerable<(SeqElement source, Thought next)> FindPredictionsFor(Thought focus)
@@ -354,23 +308,11 @@ private IEnumerable EnumeratePredictionTargets()
private void ClearPredictions()
{
return;
- EnsureLinkTypes();
- foreach (var link in _predRoot.LinksTo.Where(x => x.LinkType == _ltPredItem).ToList())
- {
- Thought pred = link.To;
- _predRoot.RemoveLink(link);
- pred?.Delete();
- }
}
private static void ReinforcePrediction((SeqElement source, Thought next) pred)
{
return;
- pred.source.Fire();
- var nxtLink = pred.source.LinksToWriteable.FindFirst(x => x.LinkType?.Label == "NXT");
- nxtLink?.Fire();
- pred.source.NXT?.Fire();
- pred.next.Fire();
}
private ModuleMentalModel GetMentalModel()
diff --git a/BrainSimulator/Modules/ModuleBaseDlg.cs b/BrainSimulator/Modules/ModuleBaseDlg.cs
index 52c679e..9aabf24 100644
--- a/BrainSimulator/Modules/ModuleBaseDlg.cs
+++ b/BrainSimulator/Modules/ModuleBaseDlg.cs
@@ -28,8 +28,6 @@ namespace BrainSimulator.Modules;
public class ModuleBaseDlg : Window
{
public ModuleBase ParentModule;
- private DateTime dt;
- private DispatcherTimer timer;
public int UpdateMS = 100;
public Label statusLabel;
private bool initializedLayout = false;
@@ -45,7 +43,7 @@ private void ModuleBaseDlg_Loaded(object sender, RoutedEventArgs e)
initializedLayout = true;
// capture original content
- UIElement? originalContent = this.Content as UIElement;
+ UIElement originalContent = this.Content as UIElement;
// create outer grid (single row) and overlay bottom bar at the bottom
Grid shell = new()
@@ -128,7 +126,7 @@ private void ModuleBaseDlg_Loaded(object sender, RoutedEventArgs e)
/// Searches for a file with the given name (no path) in the specified root directory
/// and all its subdirectories.
///
- public static string? FindFile(string rootPath, string fileName)
+ public static string FindFile(string rootPath, string fileName)
{
if (!Directory.Exists(rootPath))
throw new DirectoryNotFoundException($"Root path not found: {rootPath}");
@@ -188,24 +186,6 @@ virtual public bool Draw(bool checkDrawTimer)
return true;
}
- public void Timer_Tick(object sender, EventArgs e)
- {
- timer.Stop();
- if (Application.Current is null) return;
- if (this is not null)
- Draw(false);
-
- }
-
- //this picks up a final draw after 1/4 second
- public void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
- {
- timer.Stop();
- if (Application.Current is null) return;
- if (this is not null)
- Draw(false);
- }
-
///
/// Sets a status message at the bottom of the dialog. Seets the background yellow if the color is red or null
///
diff --git a/BrainSimulator/Modules/ModuleGPTInfo.cs b/BrainSimulator/Modules/ModuleGPTInfo.cs
index c7bc2fa..7d0caa7 100644
--- a/BrainSimulator/Modules/ModuleGPTInfo.cs
+++ b/BrainSimulator/Modules/ModuleGPTInfo.cs
@@ -123,6 +123,8 @@ public static async Task GetChatGPTVerifyParentChild(string child,string parent)
//this turns dotted names back into more english-language strings
private static string GetStringFromThoughtLabel(string thoughtLabel)
{
+ if (string.IsNullOrEmpty(thoughtLabel))
+ return "";
string theString = thoughtLabel.ToLower();
if (theString[0] == '.') theString = theString.Substring(1);
string[] s = theString.Split('.');
@@ -641,7 +643,7 @@ public static void ParseGPTOutput(string textIn, string GPTOutput)
}
}
- public static async Task SolveDuplicates()
+ public static Task SolveDuplicates()
{
// Get the UKS.
UKS.UKS theUKS = MainWindow.theUKS;
@@ -683,6 +685,7 @@ public static async Task SolveDuplicates()
ModuleGPTInfoDlg.linkCount++;
}
+ return Task.CompletedTask;
}
}
diff --git a/BrainSimulator/Modules/ModuleGPTInfoDlg.xaml.cs b/BrainSimulator/Modules/ModuleGPTInfoDlg.xaml.cs
index 3969a40..36da545 100644
--- a/BrainSimulator/Modules/ModuleGPTInfoDlg.xaml.cs
+++ b/BrainSimulator/Modules/ModuleGPTInfoDlg.xaml.cs
@@ -215,7 +215,7 @@ public async Task ProcessWordsAsync(List words)
if (word == words.Last())
await ModuleGPTInfo.GetChatGPTData(word.Trim());
if (word.Trim() != "")
- ModuleGPTInfo.GetChatGPTData(word.Trim());
+ _ = ModuleGPTInfo.GetChatGPTData(word.Trim());
}
txtOutput.Text = $"Done running! Total word count: {words.Count}. Total link count: {linkCount}. Total error count (not accepted): {errorCount}.";
@@ -235,7 +235,7 @@ public async Task ProcessAmbiguityAsync(List words)
if (word == words.Last())
await ModuleGPTInfo.DisambiguateTermsFile(word.Trim());
if (word.Trim() != "")
- ModuleGPTInfo.DisambiguateTermsFile(word.Trim());
+ _ = ModuleGPTInfo.DisambiguateTermsFile(word.Trim());
}
txtOutput.Text = $"Done running! Total word count: {words.Count}. Total link count: {linkCount}. Total error count (not accepted): {errorCount}.";
@@ -254,7 +254,7 @@ public async Task ProcessParentsAsync(List words)
if (word == words.Last())
await ModuleGPTInfo.GetChatGPTParents(word.Trim());
if (word.Trim() != "")
- ModuleGPTInfo.GetChatGPTParents(word.Trim());
+ _ = ModuleGPTInfo.GetChatGPTParents(word.Trim());
}
SetOutputText($"Done processing unknowns! Total word count: {words.Count}. Total link count: {linkCount}. Total error count (not accepted): {errorCount}.");
@@ -275,25 +275,26 @@ public async Task verifyAllAsync()
if (t == mf.theUKS.AtomicThoughts.Last())
await VerifyAsync(t.Label);
else
- VerifyAsync(t.Label);
+ _ = VerifyAsync(t.Label);
}
SetOutputText($"Done verifying is-a links for reasonableness. Checked {count} links.");
}
- public async Task VerifyAsync(string label)
+ public Task VerifyAsync(string label)
{
ModuleGPTInfo mf = (ModuleGPTInfo)base.ParentModule;
if (!label.StartsWith(".")) label = "." + label;
UKS.Thought t = mf.theUKS.Labeled(label);
- if (t is null) return;
+ if (t is null) return Task.CompletedTask;
foreach (Link r in t.LinksTo)
{
//if (r.GPTVerified) continue;
if (r.LinkType.Label != "has-child") continue;
count++;
- ModuleGPTInfo.GetChatGPTVerifyParentChild(r.To.Label, t.Label);
+ _ = ModuleGPTInfo.GetChatGPTVerifyParentChild(r.To.Label, t.Label);
}
+ return Task.CompletedTask;
}
private void ClearButton_Click(object sender, RoutedEventArgs e)
@@ -358,7 +359,7 @@ private void Button_Click(object sender, RoutedEventArgs e)
}
else if (b.Content.ToString().StartsWith("Verify All"))
{
- verifyAllAsync();
+ _ = verifyAllAsync();
}
else if (b.Content.ToString().StartsWith("Add Clauses To All"))
{
@@ -384,7 +385,7 @@ private void Button_Click(object sender, RoutedEventArgs e)
words.Add(thought.Label);
}
- ProcessParentsAsync(words);
+ _ = ProcessParentsAsync(words);
}
}
}
diff --git a/BrainSimulator/Modules/ModuleLearnMelody.cs b/BrainSimulator/Modules/ModuleLearnMelody.cs
index d95b028..e9db041 100644
--- a/BrainSimulator/Modules/ModuleLearnMelody.cs
+++ b/BrainSimulator/Modules/ModuleLearnMelody.cs
@@ -119,7 +119,7 @@ private void ProcessMelodyEntry(MelodyEntry entry)
SeqElement seq2 = CreatePhraseFromNotes("temp*", entry.ResponsePhrase.Notes);
foreach (Thought t in theUKS.Labeled("possibleAction").Children)
{
- SeqElement seq3 = GetTargetOfFirstLinkOfType(t, "soundAs");
+ SeqElement seq3 = t.GetTargetOfFirstLinkOfType("soundAs") as SeqElement;
var val = theUKS.CompareSequences(seq2, seq3);
if (val == 1)
{
@@ -151,20 +151,6 @@ public bool ResponseHandled (Thought theResponse)
return true;
}
- //TODO Move to UKS
- public SeqElement GetTargetOfFirstLinkOfType(Thought thePhrase, string v)
- {
- foreach (var link in thePhrase.LinksTo)
- {
- if (link.LinkType.Label == v && link.To is SeqElement seq)
- {
- return seq;
- }
- }
- return null;
- }
-
-
// Randomize the melody lines
private void RandomizeMelodies()
{
diff --git a/BrainSimulator/Modules/ModuleLearnMelodyDlg.xaml.cs b/BrainSimulator/Modules/ModuleLearnMelodyDlg.xaml.cs
index a7961b5..9db1c2d 100644
--- a/BrainSimulator/Modules/ModuleLearnMelodyDlg.xaml.cs
+++ b/BrainSimulator/Modules/ModuleLearnMelodyDlg.xaml.cs
@@ -86,7 +86,7 @@ private void TheGrid_SizeChanged(object sender, SizeChangedEventArgs e)
Draw(false);
}
- private void SetStatus(string message, Color? color = null)
+ private new void SetStatus(string message, Color? color = null)
{
if (statusLabel != null)
{
diff --git a/BrainSimulator/Modules/ModuleOnlineInfo.cs b/BrainSimulator/Modules/ModuleOnlineInfo.cs
index 8895fb1..8fdf15f 100644
--- a/BrainSimulator/Modules/ModuleOnlineInfo.cs
+++ b/BrainSimulator/Modules/ModuleOnlineInfo.cs
@@ -24,6 +24,7 @@
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
+using System.Threading.Tasks;
using System.Xml;
using System.Linq;
using Pluralize.NET;
@@ -70,10 +71,11 @@ public override void Fire()
if (wordsToLookUp.Count > 0)
{
- string word = wordsToLookUp[0].Item1;
- int mostUsed = wordsToLookUp.FindIndex(t => t.Item2 == wordsToLookUp.Max(t => t.Item2));
- var x = wordsToLookUp[0];
- wordsToLookUp.RemoveAt(0);
+ int maxUse = wordsToLookUp.Max(t => t.Item2);
+ int mostUsed = wordsToLookUp.FindIndex(t => t.Item2 == maxUse);
+ if (mostUsed < 0) mostUsed = 0;
+ var x = wordsToLookUp[mostUsed];
+ wordsToLookUp.RemoveAt(mostUsed);
ConceptNetLocal(x.Item1);
}
@@ -147,7 +149,7 @@ public void GetCSKGData(string word)
}
}
}
- catch (Exception ex)
+ catch (Exception)
{ }
Debug.WriteLine($"{count.ToString("N0")} entries with {countEN.ToString("N0")} in english");
}
@@ -185,6 +187,10 @@ public override bool Equals(object obj)
return this == r;
return false;
}
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(rel, sourceURI, targetURI, fWeight);
+ }
}
public void ConceptNetLocal(string word)
@@ -321,7 +327,7 @@ public void ConceptNetLocal(string word)
}
}
}
- catch (Exception ex)
+ catch (Exception)
{ }
}
@@ -425,7 +431,7 @@ private void ReadConceptNetFile()
}
}
}
- catch (Exception ex)
+ catch (Exception)
{ }
foreach (var w in wordList2)
{
@@ -473,29 +479,9 @@ private static void GetValueFromURI(ref string URI, ref string partOfSpeech)
URI = URI.Substring(URI.LastIndexOf("/") + 1);
}
- public async void GetConceptNetData(string text)
+ public void GetConceptNetData(string text)
{
ConceptNetLocal(text);
- return;
- var url = @"https://api.conceptnet.io/c/en/" + text;
- var myClient = new HttpClient();
- var responseURL = await myClient.GetAsync(url);
- var propertyURL = await responseURL.Content.ReadAsStringAsync();
- Root myDeserializedClass = JsonConvert.DeserializeObject(propertyURL);
- Output = "";
- foreach (var edge in myDeserializedClass.edges)
- {
- if (edge.start.language != "en") continue;
- if (edge.end.language != "en") continue;
- string start = GetTailOfURL(edge.start.term);
- string end = GetTailOfURL(edge.end.term);
- string rel = edge.rel.label;
- if (rel == "IsA")
- Output += start + "->is-a-> " + end + " " + edge.surfaceText + " " + edge.weight.ToString("f3") + "\n";
- else
- Output += start + "->" + rel + "-> " + end + " " + edge.surfaceText + " " + edge.weight.ToString("f3") + "\n";
- }
- return;
}
string GetTailOfURL(string url)
{
@@ -506,22 +492,45 @@ string GetTailOfURL(string url)
List<(string, string)> wordList2 = new();
List wordList = new List();
+ private static string ResolveWordListPath()
+ {
+ var candidates = new List();
+ string envPath = Environment.GetEnvironmentVariable("WORDLIST_PATH");
+ if (!string.IsNullOrWhiteSpace(envPath))
+ candidates.Add(envPath);
+ candidates.Add(Path.Combine(AppContext.BaseDirectory, "Resources", "wordlist.txt"));
+ candidates.Add(Path.Combine(Directory.GetCurrentDirectory(), "Resources", "wordlist.txt"));
+ candidates.Add(Path.Combine(Directory.GetCurrentDirectory(), "wordlist.txt"));
+
+ foreach (string path in candidates)
+ {
+ if (!string.IsNullOrWhiteSpace(path) && File.Exists(path))
+ return path;
+ }
+ return null;
+ }
+
private void SetupWordList()
{
wordList = new();
+ string wordListPath = ResolveWordListPath();
+ if (wordListPath is null)
+ {
+ Debug.WriteLine("SetupWordList: no wordlist found. Set WORDLIST_PATH or deploy Resources/wordlist.txt");
+ return;
+ }
try
{
-
- // also a possible resource Vocabulary.com
-
- // common words.pdf
- ///https://cehs.unl.edu/documents/secd/aac/vocablists/VLN1.pdf
- string wordListPath = @"C:\Users\c_sim\source\wordlist.txt";
using (StreamReader reader = new StreamReader(wordListPath))
{
string line = reader.ReadLine();
while (line is not null)
{
+ if (line.TrimStart().StartsWith("#"))
+ {
+ line = reader.ReadLine();
+ continue;
+ }
line = line.Replace("\t", " ");
string[] words = line.Split(" ");
foreach (string s in words)
@@ -601,7 +610,7 @@ public void SetupWordList2(string wordIn = "")
word = word.Replace(",", "");
word = word.Replace("\t", " ");
word = word.Replace("-", "");
- word = word.Replace("’", "");
+ word = word.Replace("�", "");
word = word.ToLower();
word = RemoveParentheticals(word);
//word = Thought.TrimDigits(word.Trim());
@@ -619,7 +628,7 @@ public void SetupWordList2(string wordIn = "")
}
}
}
- catch (Exception ex)
+ catch (Exception)
{ }
wordList2 = wordList2.OrderBy(x => x.Item1).ToList();
GetUKS();
@@ -649,20 +658,24 @@ public void SetupWordList2(string wordIn = "")
}
}
- //TODO: properly handle phrases, related, similar...currently only picks up first instance
-
public class KidsWord
{
public string word;
public List definitions = new();
- public string related;
- public string similar;
- public string phrase;
+ public List related = new();
+ public List similar = new();
+ public List phrases = new();
public override string ToString()
{
string retVal = word + "\n";
foreach (var def in definitions)
retVal += def.definition + "\n";
+ if (phrases.Count > 0)
+ retVal += "phrases: " + string.Join(", ", phrases) + "\n";
+ if (similar.Count > 0)
+ retVal += "similar: " + string.Join(", ", similar) + "\n";
+ if (related.Count > 0)
+ retVal += "related: " + string.Join(", ", related) + "\n";
return retVal;
}
}
@@ -695,7 +708,7 @@ public void GetKidsDefinition(string text)
return;
}
- public async void GetKidsWordsmythNet(string text)
+ public async Task GetKidsWordsmythNet(string text)
{
var client = new HttpClient();
var response = await client.GetAsync($"https://kids.wordsmyth.net/we/?ent={text}");
@@ -756,13 +769,10 @@ int GetDefinition(int index)
Thought incomingInfo = theUKS.GetOrAddThought("CurrentIncomingInfo", "Attention");
incomingInfo.V = kidsdef;
+ CollectStringValues(htmlContent, "phrase:", kidsdef.phrases, posSearch, "<");
+ CollectStringValues(htmlContent, "similar words:", kidsdef.similar, posSearch, "<");
+ CollectStringValues(htmlContent, "related words:", kidsdef.related, posSearch, "<");
int index = 0;
- kidsdef.phrase = GetStringValue(htmlContent, "phrase:", ref index, posSearch, "<");
- index = 0;
- kidsdef.similar = GetStringValue(htmlContent, "similar words:", ref index, posSearch, "<");
- index = 0;
- kidsdef.related = GetStringValue(htmlContent, "related words:", ref index, posSearch, "<");
- index = 0;
string retString = "";
index = 0;
do
@@ -789,6 +799,31 @@ string GetStringValue(string content, string target, ref int startIndex, string
return retVal;
}
+ private static void CollectStringValues(string content, string target, List dest, string startTag, string endTag)
+ {
+ int index = 0;
+ string val;
+ while ((val = GetStringValueStatic(content, target, ref index, startTag, endTag)) != "")
+ {
+ if (!dest.Contains(val))
+ dest.Add(val);
+ }
+ }
+
+ private static string GetStringValueStatic(string content, string target, ref int startIndex, string startTag, string endTag)
+ {
+ int i1 = content.IndexOf(target, startIndex);
+ if (i1 == -1) return "";
+ i1 = content.IndexOf(startTag, i1);
+ if (i1 == -1) return "";
+ i1 += startTag.Length;
+ int i2 = content.IndexOf(endTag, i1);
+ if (i2 == -1) return "";
+ string retVal = content.Substring(i1, i2 - i1);
+ startIndex = i2;
+ return retVal;
+ }
+
public List FindAllIndices(string str, string substr)
{
var indices = new List();
@@ -803,7 +838,7 @@ public List FindAllIndices(string str, string substr)
return indices;
}
- public async void GetWiktionaryData(string textIn)
+ public async Task GetWiktionaryData(string textIn)
{
var word = textIn; // the word to look up
@@ -838,16 +873,12 @@ public async void GetWiktionaryData(string textIn)
}
public enum QueryType { general, isa, hasa, can, count, list, listCount, types, partsOf };
- public async void GetChatGPTResult(string textIn, QueryType qtIn = QueryType.isa, string altLabel = "")
+ public async Task GetChatGPTResult(string textIn, QueryType qtIn = QueryType.isa, string altLabel = "")
{
try
{
QueryType qType = qtIn;
if (altLabel == "") altLabel = textIn;
- string prompt;
- string apiKey = ConfigurationManager.AppSettings["APIKey"];
- var client = new HttpClient();
- var url = "https://api.openai.com/v1/chat/completions";
string queryText = textIn;
textIn = textIn.ToLower();
@@ -900,7 +931,7 @@ public async void GetChatGPTResult(string textIn, QueryType qtIn = QueryType.isa
else
Output = answerString;
}
- catch (Exception ex)
+ catch (Exception)
{
}
@@ -930,9 +961,10 @@ string PluralizePhrase(string input)
//This method rerieves the wikidata query number value from wikidata query
// for the Thought item and passes that value to GetPropertiesFromURL along
// with the property query named prop
- public async void GetWikidataData(string item, string prop)
+ public async Task GetWikidataData(string item, string prop)
{
var itemLabel = item;
+ bool handedOff = false;
try
{
Network.httpClientBusy = true;
@@ -941,32 +973,45 @@ public async void GetWikidataData(string item, string prop)
"&language=en&format=xml";
var response = await Network.theHttpClient.GetAsync(url);
- if (response is not null)
+ if (response is null || !response.IsSuccessStatusCode)
+ return;
+
+ string xmlContent = await response.Content.ReadAsStringAsync();
+ XmlDocument xmlItemDoc = new XmlDocument();
+ xmlItemDoc.LoadXml(xmlContent);
+ var xmlItemDocValue = xmlItemDoc.GetElementsByTagName("entity");
+ if (xmlItemDocValue.Count == 0)
{
- var content = response.Content.ReadAsStringAsync();
- XmlDocument xmlItemDoc = new XmlDocument();
- xmlItemDoc.LoadXml(content.Result.ToString());
- var xmlItemDocValue = xmlItemDoc.GetElementsByTagName("entity");
- var xmlItemDocValueFirst = xmlItemDocValue[0];
- if (xmlItemDocValueFirst is not null)
- {
- var nameLabel = xmlItemDocValueFirst.Attributes[0];
- string itemID = nameLabel.InnerXml;
- Network.httpClientBusy = false;
- GetPropertiesFromURL(itemID, prop);///
- }
+ Debug.WriteLine($"GetWikidataData: no entity for '{itemLabel}'.");
+ return;
+ }
+ var xmlItemDocValueFirst = xmlItemDocValue[0];
+ if (xmlItemDocValueFirst?.Attributes?.Count > 0)
+ {
+ var nameLabel = xmlItemDocValueFirst.Attributes[0];
+ string itemID = nameLabel.InnerXml;
+ handedOff = true;
+ await GetPropertiesFromURL(itemID, prop);
}
}
- catch { }
-
+ catch (Exception ex)
+ {
+ Debug.WriteLine($"GetWikidataData failed for '{itemLabel}': {ex.Message}");
+ }
+ finally
+ {
+ if (!handedOff)
+ Network.httpClientBusy = false;
+ }
}
//This method gets all the properties or subproperties of a property propName from a Thought
// with name propName and wikidata query number value numberOfName associated with propName
- private async void GetPropertiesFromURL(string itemID, string propName)
+ private async Task GetPropertiesFromURL(string itemID, string propName)
{
+ try
+ {
Output = "";
propName = propName.ToLower();
- Network.httpClientBusy = true;
var urlBegin = @"https://query.wikidata.org/sparql?query=";
var url = @"SELECT ?wdLabel ?ps_Label ?wdpqLabel ?pq_Label " +
@"{VALUES(?company) { (wd:" + itemID + @")} " +
@@ -975,7 +1020,7 @@ private async void GetPropertiesFromURL(string itemID, string propName)
@"?ps.OPTIONAL{?statement ?pq ?pq_. ?wdpq wikibase:qualifier " +
@"?pq .} SERVICE wikibase:label { bd:serviceParam wikibase:language ""en"" }} " +
@"ORDER BY ?wd ?statement ?ps_";
- url = urlBegin + Uri.EscapeUriString(url);
+ url = urlBegin + Uri.EscapeDataString(url);
var myClient = new HttpClient();
myClient.DefaultRequestHeaders.Add("User-Agent", "c# program");
var responseURL = await myClient.GetAsync(url);
@@ -1029,7 +1074,6 @@ private async void GetPropertiesFromURL(string itemID, string propName)
}
docCount++;
}
- string propString = "";
//TextBoxWiki.Text = "";
for (int i = 0; i < docPropertyValues.Count; i++)
{
@@ -1053,7 +1097,6 @@ private async void GetPropertiesFromURL(string itemID, string propName)
int docCount = 0;
foreach (XmlElement xn in docPropertyValues)
{
- bool found = false;
string[] fn = new string[xn.ChildNodes.Count];
//Thought prop = new Thought();
for (int i = 0; i < xn.ChildNodes.Count; i++)
@@ -1077,7 +1120,6 @@ private async void GetPropertiesFromURL(string itemID, string propName)
docCount++;
}
- string propString = "";
//TextBoxWiki.Text = "";
for (int i = 0; i < docPropertyValues.Count; i++)
{
@@ -1094,6 +1136,15 @@ private async void GetPropertiesFromURL(string itemID, string propName)
}
}
}
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine($"GetPropertiesFromURL failed for '{itemID}': {ex.Message}");
+ }
+ finally
+ {
+ Network.httpClientBusy = false;
+ }
}
@@ -1208,7 +1259,7 @@ public class View
}
- public async void GetFreeDictionaryAPIData(string text)
+ public async Task GetFreeDictionaryAPIData(string text)
{
var url = @"https://api.dictionaryapi.dev/api/v2/entries/en/" + text;
var myClient = new HttpClient();
@@ -1272,7 +1323,7 @@ public class Root1
}
- public async void GetWebstersDictionaryAPIData(string text)
+ public async Task GetWebstersDictionaryAPIData(string text)
{
var url = @"https://dictionaryapi.com/api/v3/references/sd2/json/" + text + "?key=a22c5742-ad8e-44b4-b4c4-9f3f9ac7aedb";
var myClient = new HttpClient();
diff --git a/BrainSimulator/Modules/ModuleOnlineInfoDlg.xaml.cs b/BrainSimulator/Modules/ModuleOnlineInfoDlg.xaml.cs
index eaaccbc..b62e9a9 100644
--- a/BrainSimulator/Modules/ModuleOnlineInfoDlg.xaml.cs
+++ b/BrainSimulator/Modules/ModuleOnlineInfoDlg.xaml.cs
@@ -84,16 +84,16 @@ private void txtInput_PreviewKeyDown(object sender, KeyEventArgs e)
{
case "ChatGPT":
if (txt.EndsWith("?"))
- mcn.GetChatGPTResult(txt, ModuleOnlineInfo.QueryType.general);
+ _ = mcn.GetChatGPTResult(txt, ModuleOnlineInfo.QueryType.general);
else if (txt.StartsWith("list some"))
- mcn.GetChatGPTResult(txt.Substring(10), ModuleOnlineInfo.QueryType.list);
+ _ = mcn.GetChatGPTResult(txt.Substring(10), ModuleOnlineInfo.QueryType.list);
else if (txt.StartsWith("count some"))
- mcn.GetChatGPTResult(txt.Substring(11), ModuleOnlineInfo.QueryType.listCount);
+ _ = mcn.GetChatGPTResult(txt.Substring(11), ModuleOnlineInfo.QueryType.listCount);
else if (txt.EndsWith("can"))
- mcn.GetChatGPTResult(txt.Substring(0,txt.Length-3), ModuleOnlineInfo.QueryType.can);
+ _ = mcn.GetChatGPTResult(txt.Substring(0,txt.Length-3), ModuleOnlineInfo.QueryType.can);
else
{
- mcn.GetChatGPTResult(txt, qType);
+ _ = mcn.GetChatGPTResult(txt, qType);
//Thread.Sleep(1000);
//mcn.GetChatGPTData(txt, ModuleOnlineInfo.QueryType.hasa);
//Thread.Sleep(1000);
@@ -108,16 +108,16 @@ private void txtInput_PreviewKeyDown(object sender, KeyEventArgs e)
mcn.GetConceptNetData(txt);
break;
case "WikiData":
- mcn.GetWikidataData(txt, "subclass of");
+ _ = mcn.GetWikidataData(txt, "subclass of");
break;
case "Wiktionary":
- mcn.GetWiktionaryData(txt);
+ _ = mcn.GetWiktionaryData(txt);
break;
case "Free Dictionary":
- mcn.GetFreeDictionaryAPIData(txt);
+ _ = mcn.GetFreeDictionaryAPIData(txt);
break;
case "Webster's Elementary":
- mcn.GetWebstersDictionaryAPIData(txt);
+ _ = mcn.GetWebstersDictionaryAPIData(txt);
break;
case "Kid's Definition":
mcn.GetKidsDefinition(txt);
diff --git a/BrainSimulator/Modules/ModuleSoundIn.cs b/BrainSimulator/Modules/ModuleSoundIn.cs
index 1aa9a39..1e06187 100644
--- a/BrainSimulator/Modules/ModuleSoundIn.cs
+++ b/BrainSimulator/Modules/ModuleSoundIn.cs
@@ -29,9 +29,7 @@ public class ModuleSoundIn : ModuleBase
{
DateTime lastFiredTime = DateTime.Now;
- DateTime? lastNotePressed = null;
DateTime lastCadenceTime = DateTime.Now;
- List tuneToSearch = null;
private readonly Dictionary _pitchs = new();
private const int MinNote = 60; // C4
@@ -42,7 +40,7 @@ public class ModuleSoundIn : ModuleBase
private const int MidiPatch = 0; // Acoustic Grand Piano
- public static MidiOut? midi;
+ public static MidiOut midi;
public static MidiOut Midi => midi ??= InitMidi();
private static MidiOut InitMidi()
diff --git a/BrainSimulator/Modules/ModuleSoundOut.cs b/BrainSimulator/Modules/ModuleSoundOut.cs
index 19ef5d4..85ad45b 100644
--- a/BrainSimulator/Modules/ModuleSoundOut.cs
+++ b/BrainSimulator/Modules/ModuleSoundOut.cs
@@ -29,9 +29,7 @@ public class ModuleSoundOut : ModuleBase
{
DateTime lastFiredTime = DateTime.Now;
- DateTime? lastNotePressed = null;
DateTime lastCadenceTime = DateTime.Now;
- List tuneToSearch = null;
private readonly HashSet _pressedNotes = new();
private readonly Dictionary _pitchs = new();
@@ -43,13 +41,11 @@ public class ModuleSoundOut : ModuleBase
private const int MidiPatch = 0; // Acoustic Grand Piano
- IEnumerator enumerator = null; //we'll needc multiple enumerators soon
-
// Add these properties to the ModuleSound class
public int Cadence { get; set; } = 100;
public int PitchOffset { get; set; } = 0;
- public static MidiOut? midi;
+ public static MidiOut midi;
public static MidiOut Midi => midi ??= InitMidi();
private static MidiOut InitMidi()
@@ -205,7 +201,7 @@ public async void PlayCMajorTriad(int timetonextMs = 300)
Midi.Send(MidiMessage.StopNote(g, 0, channel).RawData);
}
- public async void PlayNote(int midiNote, bool AddToMentalModel = false, int timetonextMs = 250)
+ public void PlayNote(int midiNote, bool AddToMentalModel = false, int timetonextMs = 250)
{
//Debug.WriteLine($"PlayNote: {midiNote} {timetonextMs}");
var listener = MainWindow.theWindow?.activeModules.OfType().FirstOrDefault();
diff --git a/BrainSimulator/Modules/ModuleStressTest.cs b/BrainSimulator/Modules/ModuleStressTest.cs
index e047c74..bf1f3d8 100644
--- a/BrainSimulator/Modules/ModuleStressTest.cs
+++ b/BrainSimulator/Modules/ModuleStressTest.cs
@@ -71,8 +71,6 @@ public static String AddManyTestItems(int count)
items.Add(i.ToString());
}
- int times = 0;
-
// Add each item to the UKS.
//for (int i = 0; i < items.Count; i++)
diff --git a/BrainSimulator/Modules/ModuleStressTestDlg.xaml.cs b/BrainSimulator/Modules/ModuleStressTestDlg.xaml.cs
index 3421797..f72378c 100644
--- a/BrainSimulator/Modules/ModuleStressTestDlg.xaml.cs
+++ b/BrainSimulator/Modules/ModuleStressTestDlg.xaml.cs
@@ -59,7 +59,7 @@ private void SetOutputText(string theText)
txtOutput.Text = theText;
}
- private async void Button_Click(object sender, RoutedEventArgs e)
+ private void Button_Click(object sender, RoutedEventArgs e)
{
if (sender is Button btn)
{
diff --git a/BrainSimulator/Modules/ModuleText.cs b/BrainSimulator/Modules/ModuleText.cs
index e6b1864..e0b8fb4 100644
--- a/BrainSimulator/Modules/ModuleText.cs
+++ b/BrainSimulator/Modules/ModuleText.cs
@@ -189,7 +189,7 @@ public int LoadTextFromFileOld(string filePath)
// Incremental file-load state
private StreamReader _phraseReader;
private string _phraseReaderPath;
- public async Task LoadTextFromFile(string filePath, int phrasesPerCall = 500)
+ public int LoadTextFromFile(string filePath, int phrasesPerCall = 500)
{
if (phrasesPerCall <= 0) phrasesPerCall = 1;
if (!File.Exists(filePath))
@@ -473,78 +473,6 @@ public static int CreateTrigrams()
CreateUniversalPatternClasses();
// FindPatterns();
return retVal;
-
- var theUKS = MainWindow.theUKS;
- //int retVal = 0;
-
- // Ensure type + link types exist
- theUKS.GetOrAddThought("trigram", "LanguageElement");
- theUKS.GetOrAddThought("first", "LinkType");
- theUKS.GetOrAddThought("second", "LinkType");
- theUKS.GetOrAddThought("third", "LinkType");
-
- // Iterate all existing bigram link-thoughts
- foreach (Thought t in ((Thought)"bigram").Children)
- {
- if (t is not Link lnk) continue;
- // Expect: t is a followedBy link from A -> B
- Thought a = lnk.From;
- Thought b = lnk.To;
-
- if (a == null || b == null) continue;
-
- // Find B --followedBy--> C (second bigram)
- foreach (Link l in b.LinksTo.Where(x => x.LinkType.Label == "followedBy"))
- {
- Thought c = l.To;
- if (c == null) continue;
- if (a.Label == "w:the" && b.Label == "w:baby")
- { }
-
- // Optional: ensure A,B,C occurs somewhere in actual ingested sequences
- // This prevents creating trigrams that never appeared.
- var results = theUKS.HasSequence(new List { a, b, c }, null);
- if (results.Count == 0)
- continue;
-
- // Build a deterministic trigram key (prefer IDs if stable)
- string trigramKey = $"tg_{a.Label}_{b.Label}_{c.Label}";
- if (trigramKey == "tg_the_baby_bird")
- { }
-
- Thought tg = theUKS.Labeled(trigramKey);
- if (tg is null)
- {
- tg = theUKS.GetOrAddThought(trigramKey, "trigram");
-
- // Link to components (idempotent if AddStatement de-dupes)
- theUKS.AddStatement(tg, "first", a);
- theUKS.AddStatement(tg, "second", b);
- theUKS.AddStatement(tg, "third", c);
-
- // Set / reinforce trigram weight (use avg or min; min is more conservative)
- float w = MathF.Min(t.Weight, l.Weight); // conservative
- //float w = 0.5f * (t.Weight + l.Weight); // alternative
-
- tg.Weight = MathF.Min(tg.Weight, 0.10f * w); // start small but proportional
- tg.Weight = results.Count;
- retVal++;
- }
- //else
- //{
- // // reinforce existing trigram
- // //tg.Weight = MathF.Min(1f, tg.Weight + 0.05f * (1f - tg.Weight));
- // tg.Weight += 1; // simple increment; could also use a weighted average of component weights
- //}
-
- tg.LastFiredTime = DateTime.Now; // swap for UKS ticks later if you add recency
- }
- }
- Thought trigrams = theUKS.Labeled("trigram");
- var topTrigrams = trigrams.Children.OrderByDescending(x => x.Weight).ToList();
- for (int i = 50; i < topTrigrams.Count; i++)
- topTrigrams[i].Delete();
- return retVal;
}
public static void FindPatterns()
{
diff --git a/BrainSimulator/Modules/ModuleUKSDlg.xaml b/BrainSimulator/Modules/ModuleUKSDlg.xaml
index 1c56e74..79d34bf 100644
--- a/BrainSimulator/Modules/ModuleUKSDlg.xaml
+++ b/BrainSimulator/Modules/ModuleUKSDlg.xaml
@@ -34,7 +34,7 @@
Text="Thought"
primitives:TextBoxBase.TextChanged="textBoxRoot_TextChanged"
PreviewKeyDown="TextBoxRoot_KeyDown"/>
-
+
diff --git a/BrainSimulator/Modules/ModuleUKSDlg.xaml.cs b/BrainSimulator/Modules/ModuleUKSDlg.xaml.cs
index cbd869c..7d3d7ba 100644
--- a/BrainSimulator/Modules/ModuleUKSDlg.xaml.cs
+++ b/BrainSimulator/Modules/ModuleUKSDlg.xaml.cs
@@ -464,7 +464,8 @@ private void RenameBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
t.Label = tb.Text;
//clear any time-to-live on this new image
- t.LinksFrom.FindFirst(x => x.LinkType.Label == "is-a")?.TimeToLive = TimeSpan.MaxValue;
+ if (t.LinksFrom.FindFirst(x => x.LinkType.Label == "is-a") is Link isALink)
+ isALink.TimeToLive = TimeSpan.MaxValue;
cm.IsOpen = false;
}
if (e.Key == Key.Escape)
@@ -600,7 +601,7 @@ private void TheTreeView_SizeChanged(object sender, SizeChangedEventArgs e)
private void UpdateStatusLabel()
{
- statusLabel.Content = ThoughtLabels.GetLabelCount() + " Thoughts " + ThoughtLabels.GetLinksCount() + " Links.";
+ uksStatusLabel.Content = ThoughtLabels.GetLabelCount() + " Thoughts " + ThoughtLabels.GetLinksCount() + " Links.";
Title = "The Universal Knowledgs Store (UKS) -- File: " + Path.GetFileNameWithoutExtension(theUKS.FileName);
}
@@ -698,7 +699,8 @@ private void Dt_Tick(object sender, EventArgs e)
{
if (!mouseInWindow)
Draw(true);
- RefreshButton?.Visibility = Visibility.Hidden;
+ if (RefreshButton is not null)
+ RefreshButton.Visibility = Visibility.Hidden;
}
private void CheckBoxAuto_Unchecked(object sender, RoutedEventArgs e)
@@ -933,7 +935,7 @@ private void UpdateRootHistoryItems()
private static bool IsDarkMode()
{
const string personalize = @"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize";
- object? value = Registry.GetValue(personalize, "AppsUseLightTheme", 1);
+ object value = Registry.GetValue(personalize, "AppsUseLightTheme", 1);
return value is int i && i == 0;
}
diff --git a/BrainSimulator/Modules/ModuleUKSQueryDlg.xaml b/BrainSimulator/Modules/ModuleUKSQueryDlg.xaml
index 50523bb..27b0678 100644
--- a/BrainSimulator/Modules/ModuleUKSQueryDlg.xaml
+++ b/BrainSimulator/Modules/ModuleUKSQueryDlg.xaml
@@ -29,7 +29,8 @@
-
+
+
diff --git a/BrainSimulator/Modules/ModuleUKSQueryDlg.xaml.cs b/BrainSimulator/Modules/ModuleUKSQueryDlg.xaml.cs
index 81f33a9..5e79a36 100644
--- a/BrainSimulator/Modules/ModuleUKSQueryDlg.xaml.cs
+++ b/BrainSimulator/Modules/ModuleUKSQueryDlg.xaml.cs
@@ -56,6 +56,36 @@ public override bool Draw(bool checkDrawTimer)
return true;
}
+ private void BtnWhy_Click(object sender, RoutedEventArgs e)
+ {
+ ModuleUKSQuery UKSQuery = (ModuleUKSQuery)ParentModule;
+ UKS.UKS theUKS = UKSQuery.theUKS;
+ Thought? source = theUKS.Labeled(sourceText.Text);
+ if (source is null)
+ {
+ resultText.Text = "Why? — source not found";
+ return;
+ }
+
+ List links = theUKS.GetAllLinks(new List { source });
+ Link? match = null;
+ if (!string.IsNullOrWhiteSpace(targetText.Text))
+ match = links.FirstOrDefault(l => l.To?.Label == targetText.Text);
+ if (match is null && !string.IsNullOrWhiteSpace(typeText.Text))
+ match = links.FirstOrDefault(l =>
+ string.Equals(l.LinkType?.Label, typeText.Text, StringComparison.OrdinalIgnoreCase));
+ match ??= links.FirstOrDefault(l => l.InheritanceDepth > 0);
+
+ if (match is null)
+ {
+ resultText.Text = "Why? — no explainable inherited link";
+ return;
+ }
+
+ List trace = theUKS.ExplainLink(match, source);
+ resultText.Text = "Why? " + string.Join(" → ", trace.Select(t => t.Label));
+ }
+
private void BtnLinks_Click(object sender, RoutedEventArgs e)
{
if (sender is Button b)
@@ -150,7 +180,8 @@ private void QueryByAttributes()
resultText1.Text += result1.t.Label + " " + result1.conf.ToString("0.00") + "\n";
}
- if (allResults.Count == 1 || allResults[0].conf > allResults[1].conf)
+ if (allResults.Count > 0 &&
+ (allResults.Count == 1 || allResults[0].conf > allResults[1].conf))
{
UpdateMostRecent(allResults[0].t);
}
@@ -217,7 +248,6 @@ private void BtnLearn_Click(object sender, RoutedEventArgs e)
}
SetStatus("OK");
- float confidence = 0;
var allResults = theUKS.SearchForClosestMatch(queryThought, ancestor);
if (allResults.Count == 0)
diff --git a/BrainSimulator/Modules/PointPlus.cs b/BrainSimulator/Modules/PointPlus.cs
index eec72af..d5048a7 100644
--- a/BrainSimulator/Modules/PointPlus.cs
+++ b/BrainSimulator/Modules/PointPlus.cs
@@ -122,7 +122,7 @@ public bool Near(PointPlus PP, float toler)
}
public override string ToString()
{
- // string s = "R: " + R.ToString("F3") + ", Theta: " + Degrees.ToString("F3") + "° (" + X.ToString("F2") + "," + Y.ToString("F2") + ") Conf:" + Conf.ToString("F3");
+ // string s = "R: " + R.ToString("F3") + ", Theta: " + Degrees.ToString("F3") + "� (" + X.ToString("F2") + "," + Y.ToString("F2") + ") Conf:" + Conf.ToString("F3");
string s = $"({X.ToString("0.0")}.{Y.ToString("0.0")})";
return s;
}
@@ -174,6 +174,11 @@ public override bool Equals(object p1)
}
return false;
}
+
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(P.X, P.Y);
+ }
}
// this is an extension of a 3D position point which allows access via both polar and cartesian coordinates
@@ -368,7 +373,7 @@ public bool Near(Point3DPlus PP, float toler)
}
public override string ToString()
{
- string s = "R: " + R.ToString("F3") + ", Theta: " + DegreesTheta.ToString("F3") + "°, Phi: " + DegreesPhi.ToString("F3") + "° (" +
+ string s = "R: " + R.ToString("F3") + ", Theta: " + DegreesTheta.ToString("F3") + "�, Phi: " + DegreesPhi.ToString("F3") + "� (" +
X.ToString("F2") + "," + Y.ToString("F2") + "," + Z.ToString("F2") + ") Conf:" + Conf.ToString("F3");
return s;
}
@@ -442,7 +447,7 @@ public class Motion : PointPlus
public Angle rotation = 0;
public override string ToString()
{
- string s = "R: " + R.ToString("F3") + ", Theta: " + Degrees.ToString("F3") + "° (" + X.ToString("F2") + "," + Y.ToString("F2") + ") Rot:" + rotation;
+ string s = "R: " + R.ToString("F3") + ", Theta: " + Degrees.ToString("F3") + "� (" + X.ToString("F2") + "," + Y.ToString("F2") + ") Rot:" + rotation;
return s;
}
}
@@ -461,7 +466,7 @@ public Segment(Segment s)
}
public override string ToString()
{
- string retVal = $"L: {(int)Length} ({P1.X.ToString("0.0")},{P1.Y.ToString("0.0")}) : ({P2.X.ToString("0.0")},{P2.Y.ToString("0.0")}) A: {Angle.Degrees.ToString("0.0")}°";
+ string retVal = $"L: {(int)Length} ({P1.X.ToString("0.0")},{P1.Y.ToString("0.0")}) : ({P2.X.ToString("0.0")},{P2.Y.ToString("0.0")}) A: {Angle.Degrees.ToString("0.0")}�";
return retVal;
}
@@ -478,6 +483,18 @@ public override string ToString()
return !(s1 == s2);
}
+ public override bool Equals(object obj)
+ {
+ if (obj is Segment s)
+ return this == s;
+ return false;
+ }
+
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(P1?.GetHashCode() ?? 0, P2?.GetHashCode() ?? 0);
+ }
+
public Segment(PointPlus P1i, PointPlus P2i)
{
P1 = P1i;
@@ -561,7 +578,7 @@ public class Angle
public override string ToString()
{
float degrees = theAngle * 180 / (float)PI;
- string s = theAngle.ToString("0.00") + " " + degrees.ToString("0.0") + "°";
+ string s = theAngle.ToString("0.00") + " " + degrees.ToString("0.0") + "�";
return s;
}
public int CompareTo(Angle a)
diff --git a/BrainSimulator/Modules/Vision/ModuleMentalModel.cs b/BrainSimulator/Modules/Vision/ModuleMentalModel.cs
deleted file mode 100644
index 6444a11..0000000
--- a/BrainSimulator/Modules/Vision/ModuleMentalModel.cs
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Brain Simulator Through
- *
- * Copyright (c) 2026 Charles Simon
- *
- * This file is part of Brain Simulator Through and is licensed under
- * the MIT License. You may use, copy, modify, merge, publish, distribute,
- * sublicense, and/or sell copies of this software under the terms of
- * the MIT License.
- *
- * See the LICENSE file in the project root for full license information.
- */
-//
-// PROPRIETARY AND CONFIDENTIAL
-// Brain Simulator 3 v.1.0
-// © 2022 FutureAI, Inc., all rights reserved
-//
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using System.Windows;
-using System.Xml.Serialization;
-using static System.Math;
-
-namespace BrainSimulator.Modules
-{
- public class ModuleMentalModel : ModuleBase
- {
- // Any public variable you create here will automatically be saved and restored
- // with the network unless you precede it with the [XmlIgnore] directive
- // [XmlIgnore]
- // public theStatus = 1;
-
-
- // Fill this method in with code which will execute
- // once for each cycle of the engine
- public override void Fire()
- {
- Init();
- GetUKS();
- UpdateDialog();
- }
-
- // Fill this method in with code which will execute once
- // when the module is added, when "initialize" is selected from the context menu,
- // or when the engine restart button is pressed
- public override void Initialize()
- {
- }
-
- // The following can be used to massage public data to be different in the xml file
- // delete if not needed
- public override void SetUpBeforeSave()
- {
- }
- public override void SetUpAfterLoad()
- {
- }
-
- // called whenever the UKS performs an Initialize()
- public override void UKSInitializedNotification()
- {
-
- }
- }
-}
\ No newline at end of file
diff --git a/BrainSimulator/Modules/Vision/ModuleMentalModelDlg.xaml b/BrainSimulator/Modules/Vision/ModuleMentalModelDlg.xaml
deleted file mode 100644
index a820984..0000000
--- a/BrainSimulator/Modules/Vision/ModuleMentalModelDlg.xaml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
diff --git a/BrainSimulator/Modules/Vision/ModuleMentalModelDlg.xaml.cs b/BrainSimulator/Modules/Vision/ModuleMentalModelDlg.xaml.cs
deleted file mode 100644
index afae1db..0000000
--- a/BrainSimulator/Modules/Vision/ModuleMentalModelDlg.xaml.cs
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * Brain Simulator Through
- *
- * Copyright (c) 2026 Charles Simon
- *
- * This file is part of Brain Simulator Through and is licensed under
- * the MIT License. You may use, copy, modify, merge, publish, distribute,
- * sublicense, and/or sell copies of this software under the terms of
- * the MIT License.
- *
- * See the LICENSE file in the project root for full license information.
- */
-//
-// PROPRIETARY AND CONFIDENTIAL
-// Brain Simulator 3 v.1.0
-// © 2022 FutureAI, Inc., all rights reserved
-//
-
-using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Media;
-using System.Windows.Shapes;
-using UKS;
-
-namespace BrainSimulator.Modules
-{
- public partial class ModuleMentalModelDlg : ModuleBaseDlg
- {
- public ModuleMentalModelDlg()
- {
- InitializeComponent();
- }
-
- public override bool Draw(bool checkDrawTimer)
- {
- if (!base.Draw(checkDrawTimer)) return false;
- //this has a timer so that no matter how often you might call draw, the dialog
- //only updates 10x per second
-
- //use a line like this to gain access to the parent's public variables
- ModuleMentalModel parent = (ModuleMentalModel)base.ParentModule;
-
- Thing mentalModel = parent.theUKS.Labeled("MentalModel");
- if (mentalModel == null) return false;
- //if (environmentModel.lastFiredTime < DateTime.Now - TimeSpan.FromSeconds(1)) return false;
-
- int pixelSize = 6;
- int scale = (int)(theCanvas.ActualHeight / 25);
- theCanvas.Children.Clear();
-
- Thing currentShape = parent.theUKS.Labeled("CurrentShape");
- if (currentShape == null) return false;
- foreach (Thing t in currentShape?.Children)
- {
- Thing shape = t.GetAttribute("storedshape");
- Thing size = t.GetAttribute("size");
- Thing position = t.GetAttribute("position");
- Thing rotation = t.GetAttribute("rotation");
- Thing mmOffset = t.GetAttribute("offset");
- Thing aColor = t.GetAttribute("color");
- if (mmOffset == null || rotation == null || position == null || size == null || shape == null)
- return false;
-
- HSLColor theColor = new HSLColor(Colors.Pink);
- if (aColor != null)
- theColor = new HSLColor((Color)aColor.V);
-
- //hacks to get a few properties which are stashed in labels
- int offsetVal = int.Parse(mmOffset.Label[9..]);
- float sizeVal = float.Parse(size.Label.Replace("size", ""));
- string[] temp1 = position.Label.Split(':');
- string[] temp2 = temp1[1].Split(',');
- float x1 = float.Parse(temp2[0]) * scale / 4 + 10;
- float y1 = float.Parse(temp2[1]) * scale / 4 + 10;
- PointPlus curPos = new(x1, y1);
- Angle currDir = Angle.FromDegrees(float.Parse(rotation.Label[6..]));
-
- Polyline poly = new Polyline()
- {
- Fill = new SolidColorBrush(theColor.ToColor()),
- Stroke = new SolidColorBrush(Colors.Green),
- };
- poly.Points.Add(curPos);
-
- foreach (Relationship r in t.Relationships)
- {
- if (!r.reltype.Label.StartsWith("go")) continue;
- if (r.target.HasAncestor("Rotation"))
- {
- Angle theta = Angle.FromDegrees(float.Parse(r.target.Label[5..]));
- currDir += theta;
- }
- else if (r.target.HasAncestor("Distance"))
- {
- try
- {
- float dist = float.Parse(r.target.Label[8..]) * scale * 2.5f;
- PointPlus offset = new PointPlus(dist * sizeVal, currDir);
- curPos += offset;
- poly.Points.Add(curPos);
- }
- catch { }
- }
- }
- if (poly.Points.Count < 2) //there is no recenetly-refreshed data, use the stored shape
- {
- theColor.saturation /= 2;
- if (offsetVal < 0) offsetVal = 0;
- poly.Fill = new SolidColorBrush(theColor.ToColor());
- if (shape != null)
- {
- for (int i = 0; i < shape.Relationships.Count; i++)
- {
- Relationship r = shape.Relationships[(i + offsetVal) % shape.Relationships.Count];
- if (!r.reltype.Label.StartsWith("go")) continue;
- if (r.target.HasAncestor("Rotation"))
- {
- Angle theta = Angle.FromDegrees(float.Parse(r.target.Label[5..]));
- currDir += theta;
- }
- else if (r.target.HasAncestor("Distance"))
- {
- float dist = float.Parse(r.target.Label[8..]) * scale * 2.5f;
- PointPlus offset = new PointPlus(dist * sizeVal, currDir);
- curPos += offset;
- poly.Points.Add(curPos);
- }
- }
- }
- }
- theCanvas.Children.Add(poly);
-
- for (int x = 0; x < 25; x++)
- for (int y = 0; y < 25; y++)
- {
- string name = $"mm{x},{y}";
- Thing pixel = parent.theUKS.Labeled(name);
- if (pixel == null) continue;
- Color pixelColor = (Color)pixel.V;
-
- if (pixelColor != null)
- {
- //pixelColor.luminance /= 2;
- SolidColorBrush b = new SolidColorBrush(pixelColor);
- Ellipse e = new Ellipse()
- {
- Height = pixelSize,
- Width = pixelSize,
- Stroke = b,
- Fill = b,
- };
- Canvas.SetLeft(e, 10 + x * scale - pixelSize / 2);
- Canvas.SetTop(e, 10 + y * scale - pixelSize / 2);
- theCanvas.Children.Add(e);
- }
- }
-
- }
-
- return true;
- }
-
- private void TheGrid_SizeChanged(object sender, SizeChangedEventArgs e)
- {
- Draw(false);
- }
- }
-}
diff --git a/BrainSimulator/Modules/Vision/ModuleShape.cs b/BrainSimulator/Modules/Vision/ModuleShape.cs
deleted file mode 100644
index f4229d4..0000000
--- a/BrainSimulator/Modules/Vision/ModuleShape.cs
+++ /dev/null
@@ -1,257 +0,0 @@
-/*
- * Brain Simulator Through
- *
- * Copyright (c) 2026 Charles Simon
- *
- * This file is part of Brain Simulator Through and is licensed under
- * the MIT License. You may use, copy, modify, merge, publish, distribute,
- * sublicense, and/or sell copies of this software under the terms of
- * the MIT License.
- *
- * See the LICENSE file in the project root for full license information.
- */
-//
-// PROPRIETARY AND CONFIDENTIAL
-// Brain Simulator 3 v.1.0
-// © 2022 FutureAI, Inc., all rights reserved
-//
-
-using System;
-using System.Collections.Generic;
-using static System.Math;
-using UKS;
-
-namespace BrainSimulator.Modules
-{
- public class ModuleShape : ModuleBase
- {
-
- //timer only processes recently-changed items.
- DateTime lastScan = new DateTime();
- public Thing newStoredShape = null;
- public override void Fire()
- {
- Init();
-
- GetUKS();
- if (theUKS == null) return;
- theUKS.GetOrAddThing("Sense", "Thing");
- theUKS.GetOrAddThing("Visual", "Sense");
- Thing tCorners = theUKS.GetOrAddThing("corner", "Visual");
-
- //check to see if there are any new corners which need checking
- foreach (Thing tCorner in tCorners.Children)
- if (tCorner.lastFiredTime > lastScan)
- goto processingNeeded;
- return;
-
- processingNeeded:
- lastScan = DateTime.Now;
-
- CreateShapeFromCorners();
-
- Thing storedShapes = theUKS.GetOrAddThing("StoredShape", "Visual");
- foreach (Thing shape in theUKS.Labeled("currentShape").Children)
- {
- float bestValue = 0;
- //currehtShape has a set of attriburtes. We search the descendents of StoredShape for the
- // entry with the best match.
- Thing foundShape = theUKS.SearchForClosestMatch(shape, storedShapes, ref bestValue);
- if (foundShape != null)
- {
- Thing nextBest = foundShape;
- float bestSeqsScore = -1;
- float angleOffset = 0; //degrees
- int bestOffset = -1;
- while (nextBest != null)
- {
- float score = theUKS.HasSequence(nextBest, shape, out int offset, true);
- if (score > bestSeqsScore)
- {
- for (int i = 0; i < offset && i < nextBest.Relationships.Count; i++)
- {
- if (nextBest.Relationships[i].target.HasAncestorLabeled("Rotation"))
- {
- angleOffset += float.Parse(nextBest.Relationships[i].target.Label[5..]);
- }
- }
- bestSeqsScore = score;
- bestOffset = offset;
- foundShape = nextBest;
- }
- nextBest = theUKS.GetNextClosestMatch(ref bestValue);
- }
- if (bestSeqsScore > 0.5)
- {
- Relationship r = shape.SetAttribute(foundShape);
- r.Weight = bestSeqsScore;
- Thing offsetThing = theUKS.GetOrAddThing("mmOffset:" + bestOffset, "offset");
- shape.SetAttribute(offsetThing);
- foundShape.SetFired();
- newStoredShape = null;
- }
- else
- {
- newStoredShape = AddShapeToLibrary();
- //set a time-to-live on this new image
- newStoredShape.RelationshipsFrom.FindFirst(x => x.reltype.Label == "has-child").TimeToLive = TimeSpan.FromSeconds(15);
- Relationship r = shape.SetAttribute(newStoredShape);
- r.Weight = 1;
- Thing offsetThing = theUKS.GetOrAddThing("mmOffset:" + 0, "offset");
- shape.SetAttribute(offsetThing);
- newStoredShape.SetFired();
- }
- }
- }
-
- UpdateDialog();
- }
-
- //note there are two kinds of corners
- //1. Transient: seen in space which has a position and an angle (descendent of visual |
- //2. abstract: which has only an angle (perhaps a relative orientation)
-
- void CreateShapeFromCorners()
- {
- //UKS setup
- Thing currentShape = theUKS.GetOrAddThing("currentShape", "Visual");
- theUKS.GetOrAddThing("Go", "RelationshipType");
-
- //clear out any previous currentShape
- theUKS.DeleteAllChildren(currentShape);
-
- foreach (Thing outline in theUKS.Labeled("outline").Children)
- {
- ExtractShape(theUKS.GetOrAddThing("currentShape*",currentShape), outline);
- }
- }
-
- private void ExtractShape(Thing currentShape, Thing outline)
- {
- var cornersTmp = outline.GetRelationshipsWithAncestor(theUKS.Labeled("corner"));
-
- if (cornersTmp.Count < 2)
- return;
-
- //create a convenient list of the corneres for this outline
- List corners = new();
- foreach (var c in cornersTmp)
- {
- var corner = c.target.V as ModuleVision.Corner;
- if (corner == null) continue;
- corners.Add(corner);
- }
- //setup to normalize the distance
- float maxDist = 0;
- Angle prefTheta = (corners[1].pt - corners[0].pt).Theta;
- Thing theColor = outline.GetAttribute("Color");
- if (theColor != null)
- currentShape.SetAttribute(theColor);
-
- //find the length of the longest segment of this shape
- //offset is the index of the that segment
- int offset = -1;
- for (int i = 0; i < corners.Count; i++)
- {
- int next = i + 1;
- if (next >= corners.Count) next = 0;
- var nextCorner = corners[next];
- PointPlus theEdge = (nextCorner.pt - corners[i].pt);
- float dist = theEdge.R;
- if (dist > maxDist)
- {
- maxDist = dist;
- offset = i;
- }
- }
-
- //TODO: put any other attributes on your shape here
-
- //add the scale to the object
- string sizeName = "size" + (int)maxDist / 10;
- currentShape.SetAttribute(theUKS.GetOrAddThing(sizeName, "Size"));
-
- string location = $"mmPos:{(int)corners[0].pt.X},{(int)corners[0].pt.Y}";
- Thing locationThing = theUKS.GetOrAddThing(location, "Position");
- currentShape.SetAttribute(locationThing);
-
- //get the rotation
- string rotation = prefTheta.Degrees.ToString("0.0");
- rotation = "mmRot:" + rotation;
- Thing rotationThing = theUKS.GetOrAddThing(rotation,"Rotation");
- currentShape.SetAttribute(rotationThing);
-
- //add the corners to the currentShape
- for (int i = 0; i < corners.Count; i++)
- {
- var prevCorner = corners[i];
- var currCorner = corners[(i + 1) % corners.Count];
- var nextCorner = corners[(i + 2) % corners.Count];
-
- //how far to move
- float dist = (currCorner.pt - prevCorner.pt).R;
- dist /= maxDist;
- string distName = "distance." + ((int)Round(dist * 10)).ToString();
- if (Round(dist * 10) == 10) distName = "distance1.0";
- Thing theDist = theUKS.GetOrAddThing(distName, "distance");
- Relationship r = theUKS.AddStatement(currentShape, "go*", theDist);
- r.TimeToLive = TimeSpan.FromSeconds(10);
-
- //how much to turn
- Segment s1 = new Segment(prevCorner.pt, currCorner.pt);
- Segment s2 = new Segment(currCorner.pt, nextCorner.pt);
- Angle turn = s2.Angle - s1.Angle;
- while (turn.Degrees > 180) turn -= Angle.FromDegrees(360);
- while (turn.Degrees <= -180) turn += Angle.FromDegrees(360);
- int a = IRound(turn.Degrees, 10);
- Thing theAngle = theUKS.GetOrAddThing("angle" + a, "Angle");
- r = theUKS.AddStatement(currentShape, "go*", theAngle);
- r.TimeToLive = TimeSpan.FromSeconds(10);
- }
- }
-
- int IRound (float number, int multiple = 1)
- {
- float retVal = (float)Round(number / multiple) * multiple;
- return (int)retVal;
- }
-
- public Thing AddShapeToLibrary()
- {
- Thing currentShape = theUKS.Labeled("currentShape0");
- if (currentShape == null) return null;
- if (currentShape.HasRelationshipWithAncestorLabeled("distance") == null) return null;
- theUKS.GetOrAddThing("StoredShape", "Visual");
- Thing newShape = theUKS.GetOrAddThing("storedShape*", "StoredShape");
- //TODO: fix this so we can indicate which item we mearn
- foreach (Relationship r in currentShape.Relationships)
- {
- if (r.relType.Label.StartsWith("has")) continue;
- newShape.AddRelationship(r.target, r.relType);
- }
- return newShape;
- }
-
- // Fill this method in with code which will execute once
- // when the module is added, when "initialize" is selected from the context menu,
- // or when the engine restart button is pressed
- public override void Initialize()
- {
- }
-
- // The following can be used to massage public data to be different in the xml file
- // delete if not needed
- public override void SetUpBeforeSave()
- {
- }
- public override void SetUpAfterLoad()
- {
- }
-
- // called whenever the UKS performs an Initialize()
- public override void UKSInitializedNotification()
- {
-
- }
- }
-}
\ No newline at end of file
diff --git a/BrainSimulator/Modules/Vision/ModuleShapeDlg.xaml b/BrainSimulator/Modules/Vision/ModuleShapeDlg.xaml
deleted file mode 100644
index 77458c0..0000000
--- a/BrainSimulator/Modules/Vision/ModuleShapeDlg.xaml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/BrainSimulator/Modules/Vision/ModuleShapeDlg.xaml.cs b/BrainSimulator/Modules/Vision/ModuleShapeDlg.xaml.cs
deleted file mode 100644
index bc4afb7..0000000
--- a/BrainSimulator/Modules/Vision/ModuleShapeDlg.xaml.cs
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Brain Simulator Through
- *
- * Copyright (c) 2026 Charles Simon
- *
- * This file is part of Brain Simulator Through and is licensed under
- * the MIT License. You may use, copy, modify, merge, publish, distribute,
- * sublicense, and/or sell copies of this software under the terms of
- * the MIT License.
- *
- * See the LICENSE file in the project root for full license information.
- */
-//
-// PROPRIETARY AND CONFIDENTIAL
-// Brain Simulator 3 v.1.0
-// © 2022 FutureAI, Inc., all rights reserved
-//
-
-using System;
-using System.Windows;
-using UKS;
-
-namespace BrainSimulator.Modules
-{
- public partial class ModuleShapeDlg : ModuleBaseDlg
- {
- public ModuleShapeDlg()
- {
- InitializeComponent();
- }
-
- public override bool Draw(bool checkDrawTimer)
- {
- if (!base.Draw(checkDrawTimer)) return false;
-
- ModuleShape parent = (ModuleShape)ParentModule;
- if (parent.newStoredShape == null) tbNewName.IsEnabled = false;
- else tbNewName.IsEnabled = true;
-
- if (parent.theUKS.Labeled("currentShape") == null) return false;
- tbFound.Text = "";
- foreach (Thing currentShape in parent.theUKS.Labeled("currentShape").Children)
- {
- Thing shapeType = currentShape.GetAttribute("storedShape");
- if (shapeType == null) { continue; }
- Relationship confidence = parent.theUKS.GetRelationship(currentShape.Label,"hasAttribute", shapeType.Label);
- tbFound.Text += $"{currentShape.Label} {shapeType.Label} Conf.: {(int)(confidence.Weight * 100)}% {currentShape.GetAttribute("size")}\n";
- tbFound.Text += $" {currentShape.GetAttribute("Color")?.Label} Orientation:{currentShape.GetAttribute("Rotation")?.Label[6..]}\n";
- }
- return true;
- }
-
- private void TheGrid_SizeChanged(object sender, SizeChangedEventArgs e)
- {
- Draw(false);
- }
-
- private void Button_Click(object sender, RoutedEventArgs e)
- {
- ModuleShape parent = (ModuleShape)ParentModule;
- parent.newStoredShape = parent.AddShapeToLibrary();
- tbNewName.IsEnabled = true;
- }
-
- private void tbNewName_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
- {
- if (e.Key == System.Windows.Input.Key.Enter)
- {
- ModuleShape parent = (ModuleShape)ParentModule;
- if (parent.newStoredShape == null) return;
- string newName = tbNewName.Text;
- parent.newStoredShape.Label = newName;
- tbNewName.Text = "New Name";
- parent.newStoredShape.RelationshipsFrom.FindFirst(x => x.reltype.Label == "has-child").TimeToLive = TimeSpan.MaxValue;
- Draw(false);
- }
- }
-
- private void tbNewName_GotFocus(object sender, RoutedEventArgs e)
- {
- ModuleShape parent = (ModuleShape)ParentModule;
- if (parent.newStoredShape == null) tbNewName.IsEnabled = false;
- else
- {
- tbNewName.IsEnabled = true;
- if (tbNewName.Text == "New Name")
- tbNewName.Text = "";
- }
- }
- }
-}
diff --git a/BrainSimulator/Modules/Vision/ModuleVision.Boundary.cs b/BrainSimulator/Modules/Vision/ModuleVision.Boundary.cs
deleted file mode 100644
index aea0a94..0000000
--- a/BrainSimulator/Modules/Vision/ModuleVision.Boundary.cs
+++ /dev/null
@@ -1,537 +0,0 @@
-/*
- * Brain Simulator Through
- *
- * Copyright (c) 2026 Charles Simon
- *
- * This file is part of Brain Simulator Through and is licensed under
- * the MIT License. You may use, copy, modify, merge, publish, distribute,
- * sublicense, and/or sell copies of this software under the terms of
- * the MIT License.
- *
- * See the LICENSE file in the project root for full license information.
- */
-
-using System.Collections.Generic;
-using System.Windows.Media;
-using System.Windows;
-using static System.Math;
-using System.Linq;
-using System;
-
-
-namespace BrainSimulator.Modules;
-public partial class ModuleVision
-{
- Color backgroundColor = Colors.Black;
- private void FindBackgroundColor()
- {
- Dictionary colorCount = new Dictionary();
-
- int width = imageArray.GetLength(0);
- int height = imageArray.GetLength(1);
-
- for (int x = 0; x < width; x++)
- {
- for (int y = 0; y < height; y++)
- {
- Color color = imageArray[x, y];
- if (colorCount.ContainsKey(color))
- {
- colorCount[color]++;
- }
- else
- {
- colorCount[color] = 1;
- }
- }
- }
- backgroundColor = colorCount.OrderByDescending(x => x.Value).First().Key;
- }
-
- public bool horizScan = true;
- public bool vertScan = true;
- public bool fortyFiveScan = true;
- public bool minusFortyFiveScan = true;
- private void FindBoundaries(Color[,] imageArray)
- {
- strokePoints.Clear();
- boundaryPoints.Clear();
-
-
- float dx = 1;
- float dy = 0;
- int sx = 0;
- int sy = 0;
- if (horizScan)
- {
- List ptsInThisScan = new();
- for (sy = 0; sy < imageArray.GetLength(1); sy++)
- {
- var rayThruImage = LineThroughArray(dx, dy, sx, sy, imageArray);
- var pts = FindStrokePtsInRay(sx, sy, dx, dy, rayThruImage);
- ptsInThisScan.AddRange(pts);
- FindBoundaryPtsInRay(sx, sy, dx, dy, rayThruImage);
- }
- RemoveOrphanPoints(ptsInThisScan);
- strokePoints.AddRange(ptsInThisScan);
- }
- if (vertScan)
- {
- dx = 0;
- dy = 1;
- sy = 0;
- List ptsInThisScan = new();
- for (sx = 0; sx < imageArray.GetLength(0); sx++)
- {
- var rayThruImage = LineThroughArray(dx, dy, sx, sy, imageArray);
- var pts = FindStrokePtsInRay(sx, sy, dx, dy, rayThruImage);
- ptsInThisScan.AddRange(pts);
- FindBoundaryPtsInRay(sx, sy, dx, dy, rayThruImage);
- }
- RemoveOrphanPoints(ptsInThisScan);
- strokePoints.AddRange(ptsInThisScan);
- }
- MergeNearbyPoints(strokePoints);
-/* if (fortyFiveScan)
- {
- dx = 1;
- dy = 1;
- List ptsInThisScan = new();
- for (sy = 0; sy < imageArray.GetLength(1); sy++)
- {
- var rayThruImage = LineThroughArray(dx, dy, 0, sy, imageArray);
- var pts = FindStrokePtsInRay(0, sy, dx, dy, rayThruImage);
- ptsInThisScan.AddRange(pts);
- //FindBoundaryPtsInRay(0, sy, dx, dy, rayThruImage);
- }
- for (sx = 0; sx < imageArray.GetLength(0); sx++)
- {
- var rayThruImage = LineThroughArray(dx, dy, sx, 0, imageArray);
- var pts = FindStrokePtsInRay(sx, 0, dx, dy, rayThruImage);
- ptsInThisScan.AddRange(pts);
- //FindBoundaryPtsInRay(sx, 0, dx, dy, rayThruImage);
- }
- RemoveOrphanPoints(ptsInThisScan);
- strokePoints.AddRange(ptsInThisScan);
- }
- if (minusFortyFiveScan)
- {
- dx = -1;
- dy = 1;
- List ptsInThisScan = new();
- for (sx = 0; sx < imageArray.GetLength(0); sx++)
- {
- var rayThruImage = LineThroughArray(dx, dy, sx, 0, imageArray);
- var pts = FindStrokePtsInRay(sx, 0, dx, dy, rayThruImage);
- ptsInThisScan.AddRange(pts);
- //FindBoundaryPtsInRay(sx, 0, dx, dy, rayThruImage);
- }
- for (sy = 0; sy < imageArray.GetLength(1); sy++)
- {
- var rayThruImage = LineThroughArray(dx, dy, imageArray.GetLength(0) - 1, sy, imageArray);
- var pts = FindStrokePtsInRay(imageArray.GetLength(0) - 1, sy, dx, dy, rayThruImage);
- ptsInThisScan.AddRange(pts);
- //FindBoundaryPtsInRay(imageArray.GetLength(0) - 1, sy, dx, dy, rayThruImage);
- }
- RemoveOrphanPoints(ptsInThisScan);
- strokePoints.AddRange(ptsInThisScan);
- }
- */
- }
-
- private void MergeNearbyPoints (Listpts)
- {
- List<(int p1, int p2, float val)> distances = new();
- for (int i = 0; i < pts.Count; i++)
- {
- for (int j = i+1; j < pts.Count; j++)
- {
- if (i == j) continue;
- distances.Add(new (i,j,(pts[i] - pts[j]).R));
- }
- }
- distances = distances.OrderBy(v=>v.val).ToList();
- List ptsToDelete = new();
- for (int i = 0;i < distances.Count; i++)
- {
- if (distances[i].val >= 1) break;
- pts[distances[i].p1].X = (pts[distances[i].p1].X + pts[distances[i].p2].X) / 2;
- pts[distances[i].p1].Y = (pts[distances[i].p1].Y + pts[distances[i].p2].Y) / 2;
- ptsToDelete.Add(distances[i].p2);
- }
- ptsToDelete = ptsToDelete.OrderByDescending(x=>x).Distinct().ToList();
- foreach (int i in ptsToDelete)
- {
- pts.RemoveAt(i);
- }
- }
-
- private void RemoveOrphanPoints(List points)
- {
- //Remove orphan points which can be caused by curved edges
- for (int i = 0; i < points.Count; i++)
- {
- PointPlus pt1 = points[i];
- float dist = 0;
- for (int j = 0; j < points.Count; j++)
- {
- if (j == i) continue;
- PointPlus pt2 = points[j];
- dist = (pt1 - pt2).R;
- if (dist < 2)
- goto neighborFound;
- }
- points.RemoveAt(i);
- i = (i == 0) ? i - 1 : i - 2;
- neighborFound: continue;
- }
- }
-
- List FindStrokeeCentersFromBoundaryPoints(List points)
- {
- List strokeCenters = new List();
- foreach (PointPlus pt in points)
- {
- List nearbyPts = GetNearbyPoints(pt, 5f, points);
- foreach (PointPlus pt2 in nearbyPts)
- {
- if ((pt - pt2).R < 2f) continue;
- PointPlus possibleStrokeCenter = (new Segment(pt2, pt).MidPoint);
- List nearbyPts2 = GetNearbyPoints(possibleStrokeCenter, 1f, points);
- if (nearbyPts2.Count == 0 && GetLuminanceAtPoint(possibleStrokeCenter) > .8)
- {
- List nearbyPts3 = GetNearbyPoints(possibleStrokeCenter, .7f, strokeCenters);
- if (nearbyPts3.Count == 0)
- strokeCenters.Add(possibleStrokeCenter);
- else
- {
- //PointPlus averagePt = new PointPlus(nearbyPts3.Average(x=>x.X),nearbyPts3.Average(x=>x.Y) );
- }
-
- }
- }
- }
- return strokeCenters;
- }
- float GetLuminanceAtPoint(PointPlus pt)
- {
- var c = imageArray[(int)Round(pt.X), (int)Round(pt.Y)];
- HSLColor c1 = new(c);
- return c1.luminance;
- }
- private List FindStrokePtsInRay(float sx, float sy, float dx, float dy, List rayThruImage)
- {
- if (sy == 10)
- { }
- List luminance = new List();
- foreach (var color in rayThruImage)
- {
- float lum = new HSLColor(color).luminance;
- luminance.Add(lum );
- }
- List ptsInThisScan = new();
- (List<(int index, float value)> maxima, List<(int index, float value)> minima) v = FindLocalExtrema(luminance, 0.06f);
- for (int i = 0; i < v.Item1.Count; i++)
- {
- (int index, float value) item = v.maxima[i];
- float minBrightness = 0.35f; //max brightnesee for a "black" pixel
- float minBrightness1 = 0.7f; //min brightness for white pixel
- int maxStrokeWidth = 6;
-
- if (item.Item2 < minBrightness1) continue;
- //find start and end of stroke
- int startStroke = item.Item1;
- int minPos = 0;
- if (i > 0)
- minPos = v.Item2[i - 1].Item1;
- while (startStroke > minPos && luminance[startStroke] > minBrightness)
- startStroke--;
- int endStroke = item.Item1;
- int maxPos = luminance.Count - 1;
- if (i < v.Item2.Count)
- maxPos = v.Item2[i].Item1;
- while (endStroke < maxPos && luminance[endStroke] > minBrightness)
- endStroke++;
-
- if (endStroke - startStroke > maxStrokeWidth)
- continue; //stroke is too wide to be a stroke
-
- //find the position based on weighted average
- float boundaryPos = (startStroke + endStroke) / 2f; //for debug
- float numerator = 0;
- float denominator = 0;
- for (int j = startStroke+1; j < endStroke; j++)
- {
- numerator += j * luminance[j];
- denominator += luminance[j];
- }
-
- boundaryPos = numerator / denominator;
- //boundaryPos = (float)Round(boundaryPos, 1);
- if (dx == 1 && dy == 0)
- ptsInThisScan.Add(new Point(boundaryPos, sy));
- else if (dx == 0 && dy == 1)
- ptsInThisScan.Add(new Point(sx, boundaryPos));
- else if (dx >= 0 && dy >= 0)
- ptsInThisScan.Add(new Point(boundaryPos + sx, boundaryPos + sy));
- else
- ptsInThisScan.Add(new Point(sx - boundaryPos, boundaryPos + sy));
- }
-
- return ptsInThisScan;
- }
-
- public static (List<(int index, float value)>, List<(int index, float value)>) FindLocalExtrema(List data, float minDifference)
- {
- List<(int index, float value)> maxima = new();
- List<(int index, float value)> minima = new();
-
- bool? increasing = null; // Null indicates no trend established yet
- float highestValue = 0;
- float lowestValue = 0;
- for (int i = 1; i < data.Count - 1; i++)
- {
- if (data[i] > data[i - 1])
- {
- highestValue = data[i];
- if (Math.Abs(data[i - 1] - data[i]) > minDifference)
- {
- if (increasing == false)
- {
- minima.Add(new(i - 1, lowestValue));
- }
- increasing = true;
- }
- }
- else if (data[i] < data[i - 1])
- {
- lowestValue = data[i];
- if (Math.Abs(highestValue - data[i]) > minDifference)
- {
- if (increasing == true)
- {
- maxima.Add(new(i - 1, highestValue));
- increasing = false;
- }
- }
- }
- }
-
- return (maxima, minima);
- }
-
-
- private void FindBoundaryPtsInRay(float sx, float sy, float dx, float dy, List rayThruImage)
- {
- if (sx == 20)
- { }
- List ptsInThisScan = new();
-
- List luminances = new();
- foreach (var v in rayThruImage)
- luminances.Add(new HSLColor(v).luminance);
-
- List minima = new();
- List maxima = new();
-
- int direction = 0; //-1 for decreasing, 1 for increasing, 0 for flat
- float previous = luminances[0];
- if (previous > 0.5)
- {
- direction = -1;
- maxima.Add(0);
- }
- else
- {
- direction = 1;
- minima.Add(0);
- }
-
- for (int i = 1; i < luminances.Count - 1; i++)
- {
- float delta = luminances[i] - previous;
- if (Abs(delta) < .1f) continue;
-
- if (luminances[i] > previous)
- {
- if (direction == -1 && previous < 0.6f) // local minima
- {
- if (i > 1 && luminances[i - 2] < luminances[i - 1])
- minima.Add(i - 2);
- else
- minima.Add(i - 1);
- }
- direction = 1;
- }
- else if (luminances[i] < previous)
- {
- if (direction == 1 && previous > 0.6f) // local maxima
- {
- if (i > 1 && luminances[i - 2] > luminances[i - 1])
- maxima.Add(i - 2);
- else
- maxima.Add(i - 1);
- }
- direction = -1;
- }
- else
- direction = 0;
- previous = luminances[i];
- }
-
- if (direction == 1) maxima.Add(luminances.Count - 1);
- if (direction == -1) minima.Add(luminances.Count - 1);
-
- int curMin = 0;
- int curMax = 0;
- while (curMin < minima.Count && curMax < maxima.Count)
- {
- float boundaryPos = -1;
- if (minima[curMin] < maxima[curMax])
- {
- boundaryPos = FindMidValuePt(luminances, minima[curMin], maxima[curMax]);
- curMin++;
- }
- else
- {
- boundaryPos = FindMidValuePt(luminances, maxima[curMax], minima[curMin]);
- curMax++;
- }
- //no boundary found
- if (boundaryPos < 0 || boundaryPos >= rayThruImage.Count) continue;
- boundaryPos = (float)Round(boundaryPos, 1);
-
- if (dx == 1 && dy == 0)
- ptsInThisScan.Add(new Point(boundaryPos, sy));
- else if (dx == 0 && dy == 1)
- ptsInThisScan.Add(new Point(sx, boundaryPos));
- else if (dx >= 0 && dy >= 0)
- ptsInThisScan.Add(new Point(boundaryPos + sx, boundaryPos + sy));
- else
- ptsInThisScan.Add(new Point(sx - boundaryPos, boundaryPos + sy));
- }
- foreach (var pt in ptsInThisScan)
- boundaryPoints.Add(pt);
- }
- private float FindMidValuePt(List pts, int start, int end)
- {
- //if start and end are far apart, move them closer to eleminate noise values
- float val1 = pts[start];
- float val2 = pts[end];
- if (Abs(val1 - val2) < .14f) return -1;
- if (end - start > 10)
- {
- while (start < pts.Count - 2 && Abs(val1 - pts[start + 1]) < .1) start++;
- while (end > 1 && Abs(val2 - pts[end - 1]) < .1) end--;
- }
-
- float crossingPoint = -1;
- float targetValue = (pts[start] + pts[end]) / 2f;
- for (int i = start; i < end; i++)
- {
- float currVal = pts[i];
- float nextVal = pts[i + 1];
- if ((currVal <= targetValue && nextVal >= targetValue) ||
- (currVal >= targetValue && nextVal <= targetValue))
- {
- float t = (targetValue - currVal) / (nextVal - currVal);
- crossingPoint = i + t;
- break;
- }
- }
- return crossingPoint;
- }
-
- private void FindBoundaryPtsInRay2(float sx, float sy, float dx, float dy, List rayThruImage)
- {
- //given a ray of color values through an image, find the boundaries
- //todo: filter out noisy areas in the ray
- if (sx == 21 || sy == 10)
- { }
-
- int start = -1;
- for (int i = 0; i < rayThruImage.Count - 1; i++)
- {
- float boundaryPos = -1;
- float diff = PixelDifference(rayThruImage[i], rayThruImage[i + 1]);
-
- if (diff < 200)
- {
- //pixels are the same...move the start of a boundary
- start = i;
- }
- else
- {
- //find the end of the boundary There are 2 pixels the same
- for (int j = start + 1; j < rayThruImage.Count - 1; j++)
- {
- int end = j + 1;
- float diffEnd = PixelDifference(rayThruImage[j], rayThruImage[end]);
- if (diffEnd < 200)
- {
- //this will offset the boundary point based on the intensity of the intervening point
- List colors = new List();
- for (int k = start; k <= end; k++)
- colors.Add(new HSLColor(rayThruImage[k]));
-
- List lums = new();
- for (int k = start; k <= end; k++)
- {
- lums.Add((new HSLColor(rayThruImage[k])).luminance);
- }
-
-
- boundaryPos = (i + j) / 2f;
- if (colors.Count == 4)
- { }
- else if (colors.Count == 5 || colors.Count == 6)
- {
- if (colors.Count == 6)
- {
- boundaryPos -= 0.25f;
- colors.RemoveAt(3);
- }
- float startingluminance = colors[0].luminance;
- float endingluminance = colors.Last().luminance;
- float centerluminance = colors[(int)colors.Count / 2].luminance;
- float t = 1 - (startingluminance - centerluminance) / (startingluminance - endingluminance);
- boundaryPos += t - 0.5f;
- }
- else boundaryPos = -1;
- i = j - 1;
- start = i;
- break;
- }
- }
- }
- //boundaryPos = (start + end) / 2f;
- if (boundaryPos < 0 || boundaryPos >= rayThruImage.Count) continue;
- if (dx == 1 && dy == 0)
- boundaryPoints.Add(new Point(boundaryPos, sy));
- else if (dx == 0 && dy == 1)
- boundaryPoints.Add(new Point(sx, boundaryPos));
- else if (dx >= 0 && dy >= 0)
- boundaryPoints.Add(new Point(boundaryPos + sx, boundaryPos + sy));
- else
- boundaryPoints.Add(new Point(sx - boundaryPos, boundaryPos + sy));
- }
- }
-
- List LineThroughArray(float dx, float dy, int startX, int startY, Color[,] imageArray)
- {
- List retVal = new();
- float x = startX; float y = startY;
-
- while (x >= 0 && y >= 0 && x < imageArray.GetLength(0) && y < imageArray.GetLength(1))
- {
- Color c = imageArray[(int)x, (int)y];
- if (c != null)
- retVal.Add(c);
- x += dx;
- y += dy;
- }
- return retVal;
- }
-
-}
-
diff --git a/BrainSimulator/Modules/Vision/ModuleVision.cs b/BrainSimulator/Modules/Vision/ModuleVision.cs
deleted file mode 100644
index 6c92528..0000000
--- a/BrainSimulator/Modules/Vision/ModuleVision.cs
+++ /dev/null
@@ -1,725 +0,0 @@
-/*
- * Brain Simulator Through
- *
- * Copyright (c) 2026 Charles Simon
- *
- * This file is part of Brain Simulator Through and is licensed under
- * the MIT License. You may use, copy, modify, merge, publish, distribute,
- * sublicense, and/or sell copies of this software under the terms of
- * the MIT License.
- *
- * See the LICENSE file in the project root for full license information.
- */
-//
-// PROPRIETARY AND CONFIDENTIAL
-// Brain Simulator 3 v.1.0
-// © 2022 FutureAI, Inc., all rights reserved
-//
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Windows.Media.Imaging;
-using UKS;
-using System.Windows.Media;
-using System.Windows;
-using static System.Math;
-
-namespace BrainSimulator.Modules
-{
- public partial class ModuleVision : ModuleBase
- {
- public string currentFilePath = "";
- public string previousFilePath = "";
- public BitmapImage bitmap = null;
- public List corners;
- public List segments;
- public Color[,] imageArray;
- //public HoughTransform segmentFinder;
- public List strokePoints = new();
- public List boundaryPoints = new();
-
- public class Corner
- {
- public PointPlus pt;
- public virtual Angle angle
- {
- get
- {
- Segment s1 = new Segment(prevPt, pt);
- Segment s2 = new Segment(pt, nextPt);
- Angle a = s2.Angle - s1.Angle;
- while (a.Degrees > 180)
- a = a - Angle.FromDegrees(180);
- while (a.Degrees < -180)
- a = a + Angle.FromDegrees(180);
- return a;
- }
- }
- public bool curve = false;
- public PointPlus prevPt;
- public PointPlus nextPt;
- public override string ToString()
- {
- return $"[x,y:({pt.X.ToString("0.0")},{pt.Y.ToString("0.0")}) " +//A: {angle}] " +
- $"prevPt:[({prevPt.X.ToString("0.0")},{prevPt.Y.ToString("0.0")})] " +
- $"nextPt:[({nextPt.X.ToString("0.0")},{nextPt.Y.ToString("0.0")})]";
- }
- }
- public class Arc : Corner
- {
- //an arc is defined by three (non-collinear) points
- //prevPt and nextPt are the endpoints of the arc and pt is any thirde point somewhere on the arc
- public Arc()
- {
- curve = true;
- }
- public override Angle angle
- {
- get
- {
- var cir = GetCircleFromThreePoints(pt, nextPt, prevPt);
- Angle startAngle = (prevPt - cir.center).Theta.Normalize();
- Angle midAngle = (pt - cir.center).Theta.Normalize();
- Angle endAngle = (nextPt - cir.center).Theta.Normalize();
-
- Angle a = Abs(startAngle - endAngle);
- //if the midAngle is not between start and end angles, go the other way areound the arc
- //TODO: handle other cases
- if (midAngle > startAngle && midAngle > endAngle)
- a = 2 * PI - a;
-
- return a;
- }
- }
- // Function to calculate the center and radius of the circle through three points
- public (PointPlus center, float radius) GetCircleFromThreePoints(PointPlus p1, PointPlus p2, PointPlus p3)
- {
- float x1 = p1.X, y1 = p1.Y;
- float x2 = p2.X, y2 = p2.Y;
- float x3 = p3.X, y3 = p3.Y;
-
- // Calculate the perpendicular bisectors of two segments
- float ma = (y2 - y1) / (x2 - x1);
- float mb = (y3 - y2) / (x3 - x2);
-
- // Calculate the center of the circle (intersection of the bisectors)
- float cx = (ma * mb * (y1 - y3) + mb * (x1 + x2) - ma * (x2 + x3)) / (2 * (mb - ma));
- float cy = -1 * (cx - (x1 + x2) / 2) / ma + (y1 + y2) / 2;
-
- PointPlus center = new PointPlus(cx, cy);
-
- // Calculate the radius of the circle
- float radius = (center - p1).R;
-
- return (center, radius);
- }
-
-
- }
- public ModuleVision()
- {
- }
-
- //fill this method in with code which will execute
- //once for each cycle of the engine
- public override void Fire()
- {
- Init(); //be sure to leave this here
-
- if (currentFilePath == previousFilePath) return;
- previousFilePath = currentFilePath;
-
- LoadImageFileToPixelArray(currentFilePath);
-
- FindBackgroundColor();
-
- FindBoundaries(imageArray);
-
- //strokePoints = FindStrokeeCentersFromBoundaryPoints(boundaryPoints);
-
- segments = new();
- corners = new();
- if (strokePoints.Count > boundaryPoints.Count / 4)
- {
- FindArcsAndSegments(strokePoints);
- FindCorners(ref segments);
- SaveSymbolToUKS();
- }
- else
- {
- segments = FindSegments(boundaryPoints);
- FindCorners(ref segments);
- FindOutlines();
- }
-
- WriteBitmapToMentalModel();
-
- UpdateDialog();
- }
-
- private void SaveSymbolToUKS()
- {
- int maxExtent = (int)strokePoints.Max(x => Math.Max(x.X, x.Y));
- Thing shapesParent = theUKS.GetOrAddThing("CurrentSymbol", "Visual");
- theUKS.DeleteAllChildren(shapesParent);
- Thing shapeParent = theUKS.GetOrAddThing("Symbol*", shapesParent);
- theUKS.GetOrAddThing("arc", "Visual");
- theUKS.GetOrAddThing("corner", "Visual");
- theUKS.GetOrAddThing("segment", "Visual");
- foreach (var corner in corners)
- {
- Thing item = theUKS.AddThing("Item*", shapeParent);
- if (corner is Arc a)
- {
- item.AddRelationship("arc", "is");
- int degrees = (int)a.angle.Degrees;
- degrees = ((degrees + 5 * Math.Sign(degrees)) / 10) * 10;
- item.AddRelationship(theUKS.GetOrAddThing("angle" + degrees,"Rotation"), "is");
-
- }
- else
- {
- item.AddRelationship("corner", "is");
- Segment s1 = new Segment(corner.prevPt, corner.pt);
- Segment s2 = new Segment(corner.pt, corner.nextPt);
- Thing item1 = theUKS.AddThing("Item*", shapeParent);
- item1.AddRelationship("segment", "is");
- //int degrees = (int)s1.Angle.Degrees;
- //degrees = ((degrees + 5 * Math.Sign(degrees)) / 10) * 10;
- //item1.AddRelationship("angle" + degrees, "is");
- int length = (int)(s1.Length * 10 + 5) / maxExtent;
- item1.AddRelationship("distance." + length, "is");
-
- //Thing item2 = theUKS.AddThing("Item*", shapeParent);
- //item2.AddRelationship("segment", "is");
- }
- }
- }
-
- public float scale = 1;
- public int offsetX = 0;
- public int offsetY = 0;
-
- public void LoadImageFileToPixelArray(string filePath)
- {
- using (System.Drawing.Bitmap bitmap2 = new(currentFilePath))
- {
- System.Drawing.Bitmap theBitmap = bitmap2;
-
- int bitmapSizeX = theBitmap.Width;
- int bitmapSizeY = theBitmap.Height;
-
- float max = int.Max(bitmapSizeX, bitmapSizeY);
- if (max > 50)
- {
- bitmapSizeX = (int)(bitmapSizeX * 50f / max);
- bitmapSizeY = (int)(bitmapSizeY * 50f / max);
- }
-
- //do not expand an image if it is smaller than the bitmap...it can introduce problems
- if (theBitmap.Width < bitmapSizeX) scale = (float)theBitmap.Width / bitmapSizeX;
- if (scale > theBitmap.Width / bitmapSizeX) scale = theBitmap.Width / bitmapSizeX;
- //limit the x&y offsets so the picture will be displayed
- float maxOffset = bitmapSizeX * scale - bitmapSizeX;
- if (offsetX > 0) offsetX = 0;
- if (offsetX < -maxOffset) offsetX = -(int)maxOffset;
- if (offsetY > 0) offsetY = 0;
- if (offsetY < -maxOffset) offsetY = -(int)maxOffset;
- System.Drawing.Bitmap resizedImage = new(bitmapSizeX, bitmapSizeY);
- using (System.Drawing.Graphics graphics = System.Drawing.Graphics.FromImage(resizedImage))
- {
- graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
- //graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
-
- graphics.DrawImage(bitmap2, offsetX, offsetY, bitmapSizeX * scale, bitmapSizeY * scale);
- }
-
- imageArray = new Color[resizedImage.Width, resizedImage.Height];
-
- for (int i = 0; i < resizedImage.Width; i++)
- for (int j = 0; j < resizedImage.Height; j++)
- {
- var c = resizedImage.GetPixel(i, j);
- imageArray[i, j] = new Color() { A = 0xff, R = c.R, G = c.G, B = c.B };
- }
- }
- dlg.Draw(false);
- }
- private void WriteBitmapToMentalModel()
- {
- Thing mentalModel = theUKS.GetOrAddThing("MentalModel", "Thing");
- Thing mentalModelArray = theUKS.GetOrAddThing("MentalModelArray", "MentalModel");
- mentalModel.SetFired();
- //TODO Make angular
- //TODO Make 0 center
- for (int x = 0; x < 25; x++)
- for (int y = 0; y < 25; y++)
- {
- string name = $"mm{x},{y}";
- Thing theEntry = theUKS.GetOrAddThing(name, mentalModelArray);
- theEntry.V = GetAverageColor(x * 4, y * 4);
- }
- }
- private Color GetAverageColor(int x, int y)
- {
- Color retVal = Color.FromArgb(1, 0, 0, 0);
- int size = 2;
- for (int i = -size; i <= size; i++)
- for (int j = -size; j <= size; j++)
- {
- if (x + i < 0) continue;
- if (y + j < 0) continue;
- if (x + i >= imageArray.GetLength(0)) continue;
- if (y + j >= imageArray.GetLength(1)) continue;
- retVal.R += imageArray[x + i, y + j].R;
- retVal.G += imageArray[x + i, y + j].G;
- retVal.B += imageArray[x + i, y + j].B;
- }
- retVal.R /= 25;
- retVal.R /= 25;
- retVal.R /= 25;
- return retVal;
- }
-
-
- private Color[,] GetImageArrayFromBitmapImage()
- {
- int height = (int)bitmap.Height;
- int width = (int)bitmap.Width;
- if (height > bitmap.PixelHeight) height = (int)bitmap.PixelHeight;
- if (width > bitmap.PixelWidth) width = (int)bitmap.PixelWidth;
- imageArray = new Color[width, height];
- int stride = (bitmap.PixelWidth * bitmap.Format.BitsPerPixel + 7) / 8;
- byte[] pixelBuffer = new byte[stride * bitmap.PixelHeight];
- bitmap.CopyPixels(pixelBuffer, stride, 0);
- for (int i = 0; i < imageArray.GetLength(0); i++)
- {
- for (int j = 0; j < imageArray.GetLength(1); j++)
- {
- //upper for jpeg, lower for png
- int index = j * stride + i * 4; // Assuming 32 bits per pixel (4 bytes: BGRA)
- if (bitmap.Format.BitsPerPixel == 8)
- index = j * stride * 3 + i * 3;
- if (index >= pixelBuffer.Length) continue;
-
- if (bitmap.Format.BitsPerPixel != 8 && index < pixelBuffer.Length - 3)
- {
- byte blue = pixelBuffer[index];
- byte green = pixelBuffer[index + 1];
- byte red = pixelBuffer[index + 2];
- byte alpha = pixelBuffer[index + 3];
- Color pixelColor = Color.FromArgb(1, red, green, blue);
- imageArray[i, j] = pixelColor;
- }
- else
- {
- byte red, green, blue, alpha;
- if (bitmap.Palette != null)
- {
- var c = bitmap.Palette.Colors[pixelBuffer[index]];
- blue = c.B;
- red = c.R;
- green = c.G;
- }
- else
- {
- blue = pixelBuffer[index];
- if (bitmap.Format.BitsPerPixel > 8)
- {
- green = pixelBuffer[index + 1];
- red = pixelBuffer[index + 2];
- alpha = pixelBuffer[index + 3];
- }
- else
- {
- red = blue;
- green = blue;
-
- }
- }
- Color pixelColor = Color.FromArgb(1, red, green, blue);
- imageArray[i, j] = pixelColor;
-
- }
- }
- }
-
- return imageArray;
- }
-
-
- float PixelDifference(Color c1, Color c2)
- {
- float retVal = 0;
- retVal += c1.R - c2.R;
- retVal += c1.G - c2.G;
- retVal += c1.B - c2.B;
- return retVal;
- }
-
- private class taggedSegment { public Segment s; public bool pt1Used; public bool pt2Used; }
- private void FindCorners(ref List segmentsIn)
- {
- MergeSegments(segmentsIn);
-
- List taggedSegments = new();
- foreach (Segment s in segmentsIn)
- taggedSegments.Add(new taggedSegment() { s = s, pt1Used = false, pt2Used = false });
-
-
- //build a table of distances between each point and each other
- List<(int i, int j, float p1p1, float p1p2, float p2p1, float p2p2, float closest)> distances = new();
- for (int i = 0; i < taggedSegments.Count - 1; i++)
- {
- var s1 = taggedSegments[i];
- for (int j = i + 1; j < taggedSegments.Count; j++)
- {
- if (i == j) continue;
- var s2 = taggedSegments[j];
- float p1p1 = (s1.s.P1 - s2.s.P1).R;
- float p1p2 = (s1.s.P1 - s2.s.P2).R;
- float p2p1 = (s1.s.P2 - s2.s.P1).R;
- float p2p2 = (s1.s.P2 - s2.s.P2).R;
- float closest = (float)new List { p1p1, p1p2, p2p1, p2p2 }.Min();
- distances.Add((i, j, p1p1, p1p2, p2p1, p2p2, closest));
- }
- }
- distances = distances.OrderBy(x => x.closest).ToList();
-
- foreach (var distance in distances)
- {
- if (distance.closest > 4.2) break; //give up when the distance is large
- var s1 = taggedSegments[distance.i];
- var s2 = taggedSegments[distance.j];
- if (distance.closest == distance.p1p1 && !s1.pt1Used && !s2.pt1Used)
- {
- bool segmentsIntersect = Utils.LinesIntersect(s1.s, s2.s, out PointPlus intersection);
- if (segmentsIntersect)
- {
- AddCornerToList(intersection, s1.s.P2, s2.s.P2);
- UpdateCornerPoint(s1.s.P1, intersection);
- UpdateCornerPoint(s2.s.P1, intersection);
- s1.pt1Used = true;
- s2.pt1Used = true;
- }
- }
- if (distance.closest == distance.p1p2 && !s1.pt1Used && !s2.pt2Used)
- {
- bool segmentsIntersect = Utils.LinesIntersect(s1.s, s2.s, out PointPlus intersection);
- if (segmentsIntersect)
- {
- AddCornerToList(intersection, s1.s.P2, s2.s.P1);
- UpdateCornerPoint(s1.s.P1, intersection);
- UpdateCornerPoint(s2.s.P2, intersection);
- s1.pt1Used = true;
- s2.pt2Used = true;
- }
- }
- if (distance.closest == distance.p2p1 && !s1.pt2Used && !s2.pt1Used)
- {
- bool segmentsIntersect = Utils.LinesIntersect(s1.s, s2.s, out PointPlus intersection);
- if (segmentsIntersect)
- {
- AddCornerToList(intersection, s1.s.P1, s2.s.P2);
- UpdateCornerPoint(s1.s.P2, intersection);
- UpdateCornerPoint(s2.s.P1, intersection);
- s1.pt2Used = true;
- s2.pt1Used = true;
- }
- }
- if (distance.closest == distance.p2p2 && !s1.pt2Used && !s2.pt2Used)
- {
- bool segmentsIntersect = Utils.LinesIntersect(s1.s, s2.s, out PointPlus intersection);
- if (segmentsIntersect)
- {
- AddCornerToList(intersection, s1.s.P1, s2.s.P1);
- UpdateCornerPoint(s1.s.P2, intersection);
- UpdateCornerPoint(s2.s.P2, intersection);
- s1.pt2Used = true;
- s2.pt2Used = true;
- }
- }
- }
- //find any orphans
- foreach (var segment in taggedSegments)
- {
- if (!segment.pt2Used)
- AddCornerToList(segment.s.P2, segment.s.P1, segment.s.P1);
- if (!segment.pt1Used)
- AddCornerToList(segment.s.P1, segment.s.P2, segment.s.P2);
- }
- return;
- }
-
- private void UpdateCornerPoint(PointPlus oldValue, PointPlus newValue)
- {
- for (int i = 0; i < corners.Count; i++)
- {
- //if (corners[i].pt == oldValue) corners[i].pt = newValue;
- if (corners[i].nextPt == oldValue) corners[i].nextPt = newValue;
- if (corners[i].prevPt == oldValue) corners[i].prevPt = newValue;
- }
- }
- private void AddCornerToList(PointPlus intersection, PointPlus prevPt, PointPlus nextPt)
- {
- //allow things to be offset by a few pixels
- //Is this corner already in the list?
- Corner alreadyInList = corners.FindFirst(x =>
- (x.pt - intersection).R < 2 &&
- (((x.prevPt - prevPt).R < 2 && (x.nextPt - nextPt).R < 2) ||
- ((x.prevPt - nextPt).R < 2 && (x.nextPt - prevPt).R < 2)));
- if (alreadyInList == null && prevPt == nextPt)
- {
- //is it an endpoint of a curve?
- alreadyInList = corners.FindFirst(x =>
- x.curve &&
- ((x.nextPt - intersection).R < 2 ||
- (x.prevPt - intersection).R < 2));
- }
- if (alreadyInList == null)
- corners.Add(new Corner { pt = intersection, prevPt = prevPt, nextPt = nextPt });
- else { }
- }
-
-
- //trace around the outlines to get the order of corners and relative distances
- private void FindOutlines()
- {
- //set up the UKS structure for outlines
- GetUKS();
- if (theUKS == null) return;
- theUKS.GetOrAddThing("Sense", "Thing");
- theUKS.GetOrAddThing("Visual", "Sense");
- Thing outlines = theUKS.GetOrAddThing("Outline", "Visual");
- Thing tCorners = theUKS.GetOrAddThing("Corner", "Visual");
- theUKS.DeleteAllChildren(outlines);
- theUKS.DeleteAllChildren(tCorners);
-
- if (corners.Count == 0) return;
-
- //for convenience in debugging
- corners = corners.OrderBy(x => x.pt.X).OrderBy(x => x.pt.Y).ToList();
-
- //perhaps there are multiple shapes?
- List cornerAvailable = new List();
- for (int i = 0; i < corners.Count; i++)
- cornerAvailable.Add(corners[i]);
-
- while (cornerAvailable.Count > 0)
- {
- Corner curr = cornerAvailable[0];
- List outline = new();
- bool outlineClosed = false;
- Corner start = curr;
- outline.Add(curr);
- cornerAvailable.Remove(curr);
- while (!outlineClosed)
- {
- for (int i = 0; i < cornerAvailable.Count; i++)
- {
- Corner next = cornerAvailable[i];
- if (next.angle == 0) continue;
- if (outline.Contains(next))
- continue; //should never happen
- if (curr.nextPt.Near(next.pt, 2) || curr.prevPt.Near(next.pt, 2))
- {
- outline.Add(next);
- cornerAvailable.Remove(next);
- curr = next;
- goto pointAdded;
- }
- }
- outlineClosed = true; //no more points to add
- pointAdded: continue;
- }
-
- //make this a right-handed list of points
- double sum = 0;
- int cnt = outline.Count;
- for (int i = 0; i < outline.Count; i++)
- {
- Corner p1 = outline[i];
- Corner p2 = outline[(i + 1) % cnt];
- sum += (p2.pt.X - p1.pt.X) *
- (p2.pt.Y + p1.pt.Y);
- }
- if (sum > 0)
- outline.Reverse();
-
- //find the color at the center of the polygon
- List thePoints = new();
- foreach (Corner c in outline)
- thePoints.Add(c.pt);
- Point centroid = Utils.GetCentroid(thePoints);
-
- Thing currOutline = theUKS.GetOrAddThing("Outline*", "Outlines");
-
- //get the color (the centroid might be outside the image)
- try
- {
- //HSLColor theCenterColor = imageArray[(int)centroid.X, (int)centroid.Y];
- Color theCenterColor = imageArray[(int)centroid.X, (int)centroid.Y];
- Thing theColor = GetOrAddColor(theCenterColor);
- currOutline.SetAttribute(theColor);
- }
- catch (Exception e) { }
-
- //we now have an ordered, right-handed outline
- //add it to UKS
- for (int i = 1; i < outline.Count + 1; i++)
- {
- Corner c = outline[i % outline.Count];
-
- //let's update the angle
- //PointPlus prev = outline[(i - 1) % outline.Count].pt;
- //PointPlus next = outline[(i + 1) % outline.Count].pt;
- // c.nextPt = next;
- // c.prevPt = prev;
-
-
- //TODO: modify to reuse existing (shared) points
- //let's add it to the UKS
- Thing corner = theUKS.GetOrAddThing("corner*", tCorners);
- corner.V = c;
- theUKS.AddStatement(currOutline, "has*", corner);
- }
- }
- }
-
- Thing GetOrAddColor(Color color)
- {
- Thing colorParent = theUKS.GetOrAddThing("Color", "Attribute");
- foreach (Thing t in colorParent.Children)
- {
- if (t.V is Color c && c.Equals(color))
- return t;
- }
- Thing theColor = theUKS.GetOrAddThing("color*", "Color");
- theColor.V = color;
- return theColor;
- }
-
-
- // fill this method in with code which will execute once
- // when the module is added, when "initialize" is selected from the context menu,
- // or when the engine restart button is pressed
- public override void Initialize()
- {
- }
-
- // the following can be used to massage public data to be different in the xml file
- // delete if not needed
- public override void SetUpBeforeSave()
- {
- Thing t = theUKS.Labeled("currentShape");
- if (t != null) { theUKS.DeleteAllChildren(t); }
- t = theUKS.Labeled("corner");
- if (t != null) { theUKS.DeleteAllChildren(t); }
- t = theUKS.Labeled("Outline");
- if (t != null) { theUKS.DeleteAllChildren(t); }
- t = theUKS.Labeled("MentalModel");
- if (t != null) { theUKS.DeleteAllChildren(t); }
- }
-
-
- public override void SetUpAfterLoad()
- {
- SetUpUKSEntries();
-
- //here we parse
- //objects out of the Xml stream
- foreach (Thing t in theUKS.UKSList)
- {
- if (t.V is System.Xml.XmlNode[] nodes)
- {
- if (nodes[0].Value == "Color")
- {
- byte A = byte.Parse(nodes[1].InnerText);
- byte R = byte.Parse(nodes[2].InnerText);
- byte G = byte.Parse(nodes[3].InnerText);
- byte B = byte.Parse(nodes[4].InnerText);
- Color theColor = new() {A=A,R=R,G=G,B=B, };
- t.V = theColor;
- }
- if (nodes[0].Value == "HSLColor")
- {
- float hue = float.Parse(nodes[1].InnerText);
- float saturation = float.Parse(nodes[2].InnerText);
- float luminance = float.Parse(nodes[3].InnerText);
- HSLColor theColor = new(hue, saturation, luminance);
- t.V = theColor;
- }
- if (nodes[0].Value == "Corner")
- {
- Corner c = new();
- //get a pointplus node
- float x = float.Parse(nodes[1].FirstChild.InnerText);
- float y = float.Parse(nodes[1].FirstChild.NextSibling.InnerText);
- float conf = float.Parse(nodes[1].FirstChild.NextSibling.NextSibling.InnerText);
- c.pt = new PointPlus { X = x, Y = y, Conf = conf, };
- //get the angle node
- float theta = float.Parse(nodes[2].FirstChild.InnerText);
- //get the orientation node
- float theta1 = float.Parse(nodes[3].FirstChild.InnerText);
- //c.orientation = Angle.FromDegrees(theta1);
- t.V = c;
- }
- }
- }
-
- }
-
- private void SetUpUKSEntries()
- {
- theUKS.AddStatement("Attribute", "is-a", "Thing");
- theUKS.AddStatement("Color", "is-a", "Attribute");
- theUKS.AddStatement("Size", "is-a", "Attribute");
- theUKS.AddStatement("Position", "is-a", "Attribute");
- theUKS.AddStatement("Rotation", "is-a", "Attribute");
- theUKS.AddStatement("Shape", "is-a", "Attribute");
- theUKS.AddStatement("Offset", "is-a", "Attribute");
- theUKS.AddStatement("Distance", "is-a", "Attribute");
-
- //Set up angles and distances so they are near each other
- Relationship r2 = null;
- r2 = theUKS.AddStatement("isSimilarTo", "is-a", "relationshipType");
- r2 = theUKS.AddStatement("isSimilarTo", "hasProperty", "isCommutative");
- r2 = theUKS.AddStatement("isSimilarTo", "hasProperty", "isTransitive");
-
- for (int i = 1; i < 10; i++)
- {
- theUKS.AddStatement("distance." + i, "is-a", "distance");
- if (i < 9)
- r2 = theUKS.AddStatement("distance." + i, "isSimilarTo", "distance." + (i + 1));
- r2.Weight = 0.8f;
- }
- theUKS.AddStatement("distance1.0", "is-a", "distance");
- r2 = theUKS.AddStatement("distance1.0", "isSimilarTo", "distance.9");
- r2.Weight = 0.8f;
-
- for (int i = -17; i < 18; i++)
- {
- theUKS.AddStatement("angle" + (i * 10), "is-a", "Rotation");
- r2 = theUKS.AddStatement("angle" + (i * 10), "isSimilarTo", "angle" + ((i + 1) * 10));
- r2.Weight = 0.8f;
- }
- r2 = theUKS.AddStatement("angle180", "is-a", "rotation");
- r2 = theUKS.AddStatement("angle180", "isSimilarTo", "angle-170");
- r2.Weight = 0.8f;
- }
-
- // called whenever the size of the module rectangle changes
- // for example, you may choose to reinitialize whenever size changes
- // delete if not needed
- public override void SizeChanged()
- {
-
- }
- public override void UKSInitializedNotification()
- {
- SetUpUKSEntries();
- }
-
- }
-}
diff --git a/BrainSimulator/Modules/Vision/ModuleVisionDlg.xaml b/BrainSimulator/Modules/Vision/ModuleVisionDlg.xaml
deleted file mode 100644
index abe0941..0000000
--- a/BrainSimulator/Modules/Vision/ModuleVisionDlg.xaml
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/BrainSimulator/Modules/Vision/ModuleVisionDlg.xaml.cs b/BrainSimulator/Modules/Vision/ModuleVisionDlg.xaml.cs
deleted file mode 100644
index 49a6414..0000000
--- a/BrainSimulator/Modules/Vision/ModuleVisionDlg.xaml.cs
+++ /dev/null
@@ -1,461 +0,0 @@
-/*
- * Brain Simulator Through
- *
- * Copyright (c) 2026 Charles Simon
- *
- * This file is part of Brain Simulator Through and is licensed under
- * the MIT License. You may use, copy, modify, merge, publish, distribute,
- * sublicense, and/or sell copies of this software under the terms of
- * the MIT License.
- *
- * See the LICENSE file in the project root for full license information.
- */
-//
-// PROPRIETARY AND CONFIDENTIAL
-// Brain Simulator 3 v.1.0
-// © 2022 FutureAI, Inc., all rights reserved
-//
-
-using System;
-using System.Collections.Generic;
-using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Media;
-using System.Windows.Input;
-using System.Windows.Shapes;
-using static System.Math;
-using System.Windows.Threading;
-using Microsoft.VisualBasic;
-using static BrainSimulator.Modules.ModuleVision;
-
-
-namespace BrainSimulator.Modules
-{
- public partial class ModuleVisionDlg : ModuleBaseDlg
- {
- // Constructor of the ModuleUKSStatement dialog
- public ModuleVisionDlg()
- {
- InitializeComponent();
- }
-
- // Draw gets called to draw the dialog when it needs refreshing
- int scale;
- public override bool Draw(bool checkDrawTimer)
- {
- if (!base.Draw(checkDrawTimer)) return false;
-
- ModuleVision parent = (ModuleVision)base.ParentModule;
-
- if (parent.imageArray == null) return false;
- try
- {
- labelProperties.Content = "Image: " + parent.imageArray.GetLength(0) + "x" + parent.imageArray.GetLength(1) +
- // "\r\nBit Depth: " + parent.bitmap.Format.BitsPerPixel +
- "\r\nSegments: " + parent.segments?.Count +
- "\r\nCorners: " + parent.corners?.Count +
- "\r\nOutlines: " + parent.theUKS.Labeled("Outline")?.Children.Count;
- }
- catch { return false; }
-
- theCanvas.Children.Clear();
-
- scale = (int)(theCanvas.ActualHeight / parent.imageArray.GetLength(1));
- int pixelSize = scale - 2;
- if (pixelSize < 2) pixelSize = 2;
-
- try
- {
- //draw the pixels
- if (cbShowPixels.IsChecked == true && parent.imageArray != null)
- {
- for (int x = 0; x < parent.imageArray.GetLength(0); x++)
- for (int y = 0; y < parent.imageArray.GetLength(1); y++)
- {
- var pixel = parent.imageArray[x, y];
- var s = pixel.ToString();
- if (pixel.ToString() != "#01FFFFFF")
- { }
- pixel.A = 255;
-
- if (pixel != null)
- {
- //pixel.luminance /= 2;
- SolidColorBrush b = new SolidColorBrush(pixel);
- float lum = new HSLColor(pixel).luminance;
- Rectangle e = new()
- {
- Height = pixelSize,
- Width = pixelSize,
- Stroke = b,
- Fill = b,
- ToolTip = new System.Windows.Controls.ToolTip { HorizontalOffset = 50, Content = $"({(int)x},{(int)y}) {lum.ToString("0.00")}" },
- };
- Canvas.SetLeft(e, x * scale - pixelSize / 2);
- Canvas.SetTop(e, y * scale - pixelSize / 2);
- theCanvas.Children.Add(e);
- }
- }
- }
-
- //draw the strokes
- if (cbShowSrokes.IsChecked == true && parent.strokePoints != null)
- {
- foreach (var pt in parent.strokePoints)
- {
- Rectangle e = new()
- {
- Height = pixelSize / 2,
- Width = pixelSize / 2,
- Stroke = Brushes.DarkGreen,
- Fill = Brushes.DarkGreen,
- ToolTip = new System.Windows.Controls.ToolTip
- {
- HorizontalOffset = 100,
- Content = $"({pt.X.ToString("0.0")},{pt.Y.ToString("0.0")})"
- },
- };
- Canvas.SetLeft(e, pt.X * scale - pixelSize / 4);
- Canvas.SetTop(e, pt.Y * scale - pixelSize / 4);
- theCanvas.Children.Add(e);
- }
- }
- //draw the strokes
- if (cbShowBoundaries.IsChecked == true && parent.boundaryPoints != null)
- {
- foreach (var pt in parent.boundaryPoints)
- {
- Rectangle e = new()
- {
- Height = pixelSize / 2,
- Width = pixelSize / 2,
- Stroke = Brushes.Blue,
- Fill = Brushes.Blue,
- ToolTip = new System.Windows.Controls.ToolTip
- {
- HorizontalOffset = 70,
- Content = $"({pt.X.ToString("0.0")},{pt.Y.ToString("0.0")})"
- },
- };
- Canvas.SetLeft(e, pt.X * scale - pixelSize / 4);
- Canvas.SetTop(e, pt.Y * scale - pixelSize / 4);
- theCanvas.Children.Add(e);
- }
- }
-
-
- //draw the segments
- if (cbShowSegments.IsChecked == true && parent.segments is not null & parent.segments?.Count > 0)
- {
- for (int i = 0; i < parent.segments.Count; i++)
- {
- Segment segment = parent.segments[i];
- Line l = new Line()
- {
- X1 = segment.P1.X * scale,
- X2 = segment.P2.X * scale,
- Y1 = segment.P1.Y * scale,
- Y2 = segment.P2.Y * scale,
- Stroke = Brushes.Red,
- StrokeThickness = 8,
- Opacity = .5,
- ToolTip = new System.Windows.Controls.ToolTip
- {
- Content = $"{segment.debugIndex}:({segment.P1.X.ToString("0.0")},{segment.P1.Y.ToString("0.0")}) - " +
- $"-({segment.P2.X.ToString("0.0")},{segment.P2.Y.ToString("0,0")})"
- },
- };
- theCanvas.Children.Add(l);
- }
- }
-
- //draw the corners
- if (cbShowCorners.IsChecked == true && parent.corners != null && parent.corners.Count > 0)
- {
- for (int i = 0; i < parent.corners.Count; i++)
- {
- var corner = parent.corners[i];
- float size = 15;
- Brush b = Brushes.LightBlue;
- if (Abs(corner.angle.Degrees - 180) < .1 ||
- Abs(corner.angle.Degrees - -180) < .1)
- b = Brushes.Pink;
- Ellipse e = new Ellipse()
- {
- Height = size,
- Width = size,
- Stroke = b,
- Fill = b,
- ToolTip = new System.Windows.Controls.ToolTip { HorizontalOffset = 100, Content = $"{i}", },
- };
- Canvas.SetTop(e, corner.pt.Y * scale - size / 2);
- Canvas.SetLeft(e, corner.pt.X * scale - size / 2);
- theCanvas.Children.Add(e);
-
- //test out drawing little lines to represent the angle (then an elliptical arc, soon)
- int i1 = 3;
- PointPlus delta = corner.prevPt - corner.pt;
- delta.R = i1;
- PointPlus pt1 = corner.pt + delta;
-
- if (!corner.curve)
- {
- Line l = new Line()
- {
- X1 = corner.pt.X * scale,
- Y1 = corner.pt.Y * scale,
- X2 = corner.prevPt.X * scale,
- Y2 = corner.prevPt.Y * scale,
- Stroke = Brushes.DarkGray,
- StrokeThickness = 2,
- };
- theCanvas.Children.Add(l);
-
- delta = corner.nextPt - corner.pt;
- delta.R = i1;
- PointPlus pt2 = corner.pt + delta;
-
- l = new Line()
- {
- X1 = corner.pt.X * scale,
- Y1 = corner.pt.Y * scale,
- X2 = corner.nextPt.X * scale,
- Y2 = corner.nextPt.Y * scale,
- Stroke = Brushes.DarkGray,
- StrokeThickness = 2,
- };
- theCanvas.Children.Add(l);
- }
- if (corner is ModuleVision.Arc a)
- {
- Corner alreadyInList = parent.corners.FindFirst(x =>
- x != corner && x.curve &&
- ((x.nextPt - corner.nextPt).R < 3.5 ||
- (x.prevPt - corner.nextPt).R < 3.5));
- if (alreadyInList == null)
- {
- e = new Ellipse()
- {
- Height = size,
- Width = size,
- Stroke = b,
- Fill = Brushes.Pink,
- };
- Canvas.SetTop(e, corner.nextPt.Y * scale - size / 2);
- Canvas.SetLeft(e, corner.nextPt.X * scale - size / 2);
- theCanvas.Children.Add(e);
- }
- alreadyInList = parent.corners.FindFirst(x =>
- x != corner && x.curve &&
- ((x.nextPt - corner.prevPt).R < 3.5 ||
- (x.prevPt - corner.prevPt).R < 3.5));
- if (alreadyInList == null)
- {
- e = new Ellipse()
- {
- Height = size,
- Width = size,
- Stroke = b,
- Fill = Brushes.Pink,
- ToolTip = new System.Windows.Controls.ToolTip { HorizontalOffset = 100, Content = $"{i}", },
- };
- Canvas.SetTop(e, corner.prevPt.Y * scale - size / 2);
- Canvas.SetLeft(e, corner.prevPt.X * scale - size / 2);
- theCanvas.Children.Add(e);
- }
-
- var cir = a.GetCircleFromThreePoints(corner.pt * scale, corner.nextPt * scale, corner.prevPt * scale);
- Angle startAngle = (corner.prevPt * scale - cir.center).Theta.Normalize();
- Angle midAngle = (corner.pt * scale - cir.center).Theta.Normalize();
- Angle endAngle = (corner.nextPt * scale - cir.center).Theta.Normalize();
- if ((startAngle < midAngle && endAngle < midAngle) ||
- (startAngle > midAngle && endAngle > midAngle))
- {
- if (endAngle < startAngle)
- endAngle += 2 * PI;
- else
- (endAngle, startAngle) = (startAngle, endAngle);
- }
- else if (endAngle < startAngle)
- {
- (startAngle, endAngle) = (endAngle, startAngle);
- }
- var path1 = DrawArc(cir.center, cir.radius, startAngle, endAngle);
- if (path1 != null)
- theCanvas.Children.Add(path1);
- }
- }
- }
- }
- catch { }
- return true;
- }
-
-
- public Polyline DrawArc(PointPlus center, float radius, Angle startAngle, Angle endAngle)
- {
- Angle angleStep = Angle.FromDegrees(1);
- Polyline poly = new Polyline()
- {
- Stroke = Brushes.Blue,
- StrokeThickness = 3,
- };
- if (endAngle < startAngle)
- {
- endAngle += 2 * PI;
- }
- for (Angle a = startAngle; a <= endAngle; a += angleStep)
- {
- PointPlus pp = new(radius, a);
- poly.Points.Add(center + pp);
- }
- return poly;
- }
-
- string defaultDirectory = "";
- private void Button_Browse_Click(object sender, RoutedEventArgs e)
- {
- if (defaultDirectory == "")
- {
- defaultDirectory = System.IO.Path.GetDirectoryName(MainWindow.currentFileName);
- }
- System.Windows.Forms.OpenFileDialog openFileDialog1 = new System.Windows.Forms.OpenFileDialog
- {
- Filter = "Image Files| *.png;*.jpg",
- Title = "Select an image file",
- Multiselect = true,
- InitialDirectory = defaultDirectory,
- };
- // Show the Dialog.
- // If the user clicked OK in the dialog
- System.Windows.Forms.DialogResult result = openFileDialog1.ShowDialog();
- if (result == System.Windows.Forms.DialogResult.OK)
- {
- defaultDirectory = System.IO.Path.GetDirectoryName(openFileDialog1.FileName);
- ModuleVision parent = (ModuleVision)base.ParentModule;
-
- textBoxPath.Text = openFileDialog1.FileName;
- List fileList;
- string curPath;
- if (openFileDialog1.FileNames.Length > 1)
- {
- fileList = new List(openFileDialog1.FileNames);
- curPath = fileList[0];
- }
- else
- {
- fileList = GetFileList(openFileDialog1.FileName);
- curPath = openFileDialog1.FileName;
- }
- //parent.previousFilePath = "";
- parent.currentFilePath = curPath;
- //parent.SetParameters(fileList, curPath, (bool)cbAutoCycle.IsChecked, (bool)cbNameIsDescription.IsChecked);
- }
- }
-
- private List GetFileList(string filePath)
- {
- //"using System.IO" conflicts with graphics
- System.IO.SearchOption subFolder = System.IO.SearchOption.AllDirectories;
- //if (!(bool)cbSubFolders.IsChecked)
- // subFolder = SearchOption.TopDirectoryOnly;
- string dir = filePath;
- System.IO.FileAttributes attr = System.IO.File.GetAttributes(filePath);
- if ((attr & System.IO.FileAttributes.Directory) != System.IO.FileAttributes.Directory)
- dir = System.IO.Path.GetDirectoryName(filePath);
- return new List(System.IO.Directory.EnumerateFiles(dir, "*.png", subFolder));
- }
-
- private void cb_Checked(object sender, RoutedEventArgs e)
- {
- if (sender is CheckBox cb)
- {
- ModuleVision parent = (ModuleVision)base.ParentModule;
- if (parent == null) return;
- bool cbState = cb.IsChecked == true;
- switch (cb.Content)
- {
- case "Horiz": parent.horizScan = cbState; parent.previousFilePath = ""; break;
- case "Vert": parent.vertScan = cbState; parent.previousFilePath = ""; break;
- case "45": parent.fortyFiveScan = cbState; parent.previousFilePath = ""; break;
- case "-45": parent.minusFortyFiveScan = cbState; parent.previousFilePath = ""; break;
- }
- }
- Draw(false);
- }
-
- private void Button_Click(object sender, RoutedEventArgs e)
- {
- ModuleVision parent = (ModuleVision)base.ParentModule;
- parent.previousFilePath = "";
- }
-
- private void ModuleBaseDlg_SizeChanged(object sender, SizeChangedEventArgs e)
- {
- Draw(true);
- }
-
- private void ModuleBaseDlg_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
- {
- if (e.Key == Key.R || e.Key == Key.F5)
- Button_Click(null, null);
- }
-
- private void ModuleBaseDlg_MouseWheel(object sender, MouseWheelEventArgs e)
- {
- //TODO, make bitmapsize a variable here
- //TODO, at max scale, do not change offsetX,Y
- ModuleVision parent = (ModuleVision)base.ParentModule;
- parent.scale *= 1 + e.Delta / 1000f;
-
- parent.offsetX = +25 + (int)((parent.offsetX - 25) * (1 + e.Delta / 1000f));
- parent.offsetY = +25 + (int)((parent.offsetY - 25) * (1 + e.Delta / 1000f));
- if (parent.scale < 1) parent.scale = 1;
- parent.LoadImageFileToPixelArray(parent.currentFilePath);
-
- ResetTimer();
- }
-
- private Point prevPoint;
- private void ModuleBaseDlg_MouseMove(object sender, MouseEventArgs e)
- {
- Point pos = e.GetPosition(this);
- if (e.LeftButton == MouseButtonState.Pressed)
- {
- if (prevPoint == null)
- prevPoint = new();
- else
- {
- Point diff = pos - (Vector)prevPoint;
- ModuleVision parent = (ModuleVision)base.ParentModule;
- parent.offsetX += (int)diff.X / 5;
- parent.offsetY += (int)diff.Y / 5;
-
- parent.LoadImageFileToPixelArray(parent.currentFilePath);
- ResetTimer();
- }
- }
- prevPoint = pos;
- }
-
- private DispatcherTimer refreshTimer = null;
- private void ResetTimer()
- {
- if (refreshTimer == null)
- {
- refreshTimer = new DispatcherTimer();
- refreshTimer.Tick += RefreshTimer_Tick;
- refreshTimer.Interval = TimeSpan.FromMilliseconds(500);
- }
- refreshTimer.Stop();
- refreshTimer.Start();
- }
- private void RefreshTimer_Tick(object sender, EventArgs e)
- {
- refreshTimer.Stop();
- ModuleVision parent = (ModuleVision)base.ParentModule;
- parent.previousFilePath = "";
- }
-
- }
-}
diff --git a/BrainSimulator/Modules/Vision/ModuleVisionFindSegmentsAndArcs.cs b/BrainSimulator/Modules/Vision/ModuleVisionFindSegmentsAndArcs.cs
deleted file mode 100644
index 2927222..0000000
--- a/BrainSimulator/Modules/Vision/ModuleVisionFindSegmentsAndArcs.cs
+++ /dev/null
@@ -1,1193 +0,0 @@
-/*
- * Brain Simulator Through
- *
- * Copyright (c) 2026 Charles Simon
- *
- * This file is part of Brain Simulator Through and is licensed under
- * the MIT License. You may use, copy, modify, merge, publish, distribute,
- * sublicense, and/or sell copies of this software under the terms of
- * the MIT License.
- *
- * See the LICENSE file in the project root for full license information.
- */
-using System.Collections.Generic;
-using System.Windows;
-using System.Linq;
-using static System.Math;
-using System.Threading;
-
-
-namespace BrainSimulator.Modules;
-
-//public class HoughTransform
-public partial class ModuleVision
-{
- //This is currently used for STROKES while BOUNDARIES are handled by FindSegments (below)
- public void FindArcsAndSegments(List strokePts)
- {
- corners = new();
- segments = new List();
- List availablePts = [.. strokePts];
-
- //find all the contiguous sets of points in the boundary array
- List> curves = new();
- while (availablePts.Count > 0)
- {
- List contigPts = new();
- contigPts.Add(availablePts[0]);
- availablePts.RemoveAt(0);
- //find the contiguout points
- for (int curPt = 0; curPt < contigPts.Count; curPt++)
- {
- PointPlus testPt = contigPts[curPt];
- var nearbyPts = GetNearbyPointsSorted(testPt, 2.5f, availablePts);
- foreach (var ptInSeq in nearbyPts)
- {
- if (contigPts.Contains(ptInSeq.pt)) continue;
- contigPts.Add(ptInSeq.pt);
- availablePts.Remove(ptInSeq.pt);
- }
- }
- //order the continuous points
- List orderedPts = new();
- PointPlus currentPoint = contigPts.First();
- orderedPts.Add(currentPoint);
- contigPts.Remove(currentPoint);
- while (contigPts.Count > 0)
- {
- var nearest = contigPts.OrderBy(x => (currentPoint - x).R).First();
- float dist = (nearest - currentPoint).R;
- if (dist > 3)
- {
- curves.Add(orderedPts);
- orderedPts = new();
- }
- orderedPts.Add(nearest);
- currentPoint = nearest;
- contigPts.Remove(nearest);
- }
- curves.Add(orderedPts);
- }
-
-
- //find arc and straight segments in the curves
- while (curves.Count > 0)
- {
- MergeCurvePointLists(curves);
- List theCurve = curves[0];
- if (theCurve.Count < 4) //give up on short segments
- {
- curves.RemoveAt(0);
- continue;
- }
-
- //use sliding window to find straight or curved segments
- //start with the largest window and make it smaller until you find a recognizeable subset
- //length has to be at least 4 to know if it's a curve..the two innter angles must be the same sign
- for (int length = theCurve.Count; length > 3; length--)
- {
- //now slide the window through the curve
- for (int start = 0; start <= theCurve.Count - length; start++)
- {
- List windowPoints = theCurve.GetRange(start, length);
- //is this is not a contigouous set of points, continue
- bool contiguous = true;
- for (int i = 0; i < windowPoints.Count - 1; i++)
- if ((windowPoints[i] - windowPoints[i + 1]).R > 3.5)
- { contiguous = false; break; }
- if (!contiguous)
- continue;
-
- //is the set of points a defineable curve or straight line
- bool isCurve = false;
- bool isStraight = false;
-
- List theAngles = new List();
- List theDeltas = new List();
- for (int i = 0; i < windowPoints.Count - 1; i++)
- {
- Angle dTheta = (windowPoints[i] - windowPoints[i + 1]).Theta;
- //if (dTheta < 0) dTheta += Angle.FromDegrees(360);
- theAngles.Add(dTheta);
- if (theAngles.Count > 1)
- {
- Angle ddTheta = theAngles.Last() - theAngles[theAngles.Count - 2];
- if (ddTheta > Angle.FromDegrees(180))
- ddTheta = Angle.FromDegrees(360) - ddTheta;
- if (ddTheta < Angle.FromDegrees(-180))
- ddTheta = Angle.FromDegrees(-360) - ddTheta;
- theDeltas.Add(ddTheta);
- if (Abs(ddTheta) > Angle.FromDegrees(180))
- { }
- }
- }
-
- isCurve = IsCurved(theDeltas, Angle.FromDegrees(35));
-
- if (!isCurve)
- isStraight = IsLine(windowPoints, 0.5f);
-
- if (isStraight || isCurve)
- {
- if (isCurve)
- {
- //add the arc to the segment list
- //put the corner of the arc at the midpoint of the arc
- //which is either the center pt if the count is odd
- //OR the midpoint pf the two center points if the count is even
- PointPlus p1;
- if ((length % 2) != 0)
- p1 = new PointPlus(windowPoints[length / 2]);
- else
- p1 = new PointPlus(new Segment(windowPoints[length / 2 - 1], windowPoints[length / 2]).MidPoint);
- Arc newCorner = new()
- {
- curve = true,
- pt = p1,
- prevPt = theCurve[start],
- nextPt = theCurve[start + length - 1]
- };
- corners.Add(newCorner);
- }
- else if (isStraight)
- {
- //add the segment to the segment list
- PointPlus p1 = theCurve[start];
- PointPlus p2 = theCurve[start + length - 1];
- segments.Add(new Segment(p1, p2));
- }
- //remove the segment/arc from the curve
- //duplicate the last point so it can be included in the next arc/segment
- List newCurve = theCurve.GetRange(start + length - 1, theCurve.Count - (start + length - 1));
- curves[0] = curves[0].GetRange(0, start + 1);
- curves.Add(newCurve);
- goto startAgain;
- }
- }
- }
- //no curve or straight segments found
- curves.RemoveAt(0);
- startAgain: continue;
- }
- JoinUpArcsAndSegments();
- return;
- }
-
- private void JoinUpArcsAndSegments()
- {
- //some arcs are discovered as short segments and some are broken into multiple arcs
- //this method intends to merge these
- //return;
- float threshold = 2;
- //do pairs of segments form a (possibly) curved angle
- for (int i = 0; i < segments.Count; i++)
- {
- //pick a segment
- Segment sg1 = segments[i];
- if (sg1.Length > 6) continue;
- //does this segment abut a curve?
- for (int j = 0; j < corners.Count; j++)
- {
- if (corners[j] is Arc a)
- {
- if ((sg1.P1 - a.prevPt).R < threshold &&
- Abs(sg1.Angle - (a.prevPt - a.pt).Theta) < Angle.FromDegrees(40))
- {
- corners.Add(new Arc { pt = sg1.P1, prevPt = sg1.P2, nextPt = a.nextPt, });
- corners.RemoveAt(j);
- goto segmentMergedToArc;
- }
- else if ((sg1.P1 - a.nextPt).R < threshold &&
- Abs(sg1.Angle - (a.nextPt - a.pt).Theta) < Angle.FromDegrees(40))
- {
- corners.Add(new Arc { pt = sg1.P1, prevPt = sg1.P2, nextPt = a.prevPt, });
- corners.RemoveAt(j);
- goto segmentMergedToArc;
- }
- else if ((sg1.P2 - a.prevPt).R < threshold &&
- Abs(sg1.Angle - (a.prevPt - a.pt).Theta) < Angle.FromDegrees(40))
- {
- corners.Add(new Arc { pt = sg1.P2, prevPt = sg1.P1, nextPt = a.nextPt, });
- corners.RemoveAt(j);
- goto segmentMergedToArc;
- }
- else if ((sg1.P2 - a.nextPt).R < threshold &&
- Abs(sg1.Angle - (a.nextPt - a.pt).Theta) < Angle.FromDegrees(40))
- {
- corners.Add(new Arc { pt = sg1.P2, prevPt = sg1.P1, nextPt = a.prevPt, });
- corners.RemoveAt(j);
- goto segmentMergedToArc;
- }
- }
- }
- //does this abut another segment forming a curve?
- for (int j = i + 1; j < segments.Count; j++)
- {
- Segment sg2 = segments[j];
- if (sg2.Length > 6) continue;
- if (Abs(sg1.Angle - sg2.Angle) > Angle.FromDegrees(60))
- continue;
- //do they nearly meet up?
- if ((sg1.P2 - sg2.P1).R < threshold)
- {
- corners.Add(new Arc
- {
- pt = sg1.P2,
- prevPt = sg1.P1,
- nextPt = sg2.P2,
- });
- goto twoSegmentsFormArc;
- }
- if ((sg1.P1 - sg2.P1).R < threshold)
- {
- corners.Add(new Arc
- {
- pt = sg1.P1,
- prevPt = sg1.P2,
- nextPt = sg2.P2,
- });
- goto twoSegmentsFormArc;
- }
- if ((sg1.P1 - sg2.P2).R < threshold)
- {
- corners.Add(new Arc
- {
- pt = sg1.P1,
- prevPt = sg1.P2,
- nextPt = sg2.P1,
- });
- goto twoSegmentsFormArc;
- }
- if ((sg1.P2 - sg2.P2).R < threshold)
- {
- corners.Add(new Arc
- {
- pt = sg1.P2,
- prevPt = sg1.P1,
- nextPt = sg2.P1,
- });
- goto twoSegmentsFormArc;
- }
- continue;
- twoSegmentsFormArc:
- segments.RemoveAt(j);
- segments.RemoveAt(i);
- j--;
- if (i > 0)
- i--;
- }
- continue;
- segmentMergedToArc:
- segments.RemoveAt(i);
- if (i > 0)
- i--;
- }
-
- for (int i = 0; i < corners.Count - 1; i++)
- {
- for (int j = i + 1; j < corners.Count; j++)
- {
- //do they have the same curvature?
- Arc arc1 = (Arc)corners[i];
- Arc arc2 = (Arc)corners[j];
- var cir1 = arc1.GetCircleFromThreePoints(arc1.pt, arc1.nextPt, arc1.prevPt);
- var cir2 = arc2.GetCircleFromThreePoints(arc2.pt, arc2.nextPt, arc2.prevPt);
- if ((cir1.center - cir2.center).R > 3) continue;
- if (Abs(cir1.radius - cir2.radius) > 3) continue;
- //do they nearly meet up?
- PointPlus s1 = arc1.prevPt;
- PointPlus e1 = arc1.nextPt;
- PointPlus s2 = arc2.prevPt;
- PointPlus e2 = arc2.nextPt;
- if ((e1 - s2).R < threshold)
- {
- Arc newCorner = new Arc()
- {
- prevPt = s1,
- nextPt = e2,
- pt = e1,
- };
- corners[i] = newCorner;
- corners.RemoveAt(j);
- j--;
- }
- }
- }
- }
- private void MergeCurvePointLists(List> curves)
- {
- float threshold = 2.5f;
- for (int i = 0; i < curves.Count - 1; i++)
- {
- if (curves[i].Count == 0) continue;
- for (int j = i + 1; j < curves.Count; j++)
- {
- if (curves[j].Count == 0) continue;
- PointPlus s1 = curves[i].First();
- PointPlus e1 = curves[i].Last();
- PointPlus s2 = curves[j].First();
- PointPlus e2 = curves[j].Last();
- if ((e1 - s2).R < threshold)
- {
- curves[i].InsertRange(curves[i].Count, curves[j]);
- curves.RemoveAt(j);
- j--;
- }
- else if ((s1 - e2).R < threshold)
- {
- curves[i].InsertRange(0, curves[j]);
- curves.RemoveAt(j);
- j--;
- }
- else if ((s1 - s2).R < threshold)
- {
- curves[i].Reverse();
- curves[i].InsertRange(curves[i].Count, curves[j]);
- curves.RemoveAt(j);
- j--;
- }
- else if ((e1 - e2).R < threshold)
- {
- curves[j].Reverse();
- curves[i].InsertRange(curves[i].Count, curves[j]);
- curves.RemoveAt(j);
- j--;
- }
- }
- }
- }
-
- private static bool IsCurved(List dAngles, Angle threshold)
- {
- bool mayBeCurveLeft = true;
- bool mayBeCurveRight = true;
- for (int i = 0; i < dAngles.Count; i++)
- {
- Angle a = dAngles[i];
- if (!mayBeCurveLeft && !mayBeCurveRight) break;
- if (mayBeCurveRight && (a < Angle.FromDegrees(1) || a > threshold))
- mayBeCurveRight = false;
- if (mayBeCurveLeft && (a > Angle.FromDegrees(-1) || a < -threshold))
- mayBeCurveLeft = false;
- }
- return mayBeCurveLeft || mayBeCurveRight;
- }
- static bool IsLine(List points, double threshold)
- {
- float threshold1 = (points.First() - points.Last()).R / 3f;
- PointPlus pFirst = points[0];
- PointPlus pLast = points.Last();
- int count0 = 0;
- int count1 = 0;
- int count2 = 0;
- for (int i = 1; i < points.Count - 1; i++)
- {
- PointPlus curPt = points[i];
- float dist = Utils.DistancePointToLine(curPt, pFirst, pLast);
- if (dist > threshold)
- return false;
-
- //which side of the line is this point on?
- float d = (pLast.X - pFirst.X) * (curPt.Y - pFirst.Y) - (pLast.Y - pFirst.Y) * (curPt.X - pFirst.X);
- if (d > threshold1)
- count0++;
- else if (d < -threshold1)
- count1++;
- else
- count2++;
- }
- if (count0 == 0 ^ count1 == 0) //XOR operator, are all the points on one side? then more likely a curve
- return false;
- return true;
- }
-
-
-
- public List FindSegments(List boundaries)
- {
- boundaryPoints = boundaries;
- boundaryPoints = boundaryPoints.OrderBy(pt => pt.X + pt.Y * 1000).ToList();
- List segments = new List();
-
- List availablePointList = new List();
- //copy the list of boundarypoints to a temp list
- foreach (var pt in boundaryPoints)
- availablePointList.Add(pt);
-
- //Angle minAngle = Angle.FromDegrees(-90);
- List anglesAlreadyFound = new List();
-
- //pick a boundary point from the temp list.
- while (availablePointList.Count > 0)
- {
- //find the best segment through/near that point
- PointPlus ptToTest = availablePointList[0];
- //anglesAlreadyFound = new();
- Segment s1 = BestSegmentThroughPoint(ptToTest, availablePointList, anglesAlreadyFound);
-
- //if there is no segment at least 3 pixels long, remove the test point from the temp list, continue
- if (s1 is null || s1.Length < 2 || !AddSegmentToList(segments, s1))
- {
- availablePointList.RemoveAt(0);
- anglesAlreadyFound = new();
- continue;
- }
-
- //a segment has been added
-
- //what points are actually on the segment
- //delete them from the available point list
- for (int i = 0; i < availablePointList.Count; i++)
- {
- PointPlus pt = availablePointList[i];
-
- //don't delete the endpoints as there are likely to be additional lines at the corners
- if (pt.Near(s1.P1, 0.2f) || pt.Near(s1.P2, 0.2f))
- continue;
-
- float dist = Utils.DistancePointToSegment(s1, pt);
- if (Abs(dist) < .5)
- {
- availablePointList.RemoveAt(i);
- i--;
- }
- }
- //loop until the available point list is empty
- }
- return segments;
- }
-
- Angle angleStep = Angle.FromDegrees(5);
- private Segment BestSegmentThroughPoint(PointPlus pt, List availablePoints, List alreadyFound)
- {
-
- angleStep = Angle.FromDegrees(1);
- Segment bestSegment = new(pt, pt);
- Angle bestAngle = -1;
-
- //find the amgle which results in the longed segment through the given point
- for (Angle a = Angle.FromDegrees(-90); a < Angle.FromDegrees(90); a += angleStep)
- {
- //don't search the same angle at the same point over and over
- if (alreadyFound.FindFirst(x => Abs(x - a) < Angle.FromDegrees(0.9f)) != null)
- continue;
-
- //at this angle, extend the segment in either direction until you run out of nearby boundary points
- PointPlus step = new((float)Cos(a), (float)Sin(a));
- PointPlus p1 = new(pt);
-
- float maxDist = Abs(step.X) + Abs(step.Y) + .1f;
- //maxDist = .8f;
- maxDist = .7f;
- //lengthen the first endpoint (if possible)
- float dist = -1;
- do
- {
- p1 += step;
- PointPlus nearest = GetNearestPointInList(p1, availablePoints);
- dist = (p1 - nearest).R;
- } while (dist < maxDist);
- p1 -= step;
-
- //now the second endpoint
- PointPlus p2 = new(pt);
- dist = -1;
- do
- {
- p2 -= step;
- PointPlus nearest = GetNearestPointInList(p2, availablePoints);
- dist = (p2 - nearest).R;
- } while (dist < maxDist);
- p2 += step;
-
- //is this new segment better than others we've found at other angles
- Segment newSegment = new(p1, p2);
- if (newSegment.Length > bestSegment.Length)
- {
- bestSegment = newSegment;
- bestAngle = a;
- }
- }
-
- if (bestSegment.Length >= 2)
- alreadyFound.Add(bestAngle);
- // if (bestSegment.Length < 3)
- if (bestSegment.Length < 2)
- return bestSegment;
-
- //Here we have a ressonable segment hypothesis. The endpoints might not be exactly on boundary points
- //try out all combinations of nearby endpoints to see if there is an improvement
- //if you don't find any segment with actual endpoints
-
- bool improved = true;
- Segment best = null;
- var candidatePts = GetNearbyPoints(bestSegment.P1, 3f, boundaryPoints);
- var candidatePts1 = GetNearbyPoints(bestSegment.P2, 3f, boundaryPoints);
-
- //best is the best within the loop below, not to be confused with bestSegment from the loop above
- bestSegment = null;
- while (improved)// && bestSegment?.Length > 2)
- {
- improved = false;
- if (best is not null)
- {
- candidatePts = GetNearbyPoints(best.P1, 2f, boundaryPoints);
- candidatePts1 = GetNearbyPoints(best.P2, 2f, boundaryPoints);
- }
-
- //build a table of all possible candidate segments and their hit-rates
- var tempValues = new List<(int i, int j, float length, float error)>();
- for (int i = 0; i < candidatePts.Count; i++)
- {
- PointPlus pt1 = candidatePts[i];
- for (int j = 0; j < candidatePts1.Count; j++)
- {
- PointPlus pt2 = candidatePts1[j];
- if (pt1.Near(pt2, 2f)) continue;
- Segment testSeg = new Segment(pt1, pt2);
- float e1 = GetAverageError3(testSeg);
- tempValues.Add((i, j, testSeg.Length, e1));
- }
- }
-
- tempValues = tempValues.OrderBy(x => x.error).ToList();
-
- if (tempValues.Count == 0 || tempValues[0].error > .2f) return null;
-
- float maxValue = .1f;
- var limitValues = tempValues.FindAll(x => x.error < maxValue).ToList();
- while (limitValues.Count == 0)
- {
- maxValue += .1f;
- limitValues = tempValues.FindAll(x => x.error < maxValue).ToList();
- }
- var t = limitValues.FindFirst(x => x.length == limitValues.Max(x => x.length));
- best = new Segment(candidatePts[t.i], candidatePts1[t.j]);
-
- if (best != bestSegment && Abs(best.Angle - bestAngle) < Angle.FromDegrees(15))
- {
- bestSegment = best;
- improved = true;
- }
- }
-
- //has the algorithm strayed too far from the original point?
- if (bestSegment is not null && Utils.DistancePointToSegment(bestSegment, pt) > 1.5f)
- {
- //likely a curve
- //return null;
- }
-
- return bestSegment;
- }
-
-
- //add a new segment to the segment list IF it isn't within 1 of another segment
- private bool AddSegmentToList(List segs, Segment seg)
- {
- if (segs.FindFirst(x => (x.P1.Near(seg.P1, 1) && x.P2.Near(seg.P2, 1)) || (x.P1.Near(seg.P2, 1) && x.P2.Near(seg.P1, 1))) is null)
- {
- seg.debugIndex = segs.Count;
- if (segs.Count == 10)
- { }
- segs.Add(seg);
- return true;
- }
- else
- return false;
- }
- private PointPlus GetNearestPointInList(PointPlus pt, List points)
- {
- PointPlus nearest = new(-10, -10f);
- foreach (var pt1 in points)
- {
- float dist = (pt - pt1).R;
- if (dist < (pt - nearest).R)
- {
- nearest = pt1;
- }
- }
- return nearest;
- }
- private PointPlus GetNearestBoundaryPoint(PointPlus pt)
- {
- return GetNearestPointInList(pt, boundaryPoints);
- }
-
- bool PointInSegmentRange(Segment s, PointPlus pt)
- {
- float dy = Abs(s.P1.Y - s.P2.Y);
- float dx = Abs(s.P1.X - s.P2.X);
- if (dx > dy)
- {
- if (pt.X - s.P1.X > 0.001f && pt.X - s.P2.X > 0.001f) return false;
- if (pt.X - s.P1.X < -0.001f && pt.X - s.P2.X < -0.001f) return false;
- }
- else
- {
- if (pt.Y - s.P1.Y > 0.001f && pt.Y - s.P2.Y > 0.001f) return false;
- if (pt.Y - s.P1.Y < -0.001f && pt.Y - s.P2.Y < -0.001f) return false;
- }
- return true;
- }
- float GetAverageError3(Segment s)
- {
- List linePoints = new();
- float sum = 0;
- int count = 0;
- foreach (var pt in boundaryPoints)
- {
- if (!PointInSegmentRange(s, pt)) continue;
- float dist = Utils.DistancePointToSegment(s, pt);
- if (dist < 1f)
- {
- linePoints.Add(pt);
- sum += dist;
- count++;
- }
- }
-
-
- //return large numbers on illegal conditions
- //if (count < s.Length-1) average = 3;
-
- //count the number of gaps points in the segment
- float dx = s.P1.X - s.P2.X;
- float dy = s.P1.Y - s.P2.Y;
- int stepCount;
- if (Abs(dx) > Abs(dy))
- {
- stepCount = (int)Round(dx);
- dy /= dx;
- dx = 1;
- }
- else
- {
- stepCount = (int)Round(dy);
- dx /= dy;
- dy = 1;
- }
- PointPlus step = new(dx, dy);
- PointPlus currPt = s.P2;
- int misses = 0;
- for (int i = 0; i < stepCount; i++)
- {
- float dist = (GetNearestPointInList(currPt, linePoints) - currPt).R;
- if (dist > .5f)
- misses++;
- currPt += step;
- }
-
- if (s.Length > count)
- //if (s.Length < 10)
- sum += (s.Length - count) / 2; //dock 0.5 point for each missing point on segment
- float average = sum / count;
- if (misses > 1)
- average = 3;
- if (average < 0f)
- { }
- return average;
- }
-
- List GetBoundaryPointsOnSegment(Segment seg)
- {
- List retVal = new();
- foreach (var pt in boundaryPoints)
- {
- if (!PointInSegmentRange(seg, pt)) continue;
- float dist = Utils.DistancePointToSegment(seg, pt);
- if (dist < .5f)
- retVal.Add(pt);
- }
- OrderPointsAlongSegment(ref retVal);
- return retVal;
- }
-
- float GetSegmentWeight3(Segment s)
- {
- float retVal = 0;
- float sum = 0;
- int count = 0;
- foreach (var pt in boundaryPoints)
- {
- float dist = Utils.DistancePointToSegment(s, pt);
- if (dist < 1)
- {
- sum += dist;
- count++;
- retVal += 1 - dist;
- }
- }
- float average = sum / count;
- return retVal;
- }
- private List GetNearbyPoints(PointPlus pt, float dist, List availablePoints)
- {
- //this is exhaustive and can be replaced with a QuadTree for performance
- List points = new();
- foreach (PointPlus point in availablePoints)
- {
- float d1 = (point - pt).R;
- if (d1 <= dist)
- points.Add(point);
- }
- return points;
- }
- private List<(PointPlus pt, float dist, Angle theta)> GetNearbyPointsSorted(PointPlus pt, float dist, List availablePoints)
- {
- //this is exhaustive and can be replaced with a QuadTree for performance
- List<(PointPlus pt, float dist, Angle theta)> points = new();
- foreach (PointPlus point in availablePoints)
- {
- PointPlus p1 = point - pt;
-
- float d1 = p1.R;
- Angle a = p1.Theta;
- if (d1 <= dist)
- points.Add(new(point, d1, a));
- }
- points = points.OrderBy(x => x.theta * 1000 + x.dist).ToList();
- return points;
- }
- /*
- public List FindSegments2()
- {
- int maxRho = accumulator.GetLength(0);
- int maxTheta = accumulator.GetLength(1);
- List<(float weight, Segment s)> testSegments = new();
-
- for (int rhoIndex = 0; rhoIndex < maxRho; rhoIndex++)
- {
- for (int thetaIndex = 0; thetaIndex < maxTheta; thetaIndex++)
- {
- if (accumulator[rhoIndex, thetaIndex].Count < minVotes) continue;
- //if (rhoIndex != 99) continue;
- //if (thetaIndex > 92 || thetaIndex < 88) continue;
-
-
- Point p1, p2;
- float rho1 = rhoIndex - maxDistance;
-
- if (thetaIndex == 0) //special case for vertical lines
- {
- p1 = new(rho1, 0); p2 = new(rho1, 100);
- }
- else
- {
- double fTheta = thetaIndex * Math.PI / numAngles;
- double b = rho1 / Math.Sin(fTheta);
- double m = -Math.Cos(fTheta) / Math.Sin(fTheta);
- p1 = new(0, b); p2 = new(100, 100 * m + b);
- }
-
- // Calculate points on the line in the Cartesian coordinate system
- List<(float dist, Point pt)> linePoints = new();
- foreach (var pt in boundaryPoints)
- {
- float dist = Utils.DistancePointToLine2(pt, p1, p2);
- if (Abs(dist) < .8)
- {
- //if (dist <= .5) dist = 0;
- linePoints.Add((dist, pt));
- }
- }
- if (linePoints.Count < 7) continue;
- //find contiguous segments within the Line
- OrderPointsAlongSegment2(ref linePoints);
- int start = 0; //possible start of segment
- int last = linePoints.Count - 1;
- PointPlus startPt = new(linePoints[start].pt);
- PointPlus lastPt = new(linePoints.Last().pt);
-
- for (int i = 1; i < linePoints.Count; i++)
- {
- PointPlus currPt = new(linePoints[i].pt);
- PointPlus prevPt = new(linePoints[i - 1].pt);
- if ((prevPt - currPt).R <= 3) //allow for skipped pixels
- {
- //pts are near enough, keep going
- }
- else
- {
- //pts are not contiguous --add the previous section as a segment
- last = i - 1;
- OptimizeSegment(linePoints, ref start, ref last);
-
- if (last - start > minLength)
- {
- float confidence =
- GetConfidence(linePoints, start, last);
- if (confidence > minLength)
- AddTheSegment(testSegments, linePoints, confidence, linePoints[start].pt, linePoints[last].pt);
- }
- start = i;
- }
- }
- if (start < last - minLength)
- {
- last = linePoints.Count - 1;
- //shorten the segment to remove points which might be the beginning of another segment
- OptimizeSegment(linePoints, ref start, ref last);
-
- if (last - start > minLength)
- {
- float confidence = GetConfidence(linePoints, start, last);
- float err = GetAverageError(linePoints, start, last);
- if (err < 0.5 && confidence > minLength)
- AddTheSegment(testSegments, linePoints, confidence, linePoints[start].pt, linePoints[last].pt);
- }
- }
- }
- }
- //when we get here, we've found all the segments and assigned them a weight value based on their hit rate of boundary pixels
-
- testSegments = testSegments.OrderByDescending(x => x.weight).ToList();
-
- for (int i = 0; i < testSegments.Count - 1; i++)
- {
- for (int j = i + 1; j < testSegments.Count; j++)
- {
- if (testSegments[i].s.debugIndex == 907)
- { }
- if (testSegments[j].s.debugIndex == 907)
- { }
- if (MergeSegments(testSegments[i], testSegments[j], out var sNew))
- {
- testSegments[i] = sNew;
- testSegments.RemoveAt(j);
- j = i; //having changed the {i} entry, restart this inner loop
- }
- }
- }
- foreach (var testSegment in testSegments)
- {
- ExtendSegment(testSegment.s);
- }
-
- return testSegments.Select(x => x.s).ToList();
- }
-
- private void ExtendSegment(Segment s)
- {
- float weight = GetSegmentWeight3(s);
- Segment s2 = new Segment(Utils.ExtendSegment(s.P1, s.P2, 1, true), s.P2); ;
- float weight1 = GetSegmentWeight3(s2);
- while (weight1 > weight + 0.25f)
- {
- s.P1 = s2.P1;
- s2 = new Segment(Utils.ExtendSegment(s.P1, s.P2, 1, true), s.P2);
- weight = weight1;
- weight1 = GetSegmentWeight3(s2);
- }
- s2 = new Segment(s.P1, Utils.ExtendSegment(s.P1, s.P2, 1, false));
- weight1 = GetSegmentWeight3(s2);
- while (weight1 > weight + 0.25f)
- {
- s.P2 = s2.P2;
- s2 = new Segment(s.P1, Utils.ExtendSegment(s.P1, s.P2, 1, false));
- weight = weight1;
- weight1 = GetSegmentWeight3(s2);
- }
- }
-
- private static void AddTheSegment(List<(float weight, Segment s)> testSegments, List<(float dist, Point pt)> linePoints, float confidence, PointPlus startPt, PointPlus lastPt)
- {
- //build the new segment
- Segment s2 = new Segment(startPt, lastPt);
- if (startPt.Y > lastPt.Y)
- s2 = new Segment(lastPt, startPt);
- s2.debugIndex = testSegments.Count;
-
-
- //only add the segment if it's not already there
- if (testSegments.FindFirst(x => x.s.P1 == startPt && x.s.P2 == lastPt) == default)
- {
- if (testSegments.Count == 1005)
- { }
- testSegments.Add((confidence, s2));
- }
- }
-
- private static void OptimizeSegment2(List linePoints, ref int start, ref int last)
- {
- //try contracting the segment by a few pts on each end to see if it matches better
- float currWeight = GetAverageError2(linePoints, start, last);
- if (currWeight < 0.1) return;
-
- int bestStart = start;
- int bestLast = last;
- float bestWeight = currWeight;
- for (int i = 0; i < 5; i++)
- for (int j = 0; j < 5; j++)
- {
- if ((last - j) - (start + i) < 3) //don't let the segment get smaller than 3
- continue;
- float testWeight = GetAverageError2(linePoints, start + i, last - j);
- if (testWeight < bestWeight)
- {
- bestWeight = testWeight;
- bestLast = last - j;
- bestStart = start + i;
- }
- }
- start = bestStart;
- last = bestLast;
- }
- private static void OptimizeSegment(List<(float dist, Point pt)> linePoints, ref int start, ref int last)
- {
- //try contracting the segment by a few pts on each end to see if it matches better
- float currWeight = GetAverageError(linePoints, start, last);
- for (int i = 0; i < 5; i++)
- {
- if (last - start < 3) break;
- float testWeight = GetAverageError(linePoints, start + 1, last);
- if (testWeight < currWeight)
- {
- start += 1;
- currWeight = testWeight;
- }
- testWeight = GetAverageError(linePoints, start, last - 1);
- if (testWeight < currWeight)
- {
- last -= 1;
- currWeight = testWeight;
- }
- testWeight = GetAverageError(linePoints, start + 1, last - 1);
- if (testWeight < currWeight)
- {
- last -= 1;
- currWeight = testWeight;
- }
- }
- }
-
- private static float GetAverageError2(List linePoints, int start, int last)
- {
- float aveError = 0;
- Segment s = new Segment(linePoints[start], linePoints[last]);
- for (int j = start; j <= last; j++)
- {
- float dist = Utils.DistancePointToSegment(s, linePoints[j]);
- //if (dist > 0.5) dist = 1;
- aveError += dist;
- }
- aveError /= (last - start + 1);
- return aveError;
- }
- private static float GetAverageError(List<(float dist, Point pt)> linePoints, int start, int last)
- {
- float aveError = 0;
- Segment s = new Segment(linePoints[start].pt, linePoints[last].pt);
- for (int j = start; j <= last; j++)
- {
- float dist = Utils.DistancePointToSegment(s, linePoints[j].pt);
- if (dist > 0.5) dist = 1;
- aveError += dist;
- }
- aveError /= (last - start + 1);
- return aveError;
- }
- private static float GetConfidence(List<(float dist, Point pt)> linePoints, int start, int last)
- {
- float aveWeight = 0;
- Segment s = new Segment(linePoints[start].pt, linePoints[last].pt);
- for (int j = start; j <= last; j++)
- aveWeight += 1 - Utils.DistancePointToSegment(s, linePoints[j].pt);
- return aveWeight;
- }
-
-
- */
-
- public static void MergeSegments(List segments)
- {
- for (int i = 0; i < segments.Count - 1; i++)
- for (int j = i + 1; j < segments.Count; j++)
- {
- if (Merge2Segments(segments[i], segments[j], out Segment sNew))
- {
- segments[i] = sNew;
- segments.RemoveAt(j);
- }
- }
- }
-
- //returns true if one of the segments is unnecessary and the first should be replaced by sNew
- static bool Merge2Segments(Segment s1, Segment s2, out Segment sNew)
- {
- sNew = s1;
-
- if (Utils.FindIntersection(s1, s2, out PointPlus intersection, out Angle a))
- {
-
- }
-
- //are the segments nearly the same angle?
- float angleDiff = Abs((s1.Angle - s2.Angle).Degrees);
- float minLength = (float)Min(s2.Length, s1.Length);
- float angleLimit = 15;
- if (minLength < 4) angleLimit = 20;
-
- if (angleDiff > angleLimit &&
- angleDiff < 180 - angleLimit ||
- angleDiff > 180 + angleLimit &&
- angleDiff < 360 - angleLimit) return false;
- //are the segments near each other?
- float d1 = Utils.DistanceBetweenTwoSegments(s1, s2);
- if (d1 > 5) return false;
-
- d1 = Utils.DistancePointToSegment(s1, s2.P1);
- float d2 = Utils.DistancePointToSegment(s1, s2.P2);
- float d3 = Utils.DistancePointToSegment(s2, s1.P1);
- float d4 = Utils.DistancePointToSegment(s2, s1.P2);
-
- //do the segments overlap?
- if (d1 < 2 || d2 < 2 || d3 < 2 || d4 < 2)
- {
- //do the segments extend one another?
- //mix and match endpoints go get segment with highest Weight
- PointPlus[] endPts = { new(s1.P1), new(s1.P2), new(s2.P1), new(s2.P2) };
- float maxDist = 0;
- for (int i = 0; i < 3; i++)
- {
- for (int j = i + 1; j < 4; j++)
- {
- if (endPts[i] == endPts[j]) continue;
- float dist = (new Segment(endPts[i], endPts[j])).Length;
- if (dist > maxDist)
- {
- sNew.P1 = endPts[i];
- sNew.P2 = endPts[j];
- maxDist = dist;
- }
- }
- }
- return true;
- }
- return false;
- }
-
- void OrderPointsAlongSegment2(ref List<(float dist, Point pt)> points)
- {
- float bestDist = 0;
- if (points.Count == 0) return;
- Point p1 = points[0].pt;
- Point p2 = points[0].pt;
- foreach (var pa in points)
- foreach (var pb in points)
- {
- float newDist = (float)(Sqrt((pa.pt.X - pb.pt.X) * (pa.pt.X - pb.pt.X) + (pa.pt.Y - pb.pt.Y) * (pa.pt.Y - pb.pt.Y)));
- if (newDist > bestDist)
- {
- bestDist = newDist;
- p1 = pa.pt;
- p2 = pb.pt;
- }
- }
-
- double dx = p2.X - p1.X;
- double dy = p2.Y - p1.Y;
- double dSquared = dx * dx + dy * dy;
-
- // Project each point onto the direction vector and compute the projection scalar t
- var projectionScalars = points.Select(p => new
- {
- Point = p,
- Scalar = ((p.pt.X - p1.X) * dx + (p.pt.Y - p1.Y) * dy) / dSquared
- });
- // Sort the points based on the projection scalar
- points = projectionScalars.OrderBy(ps => ps.Scalar).Select(ps => ps.Point).ToList();
- }
- void OrderPointsAlongSegment(ref List points)
- {
- float bestDist = 0;
- if (points.Count == 0) return;
- var p1 = points[0];
- var p2 = points[0];
- foreach (var pa in points)
- foreach (var pb in points)
- {
- float newDist = (float)(Sqrt((pa.X - pb.X) * (pa.X - pb.X) + (pa.Y - pb.Y) * (pa.Y - pb.Y)));
- if (newDist > bestDist)
- {
- bestDist = newDist;
- p1 = pa;
- p2 = pb;
- }
- }
-
- double dx = p2.X - p1.X;
- double dy = p2.Y - p1.Y;
- double dSquared = dx * dx + dy * dy;
-
- // Project each point onto the direction vector and compute the projection scalar t
- var projectionScalars = points.Select(p => new
- {
- Point = p,
- Scalar = ((p.X - p1.X) * dx + (p.Y - p1.Y) * dy) / dSquared
- });
- // Sort the points based on the projection scalar
- points = projectionScalars.OrderBy(ps => ps.Scalar).Select(ps => ps.Point).ToList();
- }
-
- private Segment BestSegmentThroughPoint2(PointPlus pt, List availablePoints)
- {
- //Find the longest segment through the given point
-
- //start from the given point
- //Get all the points within 2 pixels
- List candidatePts = GetNearbyPoints(pt, 2.5f, availablePoints);
- List candidateSegs = new();
- //get a list of all segments at least 3 pixels long
- foreach (var pt1 in candidatePts)
- foreach (var pt2 in candidatePts)
- {
- if (pt1 == pt2) continue;
- Segment s = new Segment(pt1, pt2);
- if (s.Length < 2) continue;
- float weight = GetSegmentWeight3(s);
- if (weight >= 2.5f)
- AddSegmentToList(candidateSegs, s);
- }
- //extend each of these segments
- float weightToBeat = 3.0f;
- foreach (var seg in candidateSegs)
- {
- weightToBeat = GetSegmentWeight3(seg);
- bool improved = true;
- while (improved)
- {
- improved = false;
- candidatePts = GetNearbyPoints(seg.P1, 1.5f, availablePoints);
- candidatePts.AddRange(GetNearbyPoints(seg.P2, 1.5f, availablePoints));
- foreach (var pt1 in candidatePts)
- foreach (var pt2 in candidatePts)
- {
- if (pt1 == pt2) continue;
- Segment s = new Segment(pt1, pt2);
- float weight = GetSegmentWeight3(s);
- if (weight > weightToBeat)
- {
- seg.P1 = s.P1;
- seg.P2 = s.P2;
- weightToBeat = weight;
- improved = true;
- }
- }
- }
- }
- //choose the longest
- weightToBeat = 3; //minpoints? minlength?
- Segment bestSegment = null;
- foreach (var seg in candidateSegs)
- {
- float weight = GetSegmentWeight3(seg);
- if (weight > weightToBeat && seg.Length >= 3)
- {
- bestSegment = seg;
- weightToBeat = weight;
- }
- }
- return bestSegment;
- }
-}
diff --git a/BrainSimulator/Network.cs b/BrainSimulator/Network.cs
index 618df39..1e0464a 100644
--- a/BrainSimulator/Network.cs
+++ b/BrainSimulator/Network.cs
@@ -23,7 +23,7 @@
namespace BrainSimulator
{
- internal static class Network
+ public static class Network
{
static TcpListener server;
static TcpClient tcpClient;
@@ -112,12 +112,15 @@ public static UdpClient UDPAudioReceive
public static void Broadcast(string message)
{
//Debug.WriteLine("Broadcast: " + message);
- if (broadcastAddress is null) SetBroadcastAddress();
+ if (broadcastAddress is null && !SetBroadcastAddress())
+ return;
+ if (broadcastAddress is null)
+ return;
byte[] datagram = Encoding.UTF8.GetBytes(message);
IPEndPoint ipEnd = new(broadcastAddress, UDPSendPort);
UDPBroadcast.SendAsync(datagram, datagram.Length, ipEnd);
- }
+ }
public static bool UDP_Send(string message,IPAddress ipToSend, int udpPort)
{
if (ipToSend is null) return false;
@@ -187,7 +190,7 @@ public static bool InitTCP(string podIPString)//This really needs to have some c
return true;
}
- public static void SetBroadcastAddress()
+ public static bool SetBroadcastAddress()
{
var host = Dns.GetHostEntry(Dns.GetHostName());
foreach (var ip in host.AddressList)
@@ -198,10 +201,11 @@ public static void SetBroadcastAddress()
string[] ipComps = ipStr.Split(".");
broadcastAddress = IPAddress.Parse(ipComps[0] + "." + ipComps[1] + "." + ipComps[2] + ".255");
- return;
+ return true;
}
}
- throw new Exception("No network adapters with an IPv4 address in the system!");
+ Debug.WriteLine("SetBroadcastAddress: no IPv4 adapter; UDP broadcast disabled.");
+ return false;
}
public static void SendStringToPodTCP(string msg)
diff --git a/BrainSimulator/Resources/wordlist.txt b/BrainSimulator/Resources/wordlist.txt
new file mode 100644
index 0000000..44e875b
--- /dev/null
+++ b/BrainSimulator/Resources/wordlist.txt
@@ -0,0 +1,30 @@
+# Minimal word list for ModuleOnlineInfo.SetupWordList (one word per line).
+# Replace or set WORDLIST_PATH to a full dictionary file.
+the
+be
+to
+of
+and
+a
+in
+that
+have
+it
+for
+not
+on
+with
+he
+as
+you
+do
+at
+dog
+cat
+bird
+fish
+horse
+tree
+water
+sun
+moon
\ No newline at end of file
diff --git a/BrainSimulator/UKSContent/Ch4Ch5-FidoDemo.md b/BrainSimulator/UKSContent/Ch4Ch5-FidoDemo.md
new file mode 100644
index 0000000..0295b37
--- /dev/null
+++ b/BrainSimulator/UKSContent/Ch4Ch5-FidoDemo.md
@@ -0,0 +1,25 @@
+# Ch.4 + Ch.5 Fido demo — human test script
+
+Load `Ch4Ch5-FidoDemo.txt` (File → Open network using text import, or run UKS import in tests).
+
+## 1. Inheritance (Ch.5)
+
+1. Query **From:** `Fido`, **Type:** `has`, **To:** `fur` → inherited via `dog`.
+2. Query **From:** `Fido`, **Type:** `has`, **To:** `owner` → inherited via `pet` (multi-parent).
+3. Query **From:** `Tripper`, **Type:** `has.3`, **To:** `legs` → local exception; `has.4` must not appear.
+
+## 2. Explainability (Ch.5)
+
+1. Query **From:** `Fido`, **To:** `fur`.
+2. Click **Why?** → trace `Fido → dog → fur`.
+
+## 3. Gated traversal (Ch.4)
+
+1. In UKS Query or debugger: activate only `Fido` → gated `has` empty.
+2. Activate `Fido` + `has` → `fur` reachable.
+
+## 4. Bubbling (Ch.5)
+
+1. Enable **ModuleAttributeBubble**.
+2. Add matching `has` links on sibling instances under a category.
+3. Confirm parent gains bubbled link; check `UKS.BubbleLog` for `bubble` entries.
\ No newline at end of file
diff --git a/BrainSimulator/UKSContent/Ch4Ch5-FidoDemo.txt b/BrainSimulator/UKSContent/Ch4Ch5-FidoDemo.txt
new file mode 100644
index 0000000..1dea0b1
--- /dev/null
+++ b/BrainSimulator/UKSContent/Ch4Ch5-FidoDemo.txt
@@ -0,0 +1,19 @@
+# Ch.4 + Ch.5 Fido demo network — import via UKS text file or load in BrainSim Thought
+# Human script: BrainSimulator/UKSContent/Ch4Ch5-FidoDemo.md
+
+[dog->is-a->Object] 1.00
+[pet->is-a->Object] 1.00
+[fur->is-a->Object] 1.00
+[owner->is-a->Object] 1.00
+[legs->is-a->Object] 1.00
+[has.4->is-a->has] 1.00
+[has.3->is-a->has] 1.00
+
+[dog->has->fur] 1.00
+[dog->has.4->legs] 1.00
+[pet->has->owner] 1.00
+
+[Fido->is-a->dog] 1.00
+[Fido->is-a->pet] 1.00
+[Tripper->is-a->dog] 1.00
+[Tripper->has.3->legs] 1.00
\ No newline at end of file
diff --git a/BrainSimulator/UKSContent/NatureAnimals.xml b/BrainSimulator/UKSContent/NatureAnimals.xml
new file mode 100644
index 0000000..95fa64f
--- /dev/null
+++ b/BrainSimulator/UKSContent/NatureAnimals.xml
@@ -0,0 +1,1995 @@
+
+
+
+ 0
+
+
+
+ 1
+
+
+
+ 2
+
+
+
+ 3
+
+
+
+ 4
+
+
+
+ 5
+
+
+
+ 6
+
+
+
+ 7
+
+
+
+ 8
+
+
+
+ 9
+
+
+
+ 10
+
+
+
+ 11
+
+
+
+ 12
+
+
+
+ 13
+
+
+
+ 14
+
+
+
+ 15
+
+
+
+ 16
+
+
+
+ 17
+
+
+
+ 18
+
+
+
+ 19
+
+
+
+ 20
+
+
+
+ 21
+
+
+
+ 22
+
+
+
+ 23
+
+
+
+ 24
+
+
+
+ 25
+
+
+
+ 26
+
+
+
+ 27
+
+
+
+ 28
+
+
+
+ 29
+
+
+
+ 30
+
+
+
+ 31
+
+
+
+ 32
+
+
+
+ 33
+
+
+
+ 34
+
+
+
+ 35
+
+
+
+ 36
+
+
+
+ 37
+
+
+
+ 38
+
+
+
+ 39
+
+
+
+ 40
+
+
+
+ 41
+
+
+
+ 42
+
+
+
+ 43
+
+
+
+ 44
+
+
+
+ 45
+
+
+
+ 46
+
+
+
+ 47
+
+
+
+ 48
+
+
+
+ 49
+
+
+
+ 50
+
+
+
+ 51
+
+
+
+ 52
+
+
+
+ 53
+
+
+
+ 54
+
+
+
+ 55
+
+
+
+ 56
+
+
+
+ 57
+
+
+
+ 58
+
+
+
+ 59
+
+
+
+ 60
+
+
+
+ 61
+
+
+
+ 62
+
+
+
+ 63
+
+
+
+ 64
+
+
+
+ 65
+
+
+
+ 66
+
+
+
+ 67
+
+
+
+ 68
+
+
+
+ 69
+
+
+
+ 70
+
+
+
+ 71
+
+
+
+ 72
+
+
+
+ 73
+
+
+
+ 74
+
+
+
+ 75
+
+
+
+ 76
+
+
+
+ 77
+
+
+
+ 78
+
+
+
+ 79
+
+
+
+ 80
+
+
+
+ 81
+
+
+
+ 82
+
+
+
+ 83
+
+
+
+ 84
+
+
+
+ 85
+
+
+
+ 86
+
+
+
+ 87
+
+
+
+ 88
+
+
+
+ 89
+
+
+
+ 90
+
+
+
+ 91
+
+
+
+ 92
+
+
+
+ 93
+
+
+
+ 94
+
+
+
+ 95
+
+
+
+ 96
+
+
+
+ 97
+
+
+
+ 98
+
+
+
+ 99
+
+
+
+ 100
+
+
+
+ 101
+
+
+
+ 102
+
+
+
+ 103
+
+
+
+ 104
+
+
+
+ 105
+
+
+
+ 106
+
+ 1
+ 10
+ 0
+
+
+ 107
+
+ 2
+ 10
+ 0
+
+
+ 108
+
+ 3
+ 10
+ 0
+
+
+ 109
+
+ 4
+ 10
+ 0
+
+
+ 110
+
+ 5
+ 10
+ 4
+
+
+ 111
+
+ 6
+ 10
+ 3
+
+
+ 112
+
+ 7
+ 10
+ 3
+
+
+ 113
+
+ 8
+ 10
+ 3
+
+
+ 114
+
+ 9
+ 10
+ 0
+
+
+ 115
+
+ 35
+ 10
+ 0
+
+
+ 116
+
+ 10
+ 10
+ 2
+
+
+ 117
+
+ 11
+ 10
+ 2
+
+
+ 118
+
+ 12
+ 10
+ 2
+
+
+ 119
+
+ 13
+ 10
+ 2
+
+
+ 120
+
+ 14
+ 10
+ 2
+
+
+ 121
+
+ 15
+ 10
+ 2
+
+
+ 122
+
+ 16
+ 10
+ 2
+
+
+ 123
+
+ 16
+ 10
+ 5
+
+
+ 124
+
+ 17
+ 10
+ 2
+
+
+ 125
+
+ 17
+ 10
+ 5
+
+
+ 126
+
+ 18
+ 10
+ 2
+
+
+ 127
+
+ 19
+ 10
+ 2
+
+
+ 128
+
+ 20
+ 10
+ 2
+
+
+ 129
+
+ 21
+ 10
+ 2
+
+
+ 130
+
+ 22
+ 10
+ 11
+
+
+ 131
+
+ 23
+ 10
+ 11
+
+
+ 132
+
+ 24
+ 10
+ 11
+
+
+ 133
+
+ 25
+ 10
+ 11
+
+
+ 134
+
+ 26
+ 10
+ 11
+
+
+ 135
+
+ 27
+ 10
+ 11
+
+
+ 136
+
+ 28
+ 10
+ 4
+
+
+ 137
+
+ 29
+ 10
+ 4
+
+
+ 138
+
+ 30
+ 10
+ 4
+
+
+ 139
+
+ 31
+ 10
+ 4
+
+
+ 140
+
+ 32
+ 10
+ 4
+
+
+ 141
+
+ 33
+ 10
+ 4
+
+
+ 142
+
+ 34
+ 10
+ 33
+
+
+ 143
+
+ 36
+ 10
+ 3
+
+
+ 144
+
+ 39
+ 10
+ 3
+
+
+ 145
+
+ 37
+ 10
+ 36
+
+
+ 146
+
+ 38
+ 10
+ 36
+
+
+ 147
+
+ 87
+ 10
+ 38
+
+
+ 148
+
+ 40
+ 10
+ 37
+
+
+ 149
+
+ 41
+ 10
+ 37
+
+
+ 150
+
+ 42
+ 10
+ 37
+
+
+ 151
+
+ 43
+ 10
+ 37
+
+
+ 152
+
+ 44
+ 10
+ 37
+
+
+ 153
+
+ 45
+ 10
+ 37
+
+
+ 154
+
+ 46
+ 10
+ 40
+
+
+ 155
+
+ 47
+ 10
+ 40
+
+
+ 156
+
+ 48
+ 10
+ 40
+
+
+ 157
+
+ 49
+ 10
+ 40
+
+
+ 158
+
+ 50
+ 10
+ 40
+
+
+ 159
+
+ 51
+ 10
+ 40
+
+
+ 160
+
+ 52
+ 10
+ 40
+
+
+ 161
+
+ 53
+ 10
+ 40
+
+
+ 162
+
+ 54
+ 10
+ 40
+
+
+ 163
+
+ 55
+ 10
+ 41
+
+
+ 164
+
+ 56
+ 10
+ 41
+
+
+ 165
+
+ 57
+ 10
+ 41
+
+
+ 166
+
+ 58
+ 10
+ 41
+
+
+ 167
+
+ 59
+ 10
+ 42
+
+
+ 168
+
+ 60
+ 10
+ 42
+
+
+ 169
+
+ 61
+ 10
+ 42
+
+
+ 170
+
+ 62
+ 10
+ 43
+
+
+ 171
+
+ 63
+ 10
+ 43
+
+
+ 172
+
+ 64
+ 10
+ 43
+
+
+ 173
+
+ 65
+ 10
+ 44
+
+
+ 174
+
+ 66
+ 10
+ 45
+
+
+ 175
+
+ 67
+ 10
+ 45
+
+
+ 176
+
+ 68
+ 10
+ 6
+
+
+ 177
+
+ 69
+ 10
+ 6
+
+
+ 178
+
+ 70
+ 10
+ 6
+
+
+ 179
+
+ 71
+ 10
+ 6
+
+
+ 180
+
+ 72
+ 10
+ 6
+
+
+ 181
+
+ 73
+ 10
+ 6
+
+
+ 182
+
+ 74
+ 10
+ 6
+
+
+ 183
+
+ 75
+ 10
+ 6
+
+
+ 184
+
+ 76
+ 10
+ 6
+
+
+ 185
+
+ 77
+ 10
+ 8
+
+
+ 186
+
+ 78
+ 10
+ 8
+
+
+ 187
+
+ 79
+ 10
+ 8
+
+
+ 188
+
+ 80
+ 10
+ 8
+
+
+ 189
+
+ 81
+ 10
+ 7
+
+
+ 190
+
+ 82
+ 10
+ 7
+
+
+ 191
+
+ 83
+ 10
+ 7
+
+
+ 192
+
+ 84
+ 10
+ 7
+
+
+ 193
+
+ 85
+ 10
+ 7
+
+
+ 194
+
+ 86
+ 10
+ 7
+
+
+ 195
+
+ 40
+ 15
+ 28
+
+
+ 196
+
+ 40
+ 15
+ 30
+
+
+ 197
+
+ 40
+ 15
+ 32
+
+
+ 198
+
+ 41
+ 15
+ 28
+
+
+ 199
+
+ 41
+ 15
+ 31
+
+
+ 200
+
+ 41
+ 15
+ 32
+
+
+ 201
+
+ 42
+ 15
+ 29
+
+
+ 202
+
+ 42
+ 15
+ 31
+
+
+ 203
+
+ 42
+ 15
+ 32
+
+
+ 204
+
+ 43
+ 15
+ 29
+
+
+ 205
+
+ 43
+ 15
+ 31
+
+
+ 206
+
+ 43
+ 15
+ 32
+
+
+ 207
+
+ 44
+ 15
+ 29
+
+
+ 208
+
+ 44
+ 15
+ 31
+
+
+ 209
+
+ 45
+ 15
+ 29
+
+
+ 210
+
+ 37
+ 15
+ 32
+
+
+ 211
+
+ 46
+ 11
+ 69
+
+
+ 212
+
+ 46
+ 22
+ 68
+
+
+ 213
+
+ 46
+ 11
+ 70
+
+
+ 214
+
+ 46
+ 14
+ 77
+
+
+ 215
+
+ 47
+ 11
+ 69
+
+
+ 216
+
+ 47
+ 22
+ 68
+
+
+ 217
+
+ 47
+ 11
+ 70
+
+
+ 218
+
+ 47
+ 14
+ 78
+
+
+ 219
+
+ 48
+ 11
+ 69
+
+
+ 220
+
+ 48
+ 22
+ 68
+
+
+ 221
+
+ 48
+ 14
+ 79
+
+
+ 222
+
+ 49
+ 11
+ 73
+
+
+ 223
+
+ 49
+ 27
+ 68
+
+
+ 224
+
+ 50
+ 11
+ 74
+
+
+ 225
+
+ 50
+ 22
+ 68
+
+
+ 226
+
+ 51
+ 22
+ 68
+
+
+ 227
+
+ 51
+ 11
+ 69
+
+
+ 228
+
+ 52
+ 11
+ 69
+
+
+ 229
+
+ 52
+ 22
+ 68
+
+
+ 230
+
+ 52
+ 11
+ 70
+
+
+ 231
+
+ 53
+ 22
+ 68
+
+
+ 232
+
+ 53
+ 11
+ 69
+
+
+ 233
+
+ 54
+ 11
+ 73
+
+
+ 234
+
+ 54
+ 27
+ 68
+
+
+ 235
+
+ 41
+ 11
+ 74
+
+
+ 236
+
+ 41
+ 11
+ 71
+
+
+ 237
+
+ 41
+ 24
+ 68
+
+
+ 238
+
+ 41
+ 11
+ 76
+
+
+ 239
+
+ 55
+ 24
+ 74
+
+
+ 240
+
+ 56
+ 24
+ 74
+
+
+ 241
+
+ 57
+ 24
+ 74
+
+
+ 242
+
+ 58
+ 24
+ 74
+
+
+ 243
+
+ 42
+ 11
+ 72
+
+
+ 244
+
+ 59
+ 27
+ 68
+
+
+ 245
+
+ 59
+ 11
+ 72
+
+
+ 246
+
+ 60
+ 22
+ 68
+
+
+ 247
+
+ 60
+ 11
+ 72
+
+
+ 248
+
+ 61
+ 22
+ 68
+
+
+ 249
+
+ 61
+ 11
+ 72
+
+
+ 250
+
+ 43
+ 11
+ 73
+
+
+ 251
+
+ 43
+ 11
+ 75
+
+
+ 252
+
+ 43
+ 11
+ 72
+
+
+ 253
+
+ 62
+ 11
+ 73
+
+
+ 254
+
+ 63
+ 11
+ 73
+
+
+ 255
+
+ 63
+ 27
+ 68
+
+
+ 256
+
+ 65
+ 22
+ 68
+
+
+ 257
+
+ 66
+ 11
+ 74
+
+
+ 258
+
+ 66
+ 25
+ 68
+
+
+ 259
+
+ 66
+ 14
+ 80
+
+
+ 260
+
+ 67
+ 25
+ 68
+
+
+ 261
+
+ 46
+ 10
+ 93
+
+
+ 262
+
+ 47
+ 10
+ 93
+
+
+ 263
+
+ 48
+ 10
+ 93
+
+
+ 264
+
+ 52
+ 10
+ 94
+
+
+ 265
+
+ 55
+ 10
+ 94
+
+
+ 266
+
+ 53
+ 10
+ 94
+
+
+ 267
+
+ 46
+ 16
+ 52
+
+
+ 268
+
+ 46
+ 16
+ 47
+
+
+ 269
+
+ 47
+ 16
+ 46
+
+
+ 270
+
+ 52
+ 16
+ 46
+
+
+ 271
+
+ 62
+ 16
+ 64
+
+
+ 272
+
+ 55
+ 16
+ 58
+
+
+ 273
+
+ 46
+ 17
+ 41
+
+
+ 274
+
+ 41
+ 17
+ 43
+
+
+ 275
+
+ 40
+ 17
+ 42
+
+
+ 276
+
+ 49
+ 17
+ 43
+
+
+ 277
+
+ 50
+ 17
+ 41
+
+
+ 278
+
+ 59
+ 17
+ 46
+
+
+ 279
+
+ 55
+ 18
+ 51
+
+
+ 280
+
+ 51
+ 19
+ 55
+
+
+ 281
+
+ 52
+ 18
+ 53
+
+
+ 282
+
+ 53
+ 19
+ 52
+
+
+ 283
+
+ 63
+ 18
+ 62
+
+
+ 284
+
+ 62
+ 19
+ 63
+
+
+ 285
+
+ 58
+ 18
+ 51
+
+
+ 286
+
+ 47
+ 18
+ 51
+
+
+ 287
+
+ 53
+ 20
+ 81
+
+
+ 288
+
+ 55
+ 20
+ 83
+
+
+ 289
+
+ 62
+ 20
+ 86
+
+
+ 290
+
+ 49
+ 20
+ 82
+
+
+ 291
+
+ 63
+ 20
+ 82
+
+
+ 292
+
+ 54
+ 20
+ 82
+
+
+ 293
+
+ 57
+ 20
+ 85
+
+
+ 294
+
+ 56
+ 20
+ 81
+
+
+ 295
+
+ 65
+ 20
+ 86
+
+
+ 296
+
+ 87
+ 20
+ 81
+
+
+ 297
+
+ 46
+ 21
+ 62
+
+
+ 298
+
+ 47
+ 21
+ 51
+
+
+ 299
+
+ 55
+ 21
+ 51
+
+
+ 300
+
+ 52
+ 21
+ 53
+
+
+ 301
+
+ 63
+ 21
+ 62
+
+
+ 302
+
+ 95
+ 10
+ 46
+
+
+ 303
+
+ 95
+ 13
+ 88
+
+
+ 304
+
+ 95
+ 22
+ 68
+
+
+ 305
+
+ 95
+ 20
+ 84
+
+
+ 306
+
+ 96
+ 10
+ 46
+
+
+ 307
+
+ 96
+ 23
+ 68
+
+
+ 308
+
+ 97
+ 10
+ 47
+
+
+ 309
+
+ 97
+ 13
+ 89
+
+
+ 310
+
+ 97
+ 20
+ 84
+
+
+ 311
+
+ 98
+ 10
+ 48
+
+
+ 312
+
+ 98
+ 13
+ 88
+
+
+ 313
+
+ 98
+ 20
+ 84
+
+
+ 314
+
+ 99
+ 10
+ 46
+
+
+ 315
+
+ 99
+ 13
+ 90
+
+
+ 316
+
+ 100
+ 10
+ 55
+
+
+ 317
+
+ 100
+ 20
+ 83
+
+
+ 318
+
+ 101
+ 10
+ 59
+
+
+ 319
+
+ 101
+ 13
+ 91
+
+
+ 320
+
+ 101
+ 20
+ 81
+
+
+ 321
+
+ 102
+ 10
+ 50
+
+
+ 322
+
+ 102
+ 20
+ 81
+
+
+ 323
+
+ 103
+ 10
+ 62
+
+
+ 324
+
+ 103
+ 20
+ 86
+
+
+ 325
+
+ 104
+ 10
+ 57
+
+
+ 326
+
+ 104
+ 20
+ 85
+
+
+ 327
+
+ 105
+ 10
+ 53
+
+
+ 328
+
+ 105
+ 13
+ 88
+
+
+ 329
+
+ 105
+ 20
+ 81
+
+
diff --git a/BrainSimulator/Utils.cs b/BrainSimulator/Utils.cs
index 964dcc4..dc57c61 100644
--- a/BrainSimulator/Utils.cs
+++ b/BrainSimulator/Utils.cs
@@ -24,6 +24,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Windows.Media.Media3D;
+using System.Diagnostics;
using System.IO;
using System.Reflection.Metadata;
@@ -111,7 +112,7 @@ public HSLColor(Dictionary values)
saturation = values["Sat+"];
luminance = values["Lum+"];
}
- catch { }
+ catch (Exception ex) { Debug.WriteLine($"HSLColor dictionary parse failed: {ex.Message}"); }
}
public HSLColor(byte a, byte r, byte g, byte b)
{
@@ -194,13 +195,12 @@ Color ColorFromHSL2()
Color c;
- if (000 <= hue && hue < 060) c = Color.FromArgb(255, (byte)((C + m) * 255), (byte)((X + m) * 255), (byte)((0 + m) * 255));
- if (060 <= hue && hue < 120) c = Color.FromArgb(255, (byte)((X + m) * 255), (byte)((C + m) * 255), (byte)((0 + m) * 255));
- if (120 <= hue && hue < 180) c = Color.FromArgb(255, (byte)((0 + m) * 255), (byte)((C + m) * 255), (byte)((X + m) * 255));
- if (180 <= hue && hue < 240) c = Color.FromArgb(255, (byte)((0 + m) * 255), (byte)((X + m) * 255), (byte)((C + m) * 255));
- if (240 <= hue && hue < 300) c = Color.FromArgb(255, (byte)((X + m) * 255), (byte)((0 + m) * 255), (byte)((C + m) * 255));
- if (300 <= hue && hue < 345) c = Color.FromArgb(255, (byte)((C + m) * 255), (byte)((0 + m) * 255), (byte)((C + m) * 255));
- if (hue > 345) c = Color.FromArgb(255, (byte)((C + m) * 255), (byte)((X + m) * 255), (byte)((0 + m) * 255));
+ if (hue < 60) c = Color.FromArgb(255, (byte)((C + m) * 255), (byte)((X + m) * 255), (byte)((0 + m) * 255));
+ else if (hue < 120) c = Color.FromArgb(255, (byte)((X + m) * 255), (byte)((C + m) * 255), (byte)((0 + m) * 255));
+ else if (hue < 180) c = Color.FromArgb(255, (byte)((0 + m) * 255), (byte)((C + m) * 255), (byte)((X + m) * 255));
+ else if (hue < 240) c = Color.FromArgb(255, (byte)((0 + m) * 255), (byte)((X + m) * 255), (byte)((C + m) * 255));
+ else if (hue < 300) c = Color.FromArgb(255, (byte)((X + m) * 255), (byte)((0 + m) * 255), (byte)((C + m) * 255));
+ else c = Color.FromArgb(255, (byte)((C + m) * 255), (byte)((0 + m) * 255), (byte)((X + m) * 255));
return c;
}
@@ -228,9 +228,8 @@ public bool Equals(HSLColor c1)
if (c1.luminance > .95) return true;
}
float absHueDiff = Abs(hue - c1.hue);
- if (absHueDiff < 5 || absHueDiff > 355)
- return true;
- return false;
+ float circularHueDiff = Min(absHueDiff, 360f - absHueDiff);
+ return circularHueDiff < 5;
}
}
@@ -272,7 +271,7 @@ public static Control FindByName(Visual v, string name)
if (c2 is not null)
return c2;
}
- catch { }
+ catch (Exception ex) { Debug.WriteLine($"FindByName walk failed for '{name}': {ex.Message}"); }
}
}
return null;
diff --git a/BrainSimulator/archive/legacy/MainWindowPythonModules.cs b/BrainSimulator/archive/legacy/MainWindowPythonModules.cs
new file mode 100644
index 0000000..7462c7f
--- /dev/null
+++ b/BrainSimulator/archive/legacy/MainWindowPythonModules.cs
@@ -0,0 +1,173 @@
+/*
+ * Brain Simulator Through
+ *
+ * Copyright (c) 2026 Charles Simon
+ *
+ * This file is part of Brain Simulator Through and is licensed under
+ * the MIT License. You may use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of this software under the terms of
+ * the MIT License.
+ *
+ * See the LICENSE file in the project root for full license information.
+ */
+
+using Python.Runtime;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Windows;
+using UKS;
+using System.Windows.Interop;
+
+
+namespace BrainSimulator;
+
+public partial class MainWindow : Window
+{
+ public static List<(string, dynamic)> activePythonModules = new();
+ public static List GetPythonModules()
+ {
+ //this is a buffer of python modules so they can be imported once and run many times.
+ List<(string, dynamic)> modules = new List<(string, dynamic)>();
+ List pythonFiles = new();
+ try
+ {
+ pythonFiles = Directory.GetFiles(@".\pythonModules", "*.py").ToList();
+ string destDir = Directory.GetCurrentDirectory();
+ string sourceDir = destDir + "\\PythonModules";
+ for (int i = 0; i < pythonFiles.Count; i++)
+ {
+ if (pythonFiles[i].StartsWith("utils")) continue;
+ pythonFiles[i] = Path.GetFileName(pythonFiles[i]);
+ }
+ }
+ catch
+ {
+
+ }
+ return pythonFiles;
+ }
+ static void RunScript(string moduleLabel)
+ {
+ bool firstTime = false;
+ //get the ModuleType
+ Thing tModule = theUKS.Labeled(moduleLabel);
+ if (tModule == null) { return; }
+ Thing tModuleType = tModule.Parents.FindFirst(x => x.HasAncestorLabeled("AvailableModule"));
+ if (tModuleType == null) return;
+ string moduleType = tModuleType.Label;
+ moduleType = moduleType.Replace(".py", "");
+
+ //if this is the very first call, initialize the python engine
+ if (Runtime.PythonDLL == null)
+ {
+ try
+ {
+ //TODO use variable
+ Runtime.PythonDLL = @"Python310";
+
+ PythonEngine.Initialize();
+ dynamic sys = Py.Import("sys");
+ dynamic os = Py.Import("os");
+ sys.path.append(os.getcwd()); // enables finding scriptName module
+ sys.path.append(os.getcwd() + "\\pythonModules");
+ }
+ catch
+ {
+ Console.WriteLine("Python engine initialization failed");
+ return;
+ }
+ }
+ using (Py.GIL())
+ {
+ var theModuleEntry = activePythonModules.FirstOrDefault(x => x.Item1.ToLower() == moduleLabel.ToLower());
+ if (string.IsNullOrEmpty(theModuleEntry.Item1))
+ {
+ //if this is the first time this modulw has been used
+ try
+ {
+ Console.WriteLine("Loading " + moduleLabel);
+ dynamic theModule = Py.Import(moduleType);
+ theModuleEntry = (moduleLabel, theModule);
+ activePythonModules.Add(theModuleEntry);
+ theModule.Init();
+ firstTime = true;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("Load/initialize failed for module: " + moduleLabel + " Reason: " + ex.Message);
+ theModuleEntry = (moduleLabel, null);
+ activePythonModules.Add(theModuleEntry);
+ }
+ }
+ if (theModuleEntry.Item2 != null)
+ {
+ try
+ {
+ if (!theModuleEntry.Item2.Fire())
+ { } //this doesn't seem to work because it thows an exception instead
+ if (firstTime)
+ {
+ var HWND = theModuleEntry.Item2.GetHWND();
+ var ss = HWND.ToString();
+ // this works, and returns 1322173
+ int intValue = Convert.ToInt32(ss, 16);
+ firstTime = false;
+ SetOwner(intValue);
+ }
+ }
+ catch (Exception ex)
+ {
+ activePythonModules.Remove(theModuleEntry);
+ MainWindow.theWindow.DeactivateModule(moduleLabel);
+ Console.WriteLine("Fire method call failed for module: " + moduleLabel + " Reason: " + ex.Message);
+ }
+ }
+ }
+ }
+
+
+ private const int GWL_HWNDPARENT = -8;
+
+ [DllImport("user32.dll", EntryPoint = "SetWindowLongPtr")]
+ private static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
+
+ [DllImport("user32.dll", SetLastError = true)]
+ private static extern IntPtr SetWindowLong(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
+
+ static void SetOwner(int HWND)
+ {
+ // Example usage
+ IntPtr childHwnd = new IntPtr(HWND); // Child window handle
+ //IntPtr ownerHwnd = new IntPtr(654321); // New owner window handle
+ Window window = Window.GetWindow(MainWindow.theWindow);
+ var wih = new WindowInteropHelper(window);
+ IntPtr ownerHwnd = wih.Handle;
+
+ ChangeWindowOwner(childHwnd, ownerHwnd);
+ }
+
+ static void ChangeWindowOwner(IntPtr childHwnd, IntPtr ownerHwnd)
+ {
+ IntPtr result = 0;
+ if (IntPtr.Size == 8)
+ SetWindowLongPtr(childHwnd, GWL_HWNDPARENT, ownerHwnd);
+ else
+ SetWindowLong(childHwnd, GWL_HWNDPARENT, ownerHwnd);
+
+ if (result == IntPtr.Zero)
+ {
+ int errorCode = Marshal.GetLastWin32Error();
+ Console.WriteLine($"Failed to change window owner. Error code: {errorCode}");
+ }
+ else
+ {
+ Console.WriteLine("Window owner changed successfully.");
+ }
+ }
+}
+
+
+
diff --git a/BrainSimulator/archive/legacy/README.md b/BrainSimulator/archive/legacy/README.md
new file mode 100644
index 0000000..746dae3
--- /dev/null
+++ b/BrainSimulator/archive/legacy/README.md
@@ -0,0 +1,10 @@
+# Legacy BrainSim3 sources (not compiled)
+
+Moved from `BrainSimulator/` root on 2026-07-07. Excluded via `` in `BrainSim Thought.csproj`.
+
+| File | Reason |
+|------|--------|
+| `XmlFile.cs` | BrainSim3Data XML serializer; app uses `UKS.LoadUKSfromXMLFile` / `SaveUKStoXMLFile` |
+| `MainWindowPythonModules.cs` | Duplicate Python glue with hardcoded `Python310`; superseded by `ModuleHandler.cs` |
+
+Do not re-enable without porting to current UKS persistence and `PythonPath` configuration.
\ No newline at end of file
diff --git a/BrainSimulator/XmlFile.cs b/BrainSimulator/archive/legacy/XmlFile.cs
similarity index 100%
rename from BrainSimulator/XmlFile.cs
rename to BrainSimulator/archive/legacy/XmlFile.cs
diff --git a/PythonProj/MainWindow.py b/PythonProj/MainWindow.py
index f5422e5..e742d23 100644
--- a/PythonProj/MainWindow.py
+++ b/PythonProj/MainWindow.py
@@ -16,6 +16,7 @@ def __init__(self, level: Union[tk.Tk, tk.Toplevel]) -> None:
title: str = titleBase
super(MainWindow, self).__init__(
title=title, level=level, module_type=os.path.basename(__file__))
+ self._closed = False
self.setupUKS()
self.build()
@@ -36,9 +37,6 @@ def build(self):
self.saveAsButton.pack(side='right',padx=50)
self.saveButton.pack(side='top',pady=20)
self.setupcontent()
-
- if sys.argv[0] != "":
- self.level.mainloop()
def setupcontent(self):
self.moduleList.delete(0,'end')
@@ -73,7 +71,7 @@ def openFile(self):
self.uks.LoadUKSfromXMLFile(fileName)
self.setupUKS()
if self.uks.Labeled("MainWindow.py") == None:
- self.uks.AddThing("MainWindow.py", self.uks.Labeled("AvailableModule"));
+ self.uks.GetOrAddThought("MainWindow.py", self.uks.Labeled("AvailableModule"));
self.activateModule("MainWindow.py")
self.level.title(titleBase +' -- ' +os.path.basename(fileName))
self.setupcontent()
@@ -82,14 +80,14 @@ def openFile(self):
#Add necessary status info to older UKS if needed
def setupUKS(self):
if self.uks.Labeled("BrainSim") == None:
- self.uks.AddThing("BrainSim",None)
- self.uks.GetOrAddThing("AvailableModule","BrainSim")
- self.uks.GetOrAddThing("ActiveeModule","BrainSim")
+ self.uks.GetOrAddThought("BrainSim",None)
+ self.uks.GetOrAddThought("AvailableModule","BrainSim")
+ self.uks.GetOrAddThought("ActiveeModule","BrainSim")
if self.uks.Labeled("AvailableModule").Children.Count == 0:
python_modules = os.listdir(".")
for module in python_modules:
if module.startswith("m") and module.endswith(".py"):
- self.uks.GetOrAddThing(module,"AvailableModule")
+ self.uks.GetOrAddThought(module,"AvailableModule")
@@ -134,8 +132,10 @@ def deactivateModule(self,moduleLabel):
print ("deactivating ",moduleLabel)
thingToDeactivate= self.uks.Labeled(moduleLabel)
if thingToDeactivate != None:
- self.uks.DeleteAllChildren(thingToDeactivate)
- self.uks.DeleteThing(thingToDeactivate)
+ # native replacement for removed compat DeleteAllChildren
+ for child in list(thingToDeactivate.Children):
+ child.Delete()
+ thingToDeactivate.Delete()
def activateModule(self,moduleTypeLabel):
print ("activating ",moduleTypeLabel)
thingToActivate= self.uks.Labeled(moduleTypeLabel)
@@ -147,12 +147,21 @@ def activateModule(self,moduleTypeLabel):
def onClosing(self):
- print ("MainWindow closing")
- os._exit(0)
+ print("MainWindow closing")
+ self._closed = True
+ try:
+ self.level.destroy()
+ except tk.TclError:
+ pass
def fire(self):
- #Put your functional code HERE
- #This function is called repeateldly so you may wish to do things only on a timer like this:
+ if self._closed:
+ return False
+ try:
+ if not self.level.winfo_exists():
+ return False
+ except tk.TclError:
+ return False
curr_time: float = time.time()
try:
if curr_time > (self.prev_time + TIMEDELAY):
@@ -163,9 +172,11 @@ def fire(self):
#you always nee this:
self.setupcontent()
self.level.update()
- #don't ever close this module while the program is running
return True
+ def close(self):
+ self.onClosing()
+
######################
## Expose Methods ##
@@ -185,6 +196,7 @@ def GetHWND() -> int:
def SetLabel(label):
view.setLabel(label)
-if sys.argv[0] != "":
+if __name__ == "__main__":
Init()
+ view.level.mainloop()
diff --git a/PythonProj/module_uks_tree.py b/PythonProj/module_uks_tree.py
index 17232c1..3400e8c 100644
--- a/PythonProj/module_uks_tree.py
+++ b/PythonProj/module_uks_tree.py
@@ -138,11 +138,6 @@ def build(self) -> None:
self.tree_view.bind("<>", self.handle_close_event)
self.tree_view.bind("", self.handle_mouse_enter)
self.tree_view.bind("", self.handle_mouse_leave)
-
- if sys.argv[0] != "":
- self.level.mainloop()
-
-
############
## Fire ##
@@ -183,5 +178,6 @@ def SetLabel(label):
def Close():
view.close()
-if sys.argv[0] != "":
+if __name__ == "__main__":
Init()
+ view.level.mainloop()
diff --git a/PythonProj/utils.py b/PythonProj/utils.py
index e2e6887..fd26b31 100644
--- a/PythonProj/utils.py
+++ b/PythonProj/utils.py
@@ -33,26 +33,23 @@ def __init__(self,
self.window_height = None
self.window_x = None
self.window_y = None
- #BUG...if you enable the following line, the WINDOWS program will crash if you move/resize a window
- #self.level.bind("",self.resize)
+ # Call bind_resize_events() after build if geometry sync to UKS is needed.
def setLabel(self, new_label: str):
self.label = new_label
- def resize(self,event):
- #breakpoint()
- #this actually receives ALL the configuration events...so we have to sort out resize/move and the event source
- if hasattr(event.widget, "widgetName"):
- pass
- else: #if it doesn't have a name, it must be top level
- if (self.window_width != event.width) or (self.window_height != event.height):
- #if height/width changed, it must be resize
- self.window_width, self.window_height = event.width,event.height
- if (self.window_x != event.x ) or (self.window_y != event.y):
- self.window_x, self.window_y = event.x,event.y
- print(self.module_type, self.label)
- print(self.level.winfo_geometry())
- #TODO Add code to update values in UKS
+ def bind_resize_events(self):
+ self.level.bind("", self.resize)
+
+ def resize(self, event):
+ # Only handle the toplevel window; child Configure events caused Windows crashes.
+ if event.widget is not self.level:
+ return
+ if (self.window_width != event.width) or (self.window_height != event.height):
+ self.window_width, self.window_height = event.width, event.height
+ if (self.window_x != event.x) or (self.window_y != event.y):
+ self.window_x, self.window_y = event.x, event.y
+ # TODO: persist geometry to UKS module attributes when safe
def close(self):
self.level.destroy()
diff --git a/Tests/AlgorithmXmlFixture.cs b/Tests/AlgorithmXmlFixture.cs
new file mode 100644
index 0000000..075bb73
--- /dev/null
+++ b/Tests/AlgorithmXmlFixture.cs
@@ -0,0 +1,110 @@
+/*
+ * Shared Algorithm.xml snapshot for ModuleAlgorithmTests (loads once per test class).
+ */
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using BrainSimulator.Modules;
+using UKS;
+
+namespace UKS.Tests;
+
+public sealed class AlgorithmXmlFixture
+{
+ private readonly List snapshot;
+
+ public AlgorithmXmlFixture()
+ {
+ string xmlPath = FindAlgorithmXmlPath();
+ if (!File.Exists(xmlPath))
+ {
+ throw new FileNotFoundException($"Algorithm.xml not found at {xmlPath}");
+ }
+
+ snapshot = UKS.DeserializeUkTempFromXmlFile(xmlPath);
+ }
+
+ public (UKS uks, ModuleAlgorithm module) CreateHarness()
+ {
+ var uks = new UKS(clear: true);
+ UKS.theUKS = uks;
+ Thought.ClearRecentlyFiredQueue();
+
+ uks.RestoreFromUkTempSnapshot(CloneSnapshot(snapshot));
+
+ var module = new ModuleAlgorithm
+ {
+ theUKS = uks
+ };
+ module.UKSInitializedNotification();
+
+ return (uks, module);
+ }
+
+ private static List CloneSnapshot(IReadOnlyList source)
+ {
+ var clone = new List(source.Count);
+ foreach (UKS.sThought st in source)
+ {
+ clone.Add(new UKS.sThought
+ {
+ index = st.index,
+ label = st.label,
+ source = st.source,
+ linkType = st.linkType,
+ target = st.target,
+ weight = st.weight,
+ V = st.V,
+ });
+ }
+
+ return clone;
+ }
+
+ private static string FindAlgorithmXmlPath()
+ {
+ foreach (string candidate in CandidatePaths())
+ {
+ if (File.Exists(candidate))
+ {
+ return Path.GetFullPath(candidate);
+ }
+ }
+
+ throw new FileNotFoundException(
+ "Algorithm.xml not found. Expected next to test output under UKSContent/Algorithm.xml " +
+ "or in BrainSimulator/UKSContent under the repository root.");
+ }
+
+ private static IEnumerable CandidatePaths()
+ {
+ string outputDir = AppContext.BaseDirectory;
+ yield return Path.Combine(outputDir, "UKSContent", "Algorithm.xml");
+ yield return Path.Combine(outputDir, "UKSContent", "algorithm.xml");
+
+ string? repoRoot = FindRepositoryRoot();
+ if (repoRoot is not null)
+ {
+ string contentDir = Path.Combine(repoRoot, "BrainSimulator", "UKSContent");
+ yield return Path.Combine(contentDir, "Algorithm.xml");
+ yield return Path.Combine(contentDir, "algorithm.xml");
+ }
+ }
+
+ private static string? FindRepositoryRoot()
+ {
+ DirectoryInfo? directory = new(AppContext.BaseDirectory);
+ while (directory is not null)
+ {
+ if (File.Exists(Path.Combine(directory.FullName, "BrainSim Thought.sln")))
+ {
+ return directory.FullName;
+ }
+
+ directory = directory.Parent;
+ }
+
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/Tests/DiscreteAttributeCh4RegressionTests.cs b/Tests/DiscreteAttributeCh4RegressionTests.cs
new file mode 100644
index 0000000..965a960
--- /dev/null
+++ b/Tests/DiscreteAttributeCh4RegressionTests.cs
@@ -0,0 +1,93 @@
+/*
+ * Ch.4 discrete sensory ingress regression tests (Simon Atomic Thoughts Fig 4.2).
+ */
+
+using System.Linq;
+using UKS;
+using Xunit;
+
+namespace UKS.Tests;
+
+public class DiscreteAttributeCh4RegressionTests
+{
+ private static int LevelNumber(string label) =>
+ int.Parse(label.Split('-')[^1]);
+
+ private static UKS CreateUks()
+ {
+ var uks = new UKS(clear: true);
+ uks.CreateInitialStructure();
+ return uks;
+ }
+
+ [Fact]
+ public void Orange_rgb_quantizes_to_high_red_medium_green_low_blue()
+ {
+ var uks = CreateUks();
+ var decoded = DiscreteAttributeDecoder.DecodeRgb(255, 165, 0, uks);
+
+ Assert.Equal("red-level-8", decoded.RedLevel.Label);
+ Assert.InRange(LevelNumber(decoded.GreenLevel.Label), 5, 7);
+ Assert.Equal("blue-level-1", decoded.BlueLevel.Label);
+ Assert.InRange(LevelNumber(decoded.BrightnessLevel.Label), 5, 7);
+ }
+
+ [Fact]
+ public void Zero_rgb_emits_affirmative_black_and_low_brightness()
+ {
+ var uks = CreateUks();
+ var decoded = DiscreteAttributeDecoder.DecodeRgb(0, 0, 0, uks);
+
+ Assert.Equal("red-level-1", decoded.RedLevel.Label);
+ Assert.Equal("green-level-1", decoded.GreenLevel.Label);
+ Assert.Equal("blue-level-1", decoded.BlueLevel.Label);
+ Assert.Equal("brightness-level-1", decoded.BrightnessLevel.Label);
+ Assert.NotNull(decoded.AffirmativeBlack);
+ Assert.Equal("black", decoded.AffirmativeBlack.Label);
+ Assert.NotNull(decoded.AffirmativeLowBrightness);
+ Assert.Equal("low-brightness", decoded.AffirmativeLowBrightness.Label);
+ }
+
+ [Fact]
+ public void DecodeAndApply_writes_has_links_discoverable_via_gated_traverse()
+ {
+ var uks = CreateUks();
+ var ctx = new TraversalContext();
+ Thought outline = uks.GetOrAddThought("Outline-test", "Object");
+ Thought has = uks.Labeled("has");
+
+ DiscreteAttributeDecoder.DecodeAndApply(outline, 255, 165, 0, uks, ctx);
+
+ var links = uks.GetGatedLinks(outline, has, ctx);
+ Assert.Contains(links, l => l.To?.Label == "red-level-8");
+ Assert.Contains(links, l => l.To?.Label == "blue-level-1");
+ Assert.DoesNotContain(links, l => l.To?.Label == "black");
+ }
+
+ [Fact]
+ public void Zero_input_has_links_include_black_not_empty_result()
+ {
+ var uks = CreateUks();
+ var ctx = new TraversalContext();
+ Thought region = uks.GetOrAddThought("dark-region", "Object");
+ Thought has = uks.Labeled("has");
+
+ DiscreteAttributeDecoder.DecodeAndApply(region, 0, 0, 0, uks, ctx);
+
+ var links = uks.GetGatedLinks(region, has, ctx);
+ Assert.NotEmpty(links);
+ Assert.Contains(links, l => l.To?.Label == "black");
+ Assert.Contains(links, l => l.To?.Label == "low-brightness");
+ }
+
+ [Fact]
+ public void Discrete_level_thoughts_carry_isDiscreteLevel_property()
+ {
+ var uks = CreateUks();
+ Thought level = uks.Labeled("red-level-4");
+ Thought? isDiscrete = uks.Labeled("isDiscreteLevel");
+ Assert.NotNull(level);
+ Assert.NotNull(isDiscrete);
+ Assert.True(level.HasProperty(isDiscrete));
+ }
+}
\ No newline at end of file
diff --git a/Tests/GatedTraversalCh4RegressionTests.cs b/Tests/GatedTraversalCh4RegressionTests.cs
new file mode 100644
index 0000000..d0f36e6
--- /dev/null
+++ b/Tests/GatedTraversalCh4RegressionTests.cs
@@ -0,0 +1,101 @@
+/*
+ * Ch.4 relationship-gated traversal regression tests (Simon Atomic Thoughts).
+ * Fido+has vs Fido-only; is-a category traversal.
+ */
+
+using System.Linq;
+using UKS;
+using Xunit;
+
+namespace UKS.Tests;
+
+public class GatedTraversalCh4RegressionTests
+{
+ private static UKS CreateUks()
+ {
+ var uks = new UKS(clear: true);
+ uks.CreateInitialStructure();
+ return uks;
+ }
+
+ [Fact]
+ public void Gated_has_traversal_empty_when_only_Fido_active()
+ {
+ var uks = CreateUks();
+ var ctx = new TraversalContext();
+ uks.AddStatement("dog", "is-a", "Object");
+ uks.AddStatement("fur", "is-a", "Object");
+ uks.AddStatement("dog", "has", "fur");
+ uks.AddStatement("Fido", "is-a", "dog");
+
+ Thought fido = uks.Labeled("Fido");
+ Thought has = uks.Labeled("has");
+
+ ctx.Activate(fido);
+
+ Assert.Empty(uks.GetGatedLinks(fido, has, ctx));
+ Assert.Empty(uks.Traverse(fido, has, ctx));
+ }
+
+ [Fact]
+ public void Gated_has_traversal_returns_fur_when_Fido_and_has_active()
+ {
+ var uks = CreateUks();
+ var ctx = new TraversalContext();
+ uks.AddStatement("dog", "is-a", "Object");
+ uks.AddStatement("fur", "is-a", "Object");
+ uks.AddStatement("dog", "has", "fur");
+ uks.AddStatement("Fido", "is-a", "dog");
+
+ Thought fido = uks.Labeled("Fido");
+ Thought has = uks.Labeled("has");
+ Thought fur = uks.Labeled("fur");
+
+ ctx.Activate(fido);
+ ctx.ActivateRelationship(has);
+
+ var links = uks.GetGatedLinks(fido, has, ctx);
+ Assert.Contains(links, l => l.To == fur);
+
+ var targets = uks.Traverse(fido, has, ctx);
+ Assert.Contains(fur, targets);
+ }
+
+ [Fact]
+ public void Gated_is_a_traversal_returns_dog_when_Fido_and_is_a_active()
+ {
+ var uks = CreateUks();
+ var ctx = new TraversalContext();
+ uks.AddStatement("dog", "is-a", "Object");
+ uks.AddStatement("Fido", "is-a", "dog");
+
+ Thought fido = uks.Labeled("Fido");
+ Thought isA = uks.Labeled("is-a");
+ Thought dog = uks.Labeled("dog");
+
+ ctx.Activate(fido);
+ Assert.Empty(uks.Traverse(fido, isA, ctx));
+
+ ctx.ActivateRelationship(isA);
+ var targets = uks.Traverse(fido, isA, ctx);
+ Assert.Contains(dog, targets);
+ }
+
+ [Fact]
+ public void BeginTraversalCycle_clears_active_context()
+ {
+ var uks = CreateUks();
+ uks.AddStatement("dog", "is-a", "Object");
+ uks.AddStatement("Fido", "is-a", "dog");
+
+ Thought fido = uks.Labeled("Fido");
+ Thought isA = uks.Labeled("is-a");
+
+ uks.CurrentTraversal.Activate(fido);
+ uks.CurrentTraversal.ActivateRelationship(isA);
+ Assert.NotEmpty(uks.Traverse(fido, isA));
+
+ uks.BeginTraversalCycle();
+ Assert.Empty(uks.Traverse(fido, isA));
+ }
+}
\ No newline at end of file
diff --git a/Tests/InheritanceCh5PhaseDRegressionTests.cs b/Tests/InheritanceCh5PhaseDRegressionTests.cs
new file mode 100644
index 0000000..c69751c
--- /dev/null
+++ b/Tests/InheritanceCh5PhaseDRegressionTests.cs
@@ -0,0 +1,198 @@
+/*
+ * Ch.5 Phase D — shortcuts, provenance, multi-parent, taxonomy insert,
+ * context resolver, ephemeral TTL.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using UKS;
+using Xunit;
+
+namespace UKS.Tests;
+
+public class InheritanceCh5PhaseDRegressionTests
+{
+ private static UKS CreateUks()
+ {
+ var uks = new UKS(clear: true);
+ uks.CreateInitialStructure();
+ return uks;
+ }
+
+ private static bool HasLink(List links, string linkTypeLabel, string targetLabel, UKS uks)
+ {
+ Thought target = uks.Labeled(targetLabel);
+ return links.Any(l => l.LinkType?.Label == linkTypeLabel && l.To == target);
+ }
+
+ private static Thought? ResolveLabel(UKS uks, Thought? t)
+ {
+ if (t is null) return null;
+ return uks.Labeled(t.Label) ?? t;
+ }
+
+ private static void EnsureExistOntology(UKS uks)
+ {
+ uks.AddStatement("exist", "is-a", "LinkType");
+ uks.AddStatement("EXIST", "is-a", "exist");
+ uks.AddStatement("exist.is-a", "is-a", "exist");
+ Thought existIsA = uks.Labeled("exist.is-a");
+ Thought isA = uks.Labeled("is-a");
+ existIsA.AddLink(uks.Labeled("is"), isA);
+ }
+
+ [Fact]
+ public void Shortcut_is_a_reaches_physical_object_attributes()
+ {
+ var uks = CreateUks();
+ uks.AddStatement("physical-object", "is-a", "Object");
+ uks.AddStatement("animal", "is-a", "physical-object");
+ uks.AddStatement("dog", "is-a", "animal");
+ uks.AddStatement("dog", "is-a", "physical-object");
+ uks.AddStatement("tangible", "is-a", "Object");
+ uks.AddStatement("physical-object", "has", "tangible");
+ uks.AddStatement("Fido", "is-a", "dog");
+
+ var links = uks.GetAllLinks(new List { uks.Labeled("Fido") });
+ Assert.True(HasLink(links, "has", "tangible", uks));
+ }
+
+ [Fact]
+ public void Inherited_link_records_dog_as_InheritedFromCategory()
+ {
+ var uks = CreateUks();
+ uks.AddStatement("dog", "is-a", "Object");
+ uks.AddStatement("fur", "is-a", "Object");
+ uks.AddStatement("dog", "has", "fur");
+ uks.AddStatement("Fido", "is-a", "dog");
+
+ Thought fido = uks.Labeled("Fido");
+ var links = uks.GetAllLinks(new List { fido });
+ Link inherited = links.First(l => l.To == uks.Labeled("fur"));
+
+ Assert.Equal("dog", inherited.InheritedFromCategory?.Label);
+ Assert.Equal(fido, inherited.From);
+
+ var trace = uks.ExplainInheritance(inherited, fido);
+ Assert.Equal(new[] { "Fido", "dog", "fur" }, trace.Select(t => t.Label).ToArray());
+ }
+
+ [Fact]
+ public void Multiple_is_a_parents_merge_dog_and_pet_attributes()
+ {
+ var uks = CreateUks();
+ uks.AddStatement("dog", "is-a", "Object");
+ uks.AddStatement("pet", "is-a", "Object");
+ uks.AddStatement("fur", "is-a", "Object");
+ uks.AddStatement("owner", "is-a", "Object");
+ uks.AddStatement("dog", "has", "fur");
+ uks.AddStatement("pet", "has", "owner");
+ uks.AddStatement("Fido", "is-a", "dog");
+ uks.AddStatement("Fido", "is-a", "pet");
+
+ var links = uks.GetAllLinks(new List { uks.Labeled("Fido") });
+ Assert.True(HasLink(links, "has", "fur", uks));
+ Assert.True(HasLink(links, "has", "owner", uks));
+ }
+
+ [Fact]
+ public void InsertCategoryBetween_propagates_mammal_attributes_to_dogs()
+ {
+ var uks = CreateUks();
+ uks.AddStatement("animal", "is-a", "Object");
+ uks.AddStatement("dog", "is-a", "animal");
+ uks.AddStatement("mammal", "is-a", "Object");
+ uks.AddStatement("warm-blooded", "is-a", "Object");
+
+ uks.InsertCategoryBetween(uks.Labeled("dog"), uks.Labeled("mammal"), uks.Labeled("animal"));
+ uks.AddStatement("mammal", "has", "warm-blooded");
+ uks.AddStatement("Fido", "is-a", "dog");
+
+ var links = uks.GetAllLinks(new List { uks.Labeled("Fido") });
+ Assert.True(HasLink(links, "has", "warm-blooded", uks));
+ }
+
+ [Fact]
+ public void Located_in_links_get_ephemeral_default_ttl()
+ {
+ var uks = CreateUks();
+ uks.AddStatement("yard", "is-a", "Object");
+ uks.AddStatement("dog", "is-a", "Object");
+ uks.AddStatement("Fido", "is-a", "dog");
+
+ Link located = uks.AddStatement("Fido", "located-in", "yard");
+ Link stable = uks.AddStatement("Fido", "is-a", "dog");
+
+ Assert.Equal(TimeSpan.FromSeconds(30), located.TimeToLive);
+ Assert.Equal(TimeSpan.MaxValue, stable.TimeToLive);
+ }
+
+ [Fact]
+ public void ContextResolver_selects_higher_weight_case()
+ {
+ var uks = CreateUks();
+ EnsureExistOntology(uks);
+
+ Thought fido = uks.GetOrAddThought("Fido", "Object");
+ Thought dog = uks.GetOrAddThought("dog", "Object");
+ Thought pet = uks.GetOrAddThought("pet", "Object");
+ uks.AddStatement("Fido", "is-a", "dog");
+ uks.AddStatement("Fido", "is-a", "pet");
+
+ Thought contextRoot = uks.GetOrAddThought("park", "Context");
+ Thought caseDog = uks.GetOrAddThought("case-dog", contextRoot);
+ Thought casePet = uks.GetOrAddThought("case-pet", contextRoot);
+ Thought has = uks.Labeled("has");
+ Thought existIsA = uks.Labeled("exist.is-a");
+
+ Link dogCheck = new Link(fido, existIsA, dog);
+ Link petCheck = new Link(fido, existIsA, pet);
+ Link dogWrap = caseDog.AddLink(has, dogCheck);
+ Link petWrap = casePet.AddLink(has, petCheck);
+ dogWrap.Weight = 1;
+ petWrap.Weight = 2;
+
+ ContextCaseResult? selected = uks.SelectBestContextCase(contextRoot, t => ResolveLabel(uks, t));
+ Assert.NotNull(selected);
+ Assert.Equal("case-pet", selected.Case?.Label);
+ }
+
+ [Fact]
+ public void FilterLinksByContext_keeps_preferred_category_inherited_links()
+ {
+ var uks = CreateUks();
+ EnsureExistOntology(uks);
+
+ Thought fido = uks.GetOrAddThought("Fido", "Object");
+ Thought dog = uks.GetOrAddThought("dog", "Object");
+ Thought pet = uks.GetOrAddThought("pet", "Object");
+ Thought loud = uks.GetOrAddThought("loud", "Object");
+ Thought calm = uks.GetOrAddThought("calm", "Object");
+ uks.AddStatement("dog", "has", "loud");
+ uks.AddStatement("pet", "has", "calm");
+ uks.AddStatement("Fido", "is-a", "dog");
+ uks.AddStatement("Fido", "is-a", "pet");
+
+ Thought contextRoot = uks.GetOrAddThought("park", "Context");
+ Thought caseDog = uks.GetOrAddThought("case-dog", contextRoot);
+ Thought casePet = uks.GetOrAddThought("case-pet", contextRoot);
+ Thought has = uks.Labeled("has");
+ Thought existIsA = uks.Labeled("exist.is-a");
+
+ Link dogCheck = new Link(fido, existIsA, dog);
+ Link petCheck = new Link(fido, existIsA, pet);
+ Link dogWrap = caseDog.AddLink(has, dogCheck);
+ Link petWrap = casePet.AddLink(has, petCheck);
+ dogWrap.Weight = 1;
+ petWrap.Weight = 2;
+
+ var all = uks.GetAllLinks(new List { fido });
+ Assert.True(HasLink(all, "has", "loud", uks));
+ Assert.True(HasLink(all, "has", "calm", uks));
+
+ var filtered = uks.FilterLinksByContext(all, fido, contextRoot, t => ResolveLabel(uks, t));
+ Assert.True(HasLink(filtered, "has", "calm", uks));
+ Assert.False(HasLink(filtered, "has", "loud", uks));
+ }
+}
\ No newline at end of file
diff --git a/Tests/InheritanceCh5PhaseERegressionTests.cs b/Tests/InheritanceCh5PhaseERegressionTests.cs
new file mode 100644
index 0000000..4c6e80f
--- /dev/null
+++ b/Tests/InheritanceCh5PhaseERegressionTests.cs
@@ -0,0 +1,167 @@
+/*
+ * Ch.5 Phase E — bubble on learn, ExplainLink, Fido demo content.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using UKS;
+using Xunit;
+
+namespace UKS.Tests;
+
+public class InheritanceCh5PhaseERegressionTests
+{
+ private static UKS CreateUks()
+ {
+ var uks = new UKS(clear: true);
+ uks.CreateInitialStructure();
+ return uks;
+ }
+
+ private static string DemoPath()
+ {
+ string fromTest = Path.Combine(
+ Directory.GetCurrentDirectory(),
+ "..", "..", "..", "..",
+ "BrainSimulator", "UKSContent", "Ch4Ch5-FidoDemo.txt");
+ if (File.Exists(fromTest)) return Path.GetFullPath(fromTest);
+ return Path.GetFullPath(Path.Combine(
+ AppContext.BaseDirectory,
+ "..", "..", "..", "..", "..",
+ "BrainSimulator", "UKSContent", "Ch4Ch5-FidoDemo.txt"));
+ }
+
+ [Fact]
+ public void LinkAdded_fires_on_AddStatement()
+ {
+ var uks = CreateUks();
+ int count = 0;
+ uks.LinkAdded += _ => count++;
+ uks.AddStatement("dog", "is-a", "Object");
+ Assert.Equal(1, count);
+ }
+
+ [Fact]
+ public void BubbleSharedAttributes_majority_bubbles_to_parent()
+ {
+ var uks = CreateUks();
+ Thought animal = uks.GetOrAddThought("Animal", "Object");
+ Thought dog = uks.GetOrAddThought("Dog", animal);
+ Thought cat = uks.GetOrAddThought("Cat", animal);
+ Thought bird = uks.GetOrAddThought("Bird", animal);
+ Thought fur = uks.GetOrAddThought("fur", "Object");
+ Thought has = uks.Labeled("has");
+
+ dog.AddLink(has, fur).Weight = 1f;
+ cat.AddLink(has, fur).Weight = 1f;
+ bird.AddLink(has, fur).Weight = 1f;
+
+ Assert.True(uks.BubbleSharedAttributes(animal));
+ Assert.NotNull(animal.HasLink(has, fur));
+ Assert.Null(dog.HasLink(has, fur));
+ }
+
+ [Fact]
+ public void BubbleLog_records_bubble_action()
+ {
+ var uks = CreateUks();
+ Thought group = uks.GetOrAddThought("Group", "Object");
+ Thought a = uks.GetOrAddThought("A", group);
+ Thought b = uks.GetOrAddThought("B", group);
+ Thought flag = uks.GetOrAddThought("flag", "Object");
+ Thought has = uks.Labeled("has");
+ a.AddLink(has, flag).Weight = 1f;
+ b.AddLink(has, flag).Weight = 1f;
+
+ uks.BubbleSharedAttributes(group);
+ Assert.Contains(uks.BubbleLog, e => e.Action == "bubble" && e.TargetLabel == "flag");
+ }
+
+ [Fact]
+ public void TryFormCategoryFromChildren_returns_parent_when_bubble_succeeds()
+ {
+ var uks = CreateUks();
+ Thought vehicle = uks.GetOrAddThought("Vehicle", "Object");
+ Thought car = uks.GetOrAddThought("Car", vehicle);
+ Thought truck = uks.GetOrAddThought("Truck", vehicle);
+ Thought engine = uks.GetOrAddThought("engine", "Object");
+ Thought has = uks.Labeled("has");
+ car.AddLink(has, engine).Weight = 1f;
+ truck.AddLink(has, engine).Weight = 1f;
+
+ Thought? formed = uks.TryFormCategoryFromChildren(vehicle);
+ Assert.Same(vehicle, formed);
+ }
+
+ [Fact]
+ public void ExplainLink_traces_Fido_dog_fur()
+ {
+ var uks = CreateUks();
+ uks.AddStatement("dog", "is-a", "Object");
+ uks.AddStatement("fur", "is-a", "Object");
+ uks.AddStatement("dog", "has", "fur");
+ uks.AddStatement("Fido", "is-a", "dog");
+
+ Thought fido = uks.Labeled("Fido");
+ Link inherited = uks.GetAllLinks(new List { fido })
+ .First(l => l.To == uks.Labeled("fur"));
+
+ var trace = uks.ExplainLink(inherited, fido);
+ Assert.Equal(new[] { "Fido", "dog", "fur" }, trace.Select(t => t.Label).ToArray());
+ }
+
+ [Fact]
+ public void LinkAdded_handler_can_bubble_on_learn()
+ {
+ var uks = CreateUks();
+ uks.LinkAdded += lnk =>
+ {
+ if (lnk.From is null) return;
+ foreach (Thought parent in lnk.From.Parents)
+ uks.BubbleSharedAttributes(parent);
+ };
+
+ Thought animal = uks.GetOrAddThought("Animal", "Object");
+ Thought dog = uks.GetOrAddThought("Dog", animal);
+ Thought cat = uks.GetOrAddThought("Cat", animal);
+ Thought bird = uks.GetOrAddThought("Bird", animal);
+ Thought fur = uks.GetOrAddThought("fur", "Object");
+ Thought has = uks.Labeled("has");
+ dog.AddLink(has, fur).Weight = 1f;
+ cat.AddLink(has, fur).Weight = 1f;
+
+ uks.AddStatement("Bird", "has", "fur");
+ Assert.NotNull(animal.HasLink(has, fur));
+ }
+
+ [Fact]
+ public void Ch4Ch5_FidoDemo_txt_loads_and_validates_patterns()
+ {
+ var uks = CreateUks();
+ string path = DemoPath();
+ Assert.True(File.Exists(path), $"Demo not found at {path}");
+ uks.ImportTextFile(path);
+
+ Thought fido = uks.Labeled("Fido");
+ Thought tripper = uks.Labeled("Tripper");
+ Thought has = uks.Labeled("has");
+ Assert.NotNull(fido);
+ Assert.NotNull(tripper);
+
+ var fidoLinks = uks.GetAllLinks(new List { fido });
+ Assert.Contains(fidoLinks, l => l.LinkType == has && l.To == uks.Labeled("fur"));
+ Assert.Contains(fidoLinks, l => l.To == uks.Labeled("owner"));
+
+ var tripperLinks = uks.GetAllLinks(new List { tripper });
+ Assert.Contains(tripperLinks, l => l.LinkType?.Label == "has.3");
+ Assert.DoesNotContain(tripperLinks, l => l.LinkType?.Label == "has.4");
+
+ var ctx = new TraversalContext();
+ ctx.Activate(fido);
+ Assert.Empty(uks.GetGatedLinks(fido, has, ctx));
+ ctx.ActivateRelationship(has);
+ Assert.Contains(uks.GetGatedLinks(fido, has, ctx), l => l.To == uks.Labeled("fur"));
+ }
+}
\ No newline at end of file
diff --git a/Tests/InheritanceCh5RegressionTests.cs b/Tests/InheritanceCh5RegressionTests.cs
new file mode 100644
index 0000000..cf4980f
--- /dev/null
+++ b/Tests/InheritanceCh5RegressionTests.cs
@@ -0,0 +1,160 @@
+/*
+ * Ch.5 inheritance regression tests (Simon Atomic Thoughts).
+ * Fido/dog/fur, Tripper three-legs exception, transitive is-a chains.
+ */
+
+using System.Collections.Generic;
+using System.Linq;
+using UKS;
+using Xunit;
+
+namespace UKS.Tests;
+
+public class InheritanceCh5RegressionTests
+{
+ private static UKS CreateUks()
+ {
+ var uks = new UKS(clear: true);
+ uks.CreateInitialStructure();
+ return uks;
+ }
+
+ private static bool HasLink(List links, string linkTypeLabel, string targetLabel, UKS uks)
+ {
+ Thought target = uks.Labeled(targetLabel);
+ return links.Any(l => l.LinkType?.Label == linkTypeLabel && l.To == target);
+ }
+
+ [Fact]
+ public void Fido_inherits_dog_fur_via_is_a_traversal()
+ {
+ var uks = CreateUks();
+ uks.AddStatement("animal", "is-a", "Object");
+ uks.AddStatement("dog", "is-a", "animal");
+ uks.AddStatement("fur", "is-a", "Object");
+ uks.AddStatement("dog", "has", "fur");
+ uks.AddStatement("Fido", "is-a", "dog");
+
+ var links = uks.GetAllLinks(new List { uks.Labeled("Fido") });
+
+ Assert.True(HasLink(links, "has", "fur", uks));
+ }
+
+ [Fact]
+ public void Fido_inherits_four_legs_from_dog_without_local_copy()
+ {
+ var uks = CreateUks();
+ uks.AddStatement("dog", "is-a", "Object");
+ uks.AddStatement("legs", "is-a", "Object");
+ uks.AddStatement("has.4", "is-a", "has");
+ uks.AddStatement("dog", "has.4", "legs");
+ uks.AddStatement("Fido", "is-a", "dog");
+
+ var links = uks.GetAllLinks(new List { uks.Labeled("Fido") });
+
+ Assert.True(HasLink(links, "has.4", "legs", uks));
+ }
+
+ [Fact]
+ public void Tripper_local_three_legs_overrides_inherited_four_legs()
+ {
+ var uks = CreateUks();
+ uks.AddStatement("dog", "is-a", "Object");
+ uks.AddStatement("legs", "is-a", "Object");
+ uks.AddStatement("has.4", "is-a", "has");
+ uks.AddStatement("has.3", "is-a", "has");
+ uks.AddStatement("dog", "has.4", "legs");
+ uks.AddStatement("Tripper", "is-a", "dog");
+ uks.AddStatement("Tripper", "has.3", "legs");
+
+ var links = uks.GetAllLinks(new List { uks.Labeled("Tripper") });
+
+ Assert.True(HasLink(links, "has.3", "legs", uks));
+ Assert.False(HasLink(links, "has.4", "legs", uks));
+ }
+
+ [Fact]
+ public void Tripper_exception_wins_when_local_link_added_after_category_default()
+ {
+ var uks = CreateUks();
+ uks.AddStatement("dog", "is-a", "Object");
+ uks.AddStatement("legs", "is-a", "Object");
+ uks.AddStatement("has.4", "is-a", "has");
+ uks.AddStatement("has.3", "is-a", "has");
+ uks.AddStatement("Tripper", "is-a", "dog");
+ uks.AddStatement("dog", "has.4", "legs");
+ uks.AddStatement("Tripper", "has.3", "legs");
+
+ var links = uks.GetAllLinks(new List { uks.Labeled("Tripper") });
+
+ Assert.True(HasLink(links, "has.3", "legs", uks));
+ Assert.False(HasLink(links, "has.4", "legs", uks));
+ }
+
+ [Fact]
+ public void Deep_is_a_chain_inherits_category_attribute()
+ {
+ var uks = CreateUks();
+ uks.AddStatement("living", "is-a", "Object");
+ uks.AddStatement("animal", "is-a", "living");
+ uks.AddStatement("dog", "is-a", "animal");
+ uks.AddStatement("alive", "is-a", "Object");
+ uks.AddStatement("living", "has", "alive");
+ uks.AddStatement("Fido", "is-a", "dog");
+
+ var links = uks.GetAllLinks(new List { uks.Labeled("Fido") });
+
+ Assert.True(HasLink(links, "has", "alive", uks));
+ }
+
+ [Fact]
+ public void Inheritance_stops_beyond_max_hops()
+ {
+ var uks = CreateUks();
+ const int chainLength = 12; // chain0 -> chain12 is 12 hops; default maxHops = 8
+ Thought prev = uks.GetOrAddThought("chain0", "Object");
+ for (int i = 1; i <= chainLength; i++)
+ {
+ Thought next = uks.GetOrAddThought($"chain{i}", "Object");
+ uks.AddStatement(prev.Label, "is-a", next.Label);
+ prev = next;
+ }
+ uks.AddStatement("deepFact", "is-a", "Object");
+ uks.AddStatement(prev.Label, "has", "deepFact");
+
+ var links = uks.GetAllLinks(new List { uks.Labeled("chain0") });
+
+ Assert.False(HasLink(links, "has", "deepFact", uks));
+ }
+
+ [Fact]
+ public void Inherited_attributes_have_positive_inheritance_depth()
+ {
+ var uks = CreateUks();
+ uks.AddStatement("dog", "is-a", "Object");
+ uks.AddStatement("fur", "is-a", "Object");
+ uks.AddStatement("dog", "has", "fur");
+ uks.AddStatement("Fido", "is-a", "dog");
+
+ var links = uks.GetAllLinks(new List { uks.Labeled("Fido") });
+ Link inherited = links.First(l => l.LinkType?.Label == "has" && l.To == uks.Labeled("fur"));
+
+ Assert.True(inherited.InheritanceDepth > 0);
+ }
+
+ [Fact]
+ public void Local_exception_link_has_zero_inheritance_depth()
+ {
+ var uks = CreateUks();
+ uks.AddStatement("dog", "is-a", "Object");
+ uks.AddStatement("legs", "is-a", "Object");
+ uks.AddStatement("has.3", "is-a", "has");
+ uks.AddStatement("Tripper", "is-a", "dog");
+ uks.AddStatement("Tripper", "has.3", "legs");
+
+ var links = uks.GetAllLinks(new List { uks.Labeled("Tripper") });
+ Link local = links.First(l => l.LinkType?.Label == "has.3");
+
+ Assert.Equal(0, local.InheritanceDepth);
+ }
+}
\ No newline at end of file
diff --git a/Tests/ModuleAlgorithm.Tests.cs b/Tests/ModuleAlgorithm.Tests.cs
index 66a1073..3bc8ff0 100644
--- a/Tests/ModuleAlgorithm.Tests.cs
+++ b/Tests/ModuleAlgorithm.Tests.cs
@@ -11,63 +11,63 @@
* See the LICENSE file in the project root for full license information.
*/
-using System;
-using System.IO;
using BrainSimulator.Modules;
using UKS;
using Xunit;
namespace UKS.Tests;
-public class ModuleAlgorithmTests : IDisposable
+public class ModuleAlgorithmTests : IClassFixture
{
- private UKS uks;
- private ModuleAlgorithm module;
+ private readonly UKS uks;
+ private readonly ModuleAlgorithm module;
- public ModuleAlgorithmTests()
+ public ModuleAlgorithmTests(AlgorithmXmlFixture fixture)
{
- uks = new UKS();
+ uks = new UKS(clear: true);
uks.CreateInitialStructure();
-
+ UKS.theUKS = uks;
+ Thought.ClearRecentlyFiredQueue();
+
module = new ModuleAlgorithm();
module.theUKS = uks;
+ module.UKSInitializedNotification();
- string currentDir = Directory.GetCurrentDirectory();
- string brainSimRoot = FindBrainSimRoot(currentDir);
-
-
- // Load algorithm.xml from UKSContent folder
- string xmlPath = Path.Combine(brainSimRoot,"brainsimulator", "UKSContent", "algorithm.xml");
+ string xmlPath = FindAlgorithmXmlPath();
if (!File.Exists(xmlPath))
{
- // Try alternative path
- xmlPath = Path.Combine("UKSContent", "algorithm.xml");
- }
-
- if (File.Exists(xmlPath))
- {
- uks.LoadUKSfromXMLFile(xmlPath);
+ throw new FileNotFoundException($"Algorithm.xml not found at {xmlPath}");
}
- else
+
+ uks.LoadUKSfromXMLFile(xmlPath);
+ }
+
+ private static string FindRepositoryRoot()
+ {
+ DirectoryInfo directory = new(AppContext.BaseDirectory);
+ while (directory is not null
+ && !File.Exists(Path.Combine(directory.FullName, "BrainSim Thought.sln")))
{
- throw new FileNotFoundException($"algorithm.xml not found. Searched paths.");
+ directory = directory.Parent;
}
+
+ return directory?.FullName
+ ?? throw new DirectoryNotFoundException("BrainSim Thought repository root not found.");
}
- private string FindBrainSimRoot(string startPath)
- {
- DirectoryInfo dir = new DirectoryInfo(startPath);
- while (dir != null)
+ private static string FindAlgorithmXmlPath()
+ {
+ string contentDir = Path.Combine(FindRepositoryRoot(), "BrainSimulator", "UKSContent");
+ foreach (string name in new[] { "Algorithm.xml", "algorithm.xml" })
{
- // Check if current directory name is "BrainSim Thought"
- if (dir.Name.Equals("BrainSim Thought", StringComparison.OrdinalIgnoreCase))
+ string candidate = Path.Combine(contentDir, name);
+ if (File.Exists(candidate))
{
- return dir.FullName;
+ return candidate;
}
- dir = dir.Parent;
}
- return null;
+ return Path.Combine(contentDir, "Algorithm.xml");
}
public void Dispose()
{
diff --git a/Tests/ModuleAttributeBubbleTests.cs b/Tests/ModuleAttributeBubbleTests.cs
index 572f501..ba349c5 100644
--- a/Tests/ModuleAttributeBubbleTests.cs
+++ b/Tests/ModuleAttributeBubbleTests.cs
@@ -124,8 +124,8 @@ public void BubbleChildAttributes_RemovesFromChildren()
module.DoTheWork();
// Assert
- Assert.False(dog.LinksTo.Any(l => l.LinkType == breathes && l.To == air));
- Assert.False(cat.LinksTo.Any(l => l.LinkType == breathes && l.To == air));
+ Assert.DoesNotContain(dog.LinksTo, l => l.LinkType == breathes && l.To == air);
+ Assert.DoesNotContain(cat.LinksTo, l => l.LinkType == breathes && l.To == air);
}
[Fact]
diff --git a/Tests/ModuleMentelModelTests.cs b/Tests/ModuleMentelModelTests.cs
index 3410a20..7d2f76f 100644
--- a/Tests/ModuleMentelModelTests.cs
+++ b/Tests/ModuleMentelModelTests.cs
@@ -35,7 +35,7 @@ public void RotateMentalModel_MovesContainsLinkToNewCell()
// assert: the object should now be bound to the rotated cell
Thought expectedCell = module.GetCell(Angle.FromDegrees(20), Angle.FromDegrees(20));
- Link? containsAfter = expectedCell.LinksTo
+ Link containsAfter = expectedCell.LinksTo
.FirstOrDefault(l => l.LinkType?.Label == "_mm:contains" && l.To == obj);
Assert.NotNull(containsAfter);
diff --git a/Tests/NatureAnimalsXmlTests.cs b/Tests/NatureAnimalsXmlTests.cs
new file mode 100644
index 0000000..3954922
--- /dev/null
+++ b/Tests/NatureAnimalsXmlTests.cs
@@ -0,0 +1,87 @@
+/*
+ * Brain Simulator Thought — NatureAnimals.xml import verification
+ */
+
+using System.IO;
+using UKS;
+using Xunit;
+
+namespace UKS.Tests;
+
+public class NatureAnimalsXmlTests
+{
+ private static string XmlPath()
+ {
+ string fromTest = Path.Combine(
+ Directory.GetCurrentDirectory(),
+ "..", "..", "..", "..",
+ "BrainSimulator", "UKSContent", "NatureAnimals.xml");
+ if (File.Exists(fromTest)) return Path.GetFullPath(fromTest);
+
+ string fromRepo = Path.Combine(
+ AppContext.BaseDirectory,
+ "..", "..", "..", "..", "..",
+ "BrainSimulator", "UKSContent", "NatureAnimals.xml");
+ return Path.GetFullPath(fromRepo);
+ }
+
+ private static UKS LoadNatureUks()
+ {
+ var uks = new UKS(clear: true);
+ string path = XmlPath();
+ Assert.True(File.Exists(path), $"NatureAnimals.xml not found at {path}");
+ Assert.True(uks.LoadUKSfromXMLFile(path));
+ return uks;
+ }
+
+ private static void AssertLink(UKS uks, string from, string linkType, string to)
+ {
+ Thought f = uks.Labeled(from);
+ Thought lt = uks.Labeled(linkType);
+ Thought t = uks.Labeled(to);
+ Assert.NotNull(f);
+ Assert.NotNull(lt);
+ Assert.NotNull(t);
+ Assert.NotNull(f.HasLink(lt, t));
+ }
+
+ [Fact]
+ public void NatureAnimals_LoadsFromXml()
+ {
+ var uks = LoadNatureUks();
+ Assert.NotNull(uks.Labeled("Fido"));
+ Assert.NotNull(uks.Labeled("mammal"));
+ Assert.True(uks.AtomicThoughts.Count > 100);
+ }
+
+ [Fact]
+ public void NatureAnimals_FidoPattern()
+ {
+ var uks = LoadNatureUks();
+ AssertLink(uks, "Fido", "is-a", "dog");
+ AssertLink(uks, "Fido", "is", "brown");
+ AssertLink(uks, "Fido", "has.4", "leg");
+ AssertLink(uks, "dog", "has.4", "leg");
+ }
+
+ [Fact]
+ public void NatureAnimals_TripperException()
+ {
+ var uks = LoadNatureUks();
+ AssertLink(uks, "Tripper", "is-a", "dog");
+ AssertLink(uks, "Tripper", "has.3", "leg");
+ }
+
+ [Fact]
+ public void NatureAnimals_TaxonomyAndCrossSpecies()
+ {
+ var uks = LoadNatureUks();
+ AssertLink(uks, "dog", "is-a", "mammal");
+ AssertLink(uks, "eagle", "is-a", "bird");
+ AssertLink(uks, "salmon", "is-a", "fish");
+ AssertLink(uks, "dog", "isSimilarTo", "wolf");
+ AssertLink(uks, "eagle", "predatorOf", "mouse");
+ AssertLink(uks, "whale", "differsFrom", "fish");
+ AssertLink(uks, "penguin", "livesIn", "antarctica");
+ }
+}
\ No newline at end of file
diff --git a/Tests/NonUksP0FixesRegressionTests.cs b/Tests/NonUksP0FixesRegressionTests.cs
new file mode 100644
index 0000000..ee52fe2
--- /dev/null
+++ b/Tests/NonUksP0FixesRegressionTests.cs
@@ -0,0 +1,38 @@
+/*
+ * Regression tests for P0 non-UKS fixes (2026-07-07).
+ */
+
+using BrainSimulator;
+using Xunit;
+
+namespace UKS.Tests;
+
+public class NonUksP0FixesRegressionTests
+{
+ [Fact]
+ public void HSLColor_Equals_does_not_merge_hues_beyond_threshold_across_wrap()
+ {
+ var a = new HSLColor(350f, 0.8f, 0.5f);
+ var b = new HSLColor(10f, 0.8f, 0.5f);
+
+ Assert.False(a.Equals(b));
+ }
+
+ [Fact]
+ public void HSLColor_Equals_distinguishes_visually_different_hues()
+ {
+ var a = new HSLColor(30f, 0.8f, 0.5f);
+ var b = new HSLColor(200f, 0.8f, 0.5f);
+
+ Assert.False(a.Equals(b));
+ }
+
+ [Fact]
+ public void HSLColor_Equals_treats_near_wrap_pairs_as_equal()
+ {
+ var a = new HSLColor(2f, 0.8f, 0.5f);
+ var b = new HSLColor(358f, 0.8f, 0.5f);
+
+ Assert.True(a.Equals(b));
+ }
+}
\ No newline at end of file
diff --git a/Tests/NonUksP1FixesRegressionTests.cs b/Tests/NonUksP1FixesRegressionTests.cs
new file mode 100644
index 0000000..384dad1
--- /dev/null
+++ b/Tests/NonUksP1FixesRegressionTests.cs
@@ -0,0 +1,39 @@
+/*
+ * Regression tests for P1 non-UKS fixes (2026-07-07).
+ */
+
+using BrainSimulator;
+using UKS;
+using Xunit;
+
+namespace UKS.Tests;
+
+public class NonUksP1FixesRegressionTests
+{
+ [Fact]
+ public void Broadcast_does_not_throw_when_ipv4_unavailable()
+ {
+ Exception ex = Record.Exception(() => Network.Broadcast("brainsim-test"));
+ Assert.Null(ex);
+ }
+
+ [Fact]
+ public void DeactivateModule_preserves_shared_link_targets()
+ {
+ var uks = new global::UKS.UKS(clear: true);
+ uks.CreateInitialStructure();
+ var handler = new ModuleHandler();
+
+ Thought shared = uks.GetOrAddThought("SharedConfig", "Object");
+ Thought moduleType = uks.GetOrAddThought("ModuleTest", uks.Labeled("AvailableModule"));
+ Thought instance = uks.CreateInstanceOf(moduleType);
+ uks.AddStatement(instance, "is", shared);
+
+ string instanceLabel = instance.Label;
+ handler.DeactivateModule(instanceLabel);
+
+ Assert.NotNull(uks.Labeled("SharedConfig"));
+ Assert.NotNull(uks.Labeled(moduleType.Label));
+ Assert.Null(uks.Labeled(instanceLabel));
+ }
+}
\ No newline at end of file
diff --git a/Tests/NonUksP2FixesRegressionTests.cs b/Tests/NonUksP2FixesRegressionTests.cs
new file mode 100644
index 0000000..b00b91a
--- /dev/null
+++ b/Tests/NonUksP2FixesRegressionTests.cs
@@ -0,0 +1,57 @@
+/*
+ * Regression tests for P2 non-UKS fixes (2026-07-07).
+ */
+
+using System.IO;
+using UKS;
+using Xunit;
+
+namespace UKS.Tests;
+
+public class NonUksP2FixesRegressionTests
+{
+ [Fact]
+ public void Thought_GetTargetOfFirstLinkOfType_string_overload()
+ {
+ var uks = new global::UKS.UKS(clear: true);
+ uks.CreateInitialStructure();
+ Thought parent = uks.GetOrAddThought("phrase1", "Object");
+ Thought linkType = uks.GetOrAddThought("soundAs", "LinkType");
+ var seq = uks.AddSequenceAndLink(parent, linkType, new List
+ {
+ uks.GetOrAddThought("note1", "Object"),
+ uks.GetOrAddThought("note2", "Object"),
+ });
+ Assert.NotNull(seq);
+ Thought target = parent.GetTargetOfFirstLinkOfType("soundAs");
+ Assert.Same(seq, target);
+ }
+
+ [Fact]
+ public void Bundled_wordlist_resource_exists()
+ {
+ string repoRoot = FindRepoRoot();
+ string wordlist = Path.Combine(repoRoot, "BrainSimulator", "Resources", "wordlist.txt");
+ Assert.True(File.Exists(wordlist));
+ }
+
+ [Fact]
+ public void Legacy_sources_moved_to_archive()
+ {
+ string repoRoot = FindRepoRoot();
+ Assert.True(File.Exists(Path.Combine(repoRoot, "BrainSimulator", "archive", "legacy", "XmlFile.cs")));
+ Assert.False(File.Exists(Path.Combine(repoRoot, "BrainSimulator", "XmlFile.cs")));
+ }
+
+ private static string FindRepoRoot()
+ {
+ var dir = new DirectoryInfo(Directory.GetCurrentDirectory());
+ while (dir is not null)
+ {
+ if (File.Exists(Path.Combine(dir.FullName, "BrainSim Thought.sln")))
+ return dir.FullName;
+ dir = dir.Parent;
+ }
+ throw new DirectoryNotFoundException("BrainSim Thought repo root not found.");
+ }
+}
\ No newline at end of file
diff --git a/Tests/NonUksRemainingFixesRegressionTests.cs b/Tests/NonUksRemainingFixesRegressionTests.cs
new file mode 100644
index 0000000..54ed422
--- /dev/null
+++ b/Tests/NonUksRemainingFixesRegressionTests.cs
@@ -0,0 +1,57 @@
+/*
+ * Regression tests for remaining P1 (#9-10) and P3 (#19-23) non-UKS fixes (2026-07-07).
+ */
+
+using System.IO;
+using System.Reflection;
+using System.Threading.Tasks;
+using BrainSimulator;
+using BrainSimulator.Modules;
+using Xunit;
+
+namespace UKS.Tests;
+
+public class NonUksRemainingFixesRegressionTests
+{
+ [Fact]
+ public void GetStringFromThoughtLabel_empty_returns_empty()
+ {
+ var method = typeof(ModuleGPTInfo).GetMethod(
+ "GetStringFromThoughtLabel",
+ BindingFlags.NonPublic | BindingFlags.Static);
+ Assert.NotNull(method);
+ var result = (string)method!.Invoke(null, new object[] { "" })!;
+ Assert.Equal("", result);
+ }
+
+ [Fact]
+ public void HSLColor_ToColor_hue345_returns_defined_color()
+ {
+ var hsl = new HSLColor(345f, 0.8f, 0.5f);
+ var color = hsl.ToColor();
+ Assert.InRange(color.R + color.G + color.B, 1, 765);
+ }
+
+ [Theory]
+ [InlineData(nameof(ModuleOnlineInfo.GetKidsWordsmythNet))]
+ [InlineData(nameof(ModuleOnlineInfo.GetWiktionaryData))]
+ [InlineData(nameof(ModuleOnlineInfo.GetChatGPTResult))]
+ [InlineData(nameof(ModuleOnlineInfo.GetWikidataData))]
+ [InlineData(nameof(ModuleOnlineInfo.GetFreeDictionaryAPIData))]
+ [InlineData(nameof(ModuleOnlineInfo.GetWebstersDictionaryAPIData))]
+ public void ModuleOnlineInfo_public_fetch_methods_return_task(string methodName)
+ {
+ var method = typeof(ModuleOnlineInfo).GetMethod(methodName);
+ Assert.NotNull(method);
+ Assert.Equal(typeof(Task), method!.ReturnType);
+ }
+
+ [Fact]
+ public void ModuleBalanceTreeDlg_code_behind_uses_xaml_cs_suffix()
+ {
+ var repoRoot = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "..", "..", "..", ".."));
+ var path = Path.Combine(repoRoot, "BrainSimulator", "Modules", "Agents", "ModuleBalanceTreeDlg.xaml.cs");
+ Assert.True(File.Exists(path), $"Expected code-behind at {path}");
+ Assert.False(File.Exists(path.Replace(".xaml.cs", ".xml.cs")));
+ }
+}
\ No newline at end of file
diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj
index f041009..88be3a4 100644
--- a/Tests/Tests.csproj
+++ b/Tests/Tests.csproj
@@ -4,7 +4,7 @@
net8.0-windows
true
enable
- enable
+ disable
@@ -13,7 +13,7 @@
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
@@ -25,4 +25,8 @@
+
+
+
+
\ No newline at end of file
diff --git a/Tests/UKSFixesRegressionTests.cs b/Tests/UKSFixesRegressionTests.cs
new file mode 100644
index 0000000..eca3da4
--- /dev/null
+++ b/Tests/UKSFixesRegressionTests.cs
@@ -0,0 +1,118 @@
+/*
+ * Regression tests for UKS fixes (2026-07-07 inspection)
+ */
+
+using System.IO;
+using UKS;
+using Xunit;
+
+namespace UKS.Tests;
+
+public class UKSFixesRegressionTests
+{
+ private static UKS CreateUks() { var uks = new UKS(clear: true); uks.CreateInitialStructure(); return uks; }
+
+ [Fact]
+ public void LinksAreExclusive_CommonParentSources_AreExclusive()
+ {
+ var uks = CreateUks();
+ uks.AddStatement("animal", "is-a", "Object");
+ uks.AddStatement("dog", "is-a", "animal");
+ uks.AddStatement("cat", "is-a", "animal");
+ uks.AddStatement("legs", "is-a", "Object");
+ uks.AddStatement("has.4", "is-a", "has");
+ uks.AddStatement("has.3", "is-a", "has");
+
+ var dogLegs = uks.GetLink(uks.AddStatement("dog", "has.4", "legs"));
+ var catLegs = uks.GetLink(uks.AddStatement("cat", "has.3", "legs"));
+
+ Assert.NotNull(dogLegs);
+ Assert.NotNull(catLegs);
+ Assert.True(uks.LinksAreExclusive_ForTests(dogLegs, catLegs));
+ }
+
+ [Fact]
+ public void ChildrenWithSubclasses_UsesIsA_NotLabelPrefix()
+ {
+ var uks = CreateUks();
+ uks.AddStatement("dog", "is-a", "Object");
+ uks.AddStatement("doghouse", "is-a", "Object");
+ uks.AddStatement("puppy", "is-a", "dog");
+
+ var dog = uks.Labeled("dog");
+ var doghouse = uks.Labeled("doghouse");
+ var puppy = uks.Labeled("puppy");
+ var children = dog.ChildrenWithSubclasses;
+
+ Assert.Contains(puppy, children);
+ Assert.DoesNotContain(doghouse, children);
+ }
+
+ [Fact]
+ public void AddStatement_WiresLabeledPlaceholder()
+ {
+ var uks = CreateUks();
+ uks.AddStatement("dog", "is-a", "Object");
+ uks.AddStatement("Fido", "is-a", "dog");
+
+ var placeholder = new Link { Label = "mylink" };
+ uks.AtomicThoughts.Add(placeholder);
+
+ var link = uks.AddStatement(uks.Labeled("Fido"), uks.Labeled("is-a"), uks.Labeled("dog"), "mylink");
+
+ Assert.NotNull(link);
+ Assert.Same(uks.Labeled("Fido"), link.From);
+ Assert.NotNull(link.From.HasLink(uks.Labeled("is-a"), uks.Labeled("dog")));
+ }
+
+ [Fact]
+ public void XmlRoundTrip_PreservesLink()
+ {
+ var uks = CreateUks();
+ uks.AddStatement("dog", "is-a", "Object");
+ uks.AddStatement("Fido", "is-a", "dog");
+
+ string path = Path.Combine(Path.GetTempPath(), $"uks_roundtrip_{Guid.NewGuid():N}.xml");
+ try
+ {
+ Assert.True(uks.SaveUKStoXMLFile(path));
+
+ var uks2 = new UKS(clear: true);
+ Assert.True(uks2.LoadUKSfromXMLFile(path));
+
+ Assert.NotNull(uks2.Labeled("Fido"));
+ Assert.NotNull(uks2.GetLink(uks2.Labeled("Fido"), uks2.Labeled("is-a"), uks2.Labeled("dog")));
+ }
+ finally
+ {
+ if (File.Exists(path)) File.Delete(path);
+ }
+ }
+
+ [Fact]
+ public void ExportTextFile_ThrowsOnInvalidPath()
+ {
+ var uks = CreateUks();
+ uks.AddStatement("dog", "is-a", "Object");
+ Assert.Throws(() =>
+ uks.ExportTextFile("Object", "/nonexistent_path_zzzz/export.txt"));
+ }
+
+ [Fact]
+ public void HasSequence_UnsupportedFlagsThrow()
+ {
+ var uks = CreateUks();
+ var targets = new List { uks.GetOrAddThought("A"), uks.GetOrAddThought("B") };
+ Assert.Throws(() =>
+ uks.HasSequence(targets, uks.Labeled("spelled"), circularSearch: true));
+ }
+
+ [Fact]
+ public void CreateMinimumStructure_RegistersNxtNotNxe()
+ {
+ var uks = new UKS(clear: true);
+ uks.CreateMinimumStructureForTests();
+ Assert.NotNull(uks.Labeled("NXT"));
+ Assert.Null(uks.Labeled("NXE"));
+ }
+}
\ No newline at end of file
diff --git a/UKS.Tests/AssemblyInfo.cs b/UKS.Tests/AssemblyInfo.cs
new file mode 100644
index 0000000..41a898e
--- /dev/null
+++ b/UKS.Tests/AssemblyInfo.cs
@@ -0,0 +1,3 @@
+using Xunit;
+
+[assembly: CollectionBehavior(DisableTestParallelization = true)]
\ No newline at end of file
diff --git a/UKS.Tests/UKS.Tests.csproj b/UKS.Tests/UKS.Tests.csproj
index f4ac789..3efa8bd 100644
--- a/UKS.Tests/UKS.Tests.csproj
+++ b/UKS.Tests/UKS.Tests.csproj
@@ -2,15 +2,35 @@
net8.0
false
+ enable
+ disable
-
-
-
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/UKS/IReadOnlyListExtensiion.cs b/UKS/IReadOnlyListExtension.cs
similarity index 100%
rename from UKS/IReadOnlyListExtensiion.cs
rename to UKS/IReadOnlyListExtension.cs
diff --git a/UKS/Thought.cs b/UKS/Thought.cs
index 17f4e86..a26a880 100644
--- a/UKS/Thought.cs
+++ b/UKS/Thought.cs
@@ -39,6 +39,12 @@ public Link(Thought from, Thought linkType, Thought to)
public Thought? LinkType { get; set; }
public Thought? To { get; set; }
+ /// Query-time metadata: 0 = asserted on the query source; N = inherited via N is-a hops.
+ public int InheritanceDepth { get; set; }
+
+ /// Ch.5 provenance: category Thought where an inherited link was found (e.g. dog for Fido→fur).
+ public Thought? InheritedFromCategory { get; set; }
+
///
/// Returns a formatted string for the link, showing sequence notation or From→Type→To.
///
@@ -58,11 +64,12 @@ public override string ToString()
/// A Thought is an atomic unit of thought. In the lexicon of graphs, a Thought is both a "node" and an Edge.
/// A Thought can represent anything, physical object, attribute, word, action, feeling, etc.
///
-public class Thought
+public partial class Thought
{
- private static Queue recentlyFired = new();
+ private static readonly Queue recentlyFired = new();
+ private static readonly object recentlyFiredLock = new();
- public static Thought IsA { get => ThoughtLabels.GetThought("is-a"); } //this is a cache value shortcut for (Thought)"is-a"
+ public static Thought IsA => ThoughtLabels.GetThought("is-a")!; //this is a cache value shortcut for (Thought)"is-a"
private readonly List _linksTo = new(); // links to "has", "is", is-a, many others
private readonly List _linksFrom = new(); // links from
@@ -84,9 +91,9 @@ public IReadOnlyList LinksTo
///
public IReadOnlyList LinksFrom { get { lock (_linksFrom) { return new List(_linksFrom.AsReadOnly()); } } }
/// Direct parents (targets of outgoing is-a links).
- public IReadOnlyList Parents { get { lock (_linksTo) return _linksTo.Where(x => x.LinkType?.Label == "is-a").Select(x => x.To).ToList(); } }
+ public IReadOnlyList Parents { get { lock (_linksTo) return _linksTo.Where(x => x.LinkType?.Label == "is-a").Select(x => x.To).OfType().ToList(); } }
/// Direct children (sources of incoming is-a links).
- public IReadOnlyList Children { get { lock (_linksFrom) return _linksFrom.Where(x => x.LinkType?.Label == "is-a").Select(x => x.From).ToList(); } }
+ public IReadOnlyList Children { get { lock (_linksFrom) return _linksFrom.Where(x => x.LinkType?.Label == "is-a").Select(x => x.From).OfType().ToList(); } }
private string _label = "";
public string Label
@@ -103,13 +110,14 @@ public string Label
public void Delete()
{
- if (LinksFrom.FindFirst(x=>x.LinkType.Label == "VLU") is not null)
+ if (LinksFrom.FindFirst(x => x.LinkType?.Label == "VLU") is not null)
{
//this Thought is the object of a sequence.
}
foreach (Link r in _linksTo.Where(x => (x.To as SeqElement)?.FRST == x.To))
{
- UKS.theUKS.DeleteSequence((SeqElement)r.To);
+ if (r.To is SeqElement seq)
+ UKS.theUKS.DeleteSequence(seq);
}
for (int i = 0; i < _linksTo.Count; i++)
{
@@ -121,7 +129,7 @@ public void Delete()
for (int i = 0; i < _linksFrom.Count; i++)
{
Link r = _linksFrom[i];
- if (r.From.LinksTo.Count > 0) //HACK: corrects for certain broken links
+ if (r.From?.LinksTo.Count > 0) //HACK: corrects for certain broken links
{
r.From.RemoveLink(r);
i--;
@@ -171,9 +179,9 @@ public TimeSpan TimeToLive
}
}
- private object _value;
+ private object? _value;
/// Any serializable object can be attached to a Thought. ONLY STRINGS are supported for save/restore to disk file.
- public object V
+ public object? V
{
get => _value;
set { _value = value; }
@@ -221,16 +229,12 @@ public override string ToString()
///
/// Allows implicit conversion from a label string to an existing Thought (or null if not found).
///
- public static implicit operator Thought(string label)
- {
- Thought t = ThoughtLabels.GetThought(label);
- return t;
- }
+ public static implicit operator Thought?(string label) => ThoughtLabels.GetThought(label);
///
/// Equality by label; for Link, also compares endpoints and link type.
///
- public override bool Equals(object obj)
+ public override bool Equals(object? obj)
{
if (obj is Thought t)
{
@@ -245,6 +249,13 @@ public override bool Equals(object obj)
return false;
}
+ public override int GetHashCode()
+ {
+ if (this is Link link)
+ return HashCode.Combine(Label, link.From, link.LinkType, link.To);
+ return Label.GetHashCode(StringComparison.Ordinal);
+ }
+
public static bool operator ==(Thought? a, Thought? b)
{
if (ReferenceEquals(a, b)) return true;
@@ -274,15 +285,16 @@ public IReadOnlyList ChildrenWithSubclasses
{
get
{
- List retVal = Children.ToList();
- for (int i = 0; i < retVal.Count; i++)
+ List retVal = new();
+ HashSet seen = new();
+ foreach (Thought child in Children)
{
- Thought t = retVal[i];
- if (t.Label.StartsWith(this._label))
+ if (seen.Add(child))
+ retVal.Add(child);
+ foreach (Thought descendant in child.Descendants)
{
- retVal.AddRange(t.Children);
- retVal.RemoveAt(i);
- i--;
+ if (seen.Add(descendant))
+ retVal.Add(descendant);
}
}
return retVal;
@@ -352,8 +364,15 @@ public IEnumerable Descendants
/// Determines whether this thought has the specified ancestor (self-inclusive).
///
/// Ancestor to test.
- public bool HasAncestor(Thought t)
+ public bool HasAncestor(string label)
+ {
+ Thought? t = ThoughtLabels.GetThought(label);
+ return t is not null && HasAncestor(t);
+ }
+
+ public bool HasAncestor(Thought? t)
{
+ if (t is null) return false;
foreach (var ancestor in AncestorsWithSelf)
if (ancestor == t) return true;
return false;
@@ -376,21 +395,30 @@ public void Fire()
private void AddToRecentlyFired()
{
int maxCount = 100;
- recentlyFired.Enqueue(this);
- while (recentlyFired.Count > maxCount) _ = recentlyFired.Dequeue();
+ lock (recentlyFiredLock)
+ {
+ recentlyFired.Enqueue(this);
+ while (recentlyFired.Count > maxCount) _ = recentlyFired.Dequeue();
+ }
}
public static void DeleteFromRecentlyFired(Thought t)
{
- //CAUTION NOT THREAD SAFE
- var tempList = recentlyFired.Where(x => x != t).ToList();
- recentlyFired = new Queue(tempList);
+ lock (recentlyFiredLock)
+ {
+ var tempList = recentlyFired.Where(x => x != t).ToList();
+ recentlyFired.Clear();
+ foreach (var item in tempList)
+ recentlyFired.Enqueue(item);
+ }
}
public static IReadOnlyList GetRecentlyFiredThoughts(TimeSpan recency)
{
//remove duplicates while preserving order (keeping the most recent occurrence of each Thought)
DateTime cutoff = DateTime.MinValue;
if (recency < DateTime.Now-DateTime.MinValue) cutoff = DateTime.Now - recency;
- var snapshot = recentlyFired.Where(x=>x.LastFiredTime > cutoff).ToArray();
+ Thought[] snapshot;
+ lock (recentlyFiredLock)
+ snapshot = recentlyFired.Where(x=>x.LastFiredTime > cutoff).ToArray();
var seen = new HashSet();
var resultRev = new List();
foreach (var item in snapshot.Reverse())
@@ -403,17 +431,23 @@ public static IReadOnlyList GetRecentlyFiredThoughts(TimeSpan recency)
public static void FireAllRecentlyFiredThoughts(TimeSpan recency)
{
DateTime cutoff = DateTime.Now - recency;
- var snapshot = recentlyFired.ToArray();
- var seen = new HashSet();
- var resultRev = new List();
-
- foreach (var item in snapshot.Reverse())
+ List resultRev;
+ lock (recentlyFiredLock)
{
- if (seen.Add(item))
- resultRev.Add(item); // keep first time we see it from the back (i.e., the last occurrence)
+ var snapshot = recentlyFired.ToArray();
+ var seen = new HashSet();
+ resultRev = new List();
+
+ foreach (var item in snapshot.Reverse())
+ {
+ if (seen.Add(item))
+ resultRev.Add(item);
+ }
+ resultRev.Reverse();
+ recentlyFired.Clear();
+ foreach (var item in resultRev)
+ recentlyFired.Enqueue(item);
}
- resultRev.Reverse();
- recentlyFired = new(resultRev); // restore original ordering of the kept items
foreach (Thought t in resultRev.Where(x => x.LastFiredTime > cutoff))
{
@@ -422,7 +456,8 @@ public static void FireAllRecentlyFiredThoughts(TimeSpan recency)
}
public static void ClearRecentlyFiredQueue()
{
- recentlyFired.Clear();
+ lock (recentlyFiredLock)
+ recentlyFired.Clear();
}
private void UpdateTimeToLive()
@@ -448,11 +483,18 @@ private void UpdateTimeToLive()
/// Relationship type thought.
/// Target thought.
/// The new or existing link.
- public Link AddLink(Thought linkType, Thought to)
+ public Link? AddLink(string linkTypeLabel, Thought? to)
+ {
+ Thought? linkType = ThoughtLabels.GetThought(linkTypeLabel);
+ if (linkType is null) return null;
+ return AddLink(linkType, to);
+ }
+
+ public Link? AddLink(Thought linkType, Thought? to)
{
if (linkType is null) return null;
- Link existing = HasLink(linkType, to);
+ Link? existing = HasLink(linkType, to);
if (existing is not null)
return existing;
@@ -520,6 +562,7 @@ public void RemoveLink(Link r)
if (r.LinkType is null) return;
if (r.From is null)
{
+ if (r.To is null) return;
lock (r.LinkType._linksFrom)
{
lock (r.To._linksFrom)
@@ -558,23 +601,39 @@ public void RemoveLink(Link r)
r.Delete();
}
- public Thought GetTargetOfFirstLinkOfType(Thought linkType)
+ public Thought? GetTargetOfFirstLinkOfType(Thought linkType)
{
return LinksTo.FindFirst(x => x.LinkType == linkType)?.To;
}
- public Link HasLink(Thought linkType, Thought to = null)
+
+ public Thought? GetTargetOfFirstLinkOfType(string linkTypeLabel)
+ {
+ return LinksTo.FindFirst(x =>
+ string.Equals(x.LinkType?.Label, linkTypeLabel, StringComparison.OrdinalIgnoreCase))?.To;
+ }
+ private static bool LinkTypesMatch(Thought? a, Thought? b)
+ {
+ if (ReferenceEquals(a, b)) return true;
+ if (a is null || b is null) return false;
+ return string.Equals(a.Label, b.Label, StringComparison.OrdinalIgnoreCase);
+ }
+
+ public Link? HasLink(Thought linkType, Thought? to = null)
{
- foreach (Link r in _linksTo)
+ lock (_linksTo)
{
- if (r.From == this && (r.To == to || to is null) && r.LinkType == linkType)
- return r;
+ foreach (Link r in _linksTo)
+ {
+ if (r.From == this && (r.To == to || to is null) && LinkTypesMatch(r.LinkType, linkType))
+ return r;
+ }
}
return null;
}
///
/// Finds a link matching the optional source/type/target criteria.
///
- public Link HasLink(Thought from, Thought linkType, Thought to)
+ public Link? HasLink(Thought? from, Thought? linkType, Thought? to)
{
if (from is null && linkType is null && to is null) return null;
foreach (Link r in LinksTo)
@@ -588,7 +647,7 @@ public Link HasLink(Thought from, Thought linkType, Thought to)
/// Adds a parent link ("is-a") if not already present.
///
/// Parent to add.
- public Link AddParent(Thought newParent)
+ public Link? AddParent(Thought newParent)
{
if (newParent is null) return null;
if (!Parents.Contains(newParent))
@@ -600,8 +659,15 @@ public Link AddParent(Thought newParent)
/// Remove a parent from a Thought.
///
/// Parent thought to remove.
- public void RemoveParent(Thought t)
+ public void RemoveParent(string parentLabel)
{
+ Thought? t = ThoughtLabels.GetThought(parentLabel);
+ if (t is not null) RemoveParent(t);
+ }
+
+ public void RemoveParent(Thought? t)
+ {
+ if (t is null) return;
Link r = new() { From = this, LinkType = IsA, To = t };
t.RemoveLink(r);
}
@@ -625,7 +691,8 @@ public List GetAttributes()
foreach (Link r in LinksTo)
{
if (r.LinkType?.Label != "hasAttribute" && r.LinkType?.Label != "is") continue;
- retVal.Add(r.To);
+ if (r.To is not null)
+ retVal.Add(r.To);
}
return retVal;
}
@@ -634,7 +701,13 @@ public List GetAttributes()
/// Determines whether this thought has the specified property, considering inheritance.
///
/// Property thought to test.
- public bool HasProperty(Thought t) //with inheritance
+ public bool HasProperty(string label)
+ {
+ Thought? t = ThoughtLabels.GetThought(label);
+ return t is not null && HasProperty(t);
+ }
+
+ public bool HasProperty(Thought? t) //with inheritance
{
if (t is null) return false;
if (LinksTo.FindFirst(x => x.LinkType?.Label == "hasProperty" && x.To == t) is not null) return true;
diff --git a/UKS/ThoughtLabels.cs b/UKS/ThoughtLabels.cs
index 7639a05..1a962ec 100644
--- a/UKS/ThoughtLabels.cs
+++ b/UKS/ThoughtLabels.cs
@@ -25,12 +25,10 @@ public class ThoughtLabels
public static ConcurrentDictionary LabelList { get => labelList;}
- public static Thought GetThought(string label)
+ public static Thought? GetThought(string label)
{
if (label is null || label == "") return null;
- Thought retVal = null;
- if (labelList.TryGetValue(label.ToLower(), out retVal))
- { } //breakpoint?
+ labelList.TryGetValue(label.ToLower(), out Thought? retVal);
return retVal;
}
public static int GetLabelCount()
@@ -56,7 +54,7 @@ public static string AddThoughtLabel(string newLabel, Thought t)
{
//sets a label and appends/increments trailing digits in the event of collisions
if (newLabel == "") return newLabel; //don't index empty lables
- labelList.TryRemove(t.Label.ToLower(), out Thought dummy);
+ labelList.TryRemove(t.Label.ToLower(), out Thought? _);
int curDigits = -1;
string baseString = newLabel;
//This code allows you to put a * at the end of a label and it will auto-increment
@@ -89,7 +87,7 @@ public static List AllThoughtsInLabelList()
public static void RemoveThoughtLabel(string existingLabel)
{
if (string.IsNullOrEmpty(existingLabel)) return;
- labelList.Remove(existingLabel.ToLower(), out Thought oldThought);
+ labelList.Remove(existingLabel.ToLower(), out Thought? _);
}
}
diff --git a/UKS/UKS .Initialize.cs b/UKS/UKS .Initialize.cs
index e7b5a7a..01f31af 100644
--- a/UKS/UKS .Initialize.cs
+++ b/UKS/UKS .Initialize.cs
@@ -27,7 +27,7 @@ public void CreateMinimumStructureForTests()
isA.AddParent(linkType);
GetOrAddThought("Unknown", "Thought");
GetOrAddThought("VLU", "LinkType");
- GetOrAddThought("NXE", "LinkType");
+ GetOrAddThought("NXT", "LinkType");
GetOrAddThought("FRST", "LinkType");
}
@@ -56,16 +56,14 @@ public void CreateInitialStructure()
ClearSequenceCache();
//Bootstrapping is needed for is-a, Unknown, and the root: Thought
- //because AddStatement and GetOrAddThing won't work without them
+ //because AddStatement and GetOrAddThought won't work without them (legacy GetOrAddThing alias removed)
//Thought
if (Labeled("Thought") is null) AddThought("Thought", null);
- Thought isA = Labeled("is-a");
- if (isA is null) isA = AddThought("is-a", null);
- Thought hasChild = Labeled("has-child");
- if (hasChild is null) hasChild = AddThought("has-child", null);
+ Thought? isA = Labeled("is-a") ?? AddThought("is-a", null);
+ Thought? hasChild = Labeled("has-child") ?? AddThought("has-child", null);
Thought linkType = AddThought("LinkType", "Thought");
- isA.AddParent(linkType);
- hasChild.AddParent(linkType);
+ isA?.AddParent(linkType);
+ hasChild?.AddParent(linkType);
GetOrAddThought("Unknown", "Thought");
GetOrAddThought("Abstract", "Thought");
@@ -96,6 +94,7 @@ public void CreateInitialStructure()
AddStatement("isCommutative", "is-a", "Property");
AddStatement("allowMultiple", "is-a", "Property");
AddStatement("inheritable", "is-a", "Property");
+ AddStatement("isEphemeral", "is-a", "Property");
AddStatement("isCondition", "is-a", "Property");
AddStatement("isResult", "is-a", "Property");
@@ -143,11 +142,36 @@ public void CreateInitialStructure()
AddStatement("white", "is-a", "color");
AddStatement("gray", "is-a", "color");
+ // Ch.4 Fig 4.2 — discrete RGBI level Thoughts (analog→symbolic decode target)
+ AddStatement("isDiscreteLevel", "is-a", "Property");
+ AddStatement("discrete-channel", "is-a", "Abstract");
+ AddStatement("red-channel", "is-a", "discrete-channel");
+ AddStatement("green-channel", "is-a", "discrete-channel");
+ AddStatement("blue-channel", "is-a", "discrete-channel");
+ AddStatement("brightness-channel", "is-a", "discrete-channel");
+ for (int level = 1; level <= 8; level++)
+ {
+ AddStatement($"red-level-{level}", "is-a", "red-channel");
+ AddStatement($"red-level-{level}", "hasProperty", "isDiscreteLevel");
+ AddStatement($"green-level-{level}", "is-a", "green-channel");
+ AddStatement($"green-level-{level}", "hasProperty", "isDiscreteLevel");
+ AddStatement($"blue-level-{level}", "is-a", "blue-channel");
+ AddStatement($"blue-level-{level}", "hasProperty", "isDiscreteLevel");
+ AddStatement($"brightness-level-{level}", "is-a", "brightness-channel");
+ AddStatement($"brightness-level-{level}", "hasProperty", "isDiscreteLevel");
+ }
+ AddStatement("low-brightness", "is-a", "brightness-channel");
+ AddStatement("low-brightness", "hasProperty", "isDiscreteLevel");
+
//underlying properties
AddStatement("is-a", "hasProperty", "isTransitive");
AddStatement("is-a", "hasProperty", "inheritable");
AddStatement("has", "hasProperty", "isTransitive");
AddStatement("has", "hasProperty", "inheritable");
+ AddStatement("located-in", "is-a", "LinkType");
+ AddStatement("located-in", "hasProperty", "isEphemeral");
+ AddStatement("attention-focus", "is-a", "LinkType");
+ AddStatement("attention-focus", "hasProperty", "isEphemeral");
//Clauses
AddStatement("ClauseType", "is-a", "LinkType");
@@ -189,21 +213,47 @@ public void CreateInitialStructure()
GetOrAddThought(i.ToString(), "digit");
for (int i = 9; i > 0; i--)
AddStatement(i.ToString(), "greaterThan", (i - 1).ToString());
- AddSequenceAndLink("digit", "order", new List { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" });
+ {
+ List digits = new();
+ for (int i = 0; i < 10; i++)
+ {
+ Thought? digit = GetOrAddThought(i.ToString(), "digit");
+ if (digit is not null) digits.Add(digit);
+ }
+ Thought? digitType = GetOrAddThought("digit", "number");
+ Thought? orderLink = GetOrAddThought("order", "Comparison");
+ if (digitType is not null && orderLink is not null)
+ AddSequenceAndLink(digitType, orderLink, digits);
+ }
//demo to add PI to the structure
AddStatement("pi", "is-a", "number");
- AddSequenceAndLink("pi", "hasDigit", new List { "3", ".", "1", "4", "1", "5", "9" });
+ {
+ List piDigits = new();
+ foreach (string d in new[] { "3", ".", "1", "4", "1", "5", "9" })
+ {
+ Thought? digit = GetOrAddThought(d, "digit");
+ if (digit is not null) piDigits.Add(digit);
+ }
+ Thought? piThought = Labeled("pi");
+ Thought? hasDigitLink = GetOrAddThought("hasDigit", "has");
+ if (piThought is not null && hasDigitLink is not null)
+ AddSequenceAndLink(piThought, hasDigitLink, piDigits);
+ }
//put in letters
GetOrAddThought("letter", "Abstract");
List theAlphabet = new();
for (char c = 'A'; c <= 'Z'; c++)
{
- theAlphabet.Add(GetOrAddThought("l:"+c.ToString(), "Letter"));
+ Thought? letter = GetOrAddThought("l:" + c, "Letter");
+ if (letter is not null) theAlphabet.Add(letter);
}
GetOrAddThought("alphabet", "abstract");
- AddSequenceAndLink("alphabet", "order", theAlphabet);
+ Thought? alphabetThought = GetOrAddThought("alphabet", "abstract");
+ Thought? orderLink2 = GetOrAddThought("order", "Comparison");
+ if (alphabetThought is not null && orderLink2 is not null)
+ AddSequenceAndLink(alphabetThought, orderLink2, theAlphabet);
AddBrainSimConfigSectionIfNeeded();
}
diff --git a/UKS/UKS.Bubble.cs b/UKS/UKS.Bubble.cs
new file mode 100644
index 0000000..f0f81b1
--- /dev/null
+++ b/UKS/UKS.Bubble.cs
@@ -0,0 +1,230 @@
+/*
+ * Ch.5 attribute bubbling on learn + BubbleLog (Ch.6 provenance prep).
+ */
+
+using System.Text.RegularExpressions;
+
+namespace UKS;
+
+public sealed class BubbleLogEntry
+{
+ public DateTime When { get; init; }
+ public string ParentLabel { get; init; } = "";
+ public string LinkTypeLabel { get; init; } = "";
+ public string TargetLabel { get; init; } = "";
+ public string Action { get; init; } = "";
+}
+
+public partial class UKS
+{
+ public event Action? LinkAdded;
+
+ private readonly List bubbleLog = new();
+ private const int MaxBubbleLogEntries = 256;
+
+ public IReadOnlyList BubbleLog => bubbleLog;
+
+ private static readonly string[] BubbleExcludeTypes =
+ {
+ "hasProperty", "isTransitive", "isCommutative", "inverseOf", "hasAttribute", "hasDigit"
+ };
+
+ private sealed class LinkDest
+ {
+ public Thought linkType = null!;
+ public Thought target = null!;
+ public List links = new();
+ }
+
+ internal void RaiseLinkAdded(Link lnk) => LinkAdded?.Invoke(lnk);
+
+ /// Ch.5 category emergence: bubble shared child attrs; return parent if changed.
+ public Thought? TryFormCategoryFromChildren(Thought parent, float minFraction = 0.6f) =>
+ BubbleSharedAttributes(parent, minFraction) ? parent : null;
+
+ ///
+ /// Port of ModuleAttributeBubble majority logic — bubble shared child links to parent.
+ ///
+ public bool BubbleSharedAttributes(Thought parent, float minFraction = 0.6f)
+ {
+ if (parent is null || parent.Children.Count == 0) return false;
+ if (parent.Label == "Unknown") return false;
+
+ bool changed = false;
+ List itemCounts = new();
+ foreach (Thought child in parent.ChildrenWithSubclasses)
+ {
+ foreach (Link r in child.LinksTo)
+ {
+ if (r.LinkType == Thought.IsA) continue;
+ Thought useLinkType = GetBubbleInstanceType(r.LinkType);
+ LinkDest? found = itemCounts.FindFirst(x => x.linkType == useLinkType && x.target == r.To);
+ if (found is null)
+ {
+ found = new LinkDest { linkType = useLinkType, target = r.To! };
+ itemCounts.Add(found);
+ }
+ found.links.Add(r);
+ }
+ }
+
+ if (itemCounts.Count == 0) return false;
+ var sortedItems = itemCounts.OrderByDescending(x => x.links.Count).ToList();
+ float totalCount = parent.Children.Count;
+
+ for (int i = 0; i < sortedItems.Count; i++)
+ {
+ LinkDest rr = sortedItems[i];
+ if (BubbleExcludeTypes.Contains(rr.linkType.Label, StringComparer.OrdinalIgnoreCase)) continue;
+
+ Link? existing = GetLink(parent, rr.linkType, rr.target);
+ float currentWeight = existing?.Weight ?? 0f;
+ float positiveCount = rr.links.FindAll(x => x.Weight > 0.5f).Count;
+ float positiveWeight = rr.links.Sum(x => x.Weight);
+ float negativeCount = 0;
+ float negativeWeight = 0;
+
+ for (int j = 0; j < sortedItems.Count; j++)
+ {
+ if (j == i) continue;
+ if (BubbleLinksConflict(rr, sortedItems[j]))
+ {
+ negativeCount += sortedItems[j].links.Count;
+ negativeWeight += sortedItems[j].links.Sum(x => x.Weight);
+ }
+ }
+
+ float noInfoCount = totalCount - (positiveCount + negativeCount);
+ positiveWeight += currentWeight + noInfoCount * 0.51f;
+ if (noInfoCount < 0) noInfoCount = 0;
+
+ if (negativeCount >= positiveCount)
+ {
+ if (existing is not null)
+ {
+ parent.RemoveLink(existing);
+ RecordBubble("prune", parent, rr.linkType, rr.target);
+ changed = true;
+ }
+ continue;
+ }
+
+ float deltaWeight = positiveWeight - negativeWeight;
+ float targetWeight = deltaWeight switch
+ {
+ < 0.8f => -0.1f,
+ < 1.7f => 0.01f,
+ < 2.7f => 0.2f,
+ _ => 0.3f
+ };
+ if (currentWeight == 0) currentWeight = 0.5f;
+ float newWeight = Math.Min(currentWeight + targetWeight, 0.99f);
+
+ if (positiveCount <= totalCount * minFraction) continue;
+ if (newWeight == currentWeight && existing is not null) continue;
+
+ if (newWeight < 0.5f)
+ {
+ if (existing is not null)
+ {
+ parent.RemoveLink(existing);
+ RecordBubble("prune", parent, rr.linkType, rr.target);
+ changed = true;
+ }
+ continue;
+ }
+
+ Link bubbled = parent.AddLink(rr.linkType, rr.target)!;
+ bubbled.Weight = newWeight;
+ bubbled.Fire();
+ RecordBubble("bubble", parent, rr.linkType, rr.target);
+ changed = true;
+
+ foreach (Thought child in parent.Children)
+ child.RemoveLink(rr.linkType, rr.target);
+
+ for (int j = 0; j < parent.LinksTo.Count; j++)
+ {
+ Link parentLink = parent.LinksTo[j];
+ if (BubbleLinksConflict(new LinkDest { linkType = rr.linkType, target = rr.target },
+ new LinkDest { linkType = parentLink.LinkType!, target = parentLink.To! }))
+ {
+ parent.RemoveLink(parentLink);
+ j--;
+ }
+ }
+ }
+
+ return changed;
+ }
+
+ private void RecordBubble(string action, Thought parent, Thought linkType, Thought target)
+ {
+ bubbleLog.Add(new BubbleLogEntry
+ {
+ When = DateTime.UtcNow,
+ ParentLabel = parent.Label,
+ LinkTypeLabel = linkType.Label,
+ TargetLabel = target.Label,
+ Action = action
+ });
+ if (bubbleLog.Count > MaxBubbleLogEntries)
+ bubbleLog.RemoveAt(0);
+ }
+
+ private static bool BubbleLinksConflict(LinkDest r1, LinkDest r2)
+ {
+ if (r1.linkType == r2.linkType && r1.target == r2.target) return false;
+ if (r1.linkType == r2.linkType)
+ {
+ foreach (Thought parent in FindBubbleCommonParents(r1.target, r2.target))
+ if (parent.HasProperty("isExclusive") || parent.HasProperty("allowMultiple")) return true;
+ }
+ if (r1.target == r2.target)
+ {
+ IReadOnlyList r1Attribs = r1.linkType.GetAttributes();
+ IReadOnlyList r2Attribs = r2.linkType.GetAttributes();
+ Thought? r1Not = r1Attribs.FindFirst(x => x.Label is "not" or "no");
+ Thought? r2Not = r2Attribs.FindFirst(x => x.Label is "not" or "no");
+ if (r1Not is null != (r2Not is null)) return true;
+
+ foreach (Thought t1 in r1Attribs)
+ {
+ foreach (Thought t2 in r2Attribs)
+ {
+ if (t1 == t2) continue;
+ foreach (Thought t3 in FindBubbleCommonParents(t1, t2))
+ {
+ if (t3.HasProperty("isexclusive") || t3.HasProperty("allowMultiple"))
+ return true;
+ }
+ }
+ }
+
+ bool hasNumber1 = r1Attribs.Any(x => x.HasAncestor("number"));
+ bool hasNumber2 = r2Attribs.Any(x => x.HasAncestor("number"));
+ if (hasNumber1 || hasNumber2) return true;
+ }
+ return false;
+ }
+
+ private static List FindBubbleCommonParents(Thought t, Thought t1)
+ {
+ List common = new();
+ foreach (Thought p in t.Parents)
+ if (t1.Parents.Contains(p))
+ common.Add(p);
+ return common;
+ }
+
+ public static Thought GetBubbleInstanceType(Thought t)
+ {
+ Thought useLinkType = t;
+ while (useLinkType.Parents.Count > 0 &&
+ Regex.IsMatch(useLinkType.Label, @"\d+$") &&
+ !t.Label.Contains('.') &&
+ useLinkType.Label.StartsWith(useLinkType.Parents[0].Label))
+ useLinkType = useLinkType.Parents[0];
+ return useLinkType;
+ }
+}
\ No newline at end of file
diff --git a/UKS/UKS.ContextResolver.cs b/UKS/UKS.ContextResolver.cs
new file mode 100644
index 0000000..65ba55a
--- /dev/null
+++ b/UKS/UKS.ContextResolver.cs
@@ -0,0 +1,134 @@
+/*
+ * Ch.5 context resolution among conflicting inherited expectations.
+ * Extracted from ModuleAlgorithm.EvaluateContext.
+ */
+
+namespace UKS;
+
+public sealed class ContextCaseResult
+{
+ public Thought? Case { get; init; }
+ public Thought? Response { get; init; }
+ public float Weight { get; init; }
+}
+
+public partial class UKS
+{
+ public ContextCaseResult? SelectBestContextCase(
+ Thought contextRoot,
+ Func resolveIndirection)
+ {
+ if (contextRoot is null) return null;
+
+ Thought? bestCase = null;
+ Thought? bestResponse = null;
+ float bestWeight = 0;
+
+ foreach (Thought caseThought in contextRoot.Children)
+ {
+ float weight = ScoreContextCase(caseThought, resolveIndirection);
+ if (weight <= bestWeight) continue;
+ bestCase = caseThought;
+ bestResponse = caseThought.GetTargetOfFirstLinkOfType("response");
+ bestWeight = weight;
+ }
+
+ if (bestCase is null) return null;
+ return new ContextCaseResult
+ {
+ Case = bestCase,
+ Response = bestResponse,
+ Weight = bestWeight
+ };
+ }
+
+ public float ScoreContextCase(Thought caseThought, Func resolveIndirection)
+ {
+ if (caseThought is null) return 0;
+ float weight = 0;
+
+ foreach (Link l in caseThought.LinksTo.Where(x => x.LinkType?.Label == "has"))
+ {
+ if (l.To is not Link test) continue;
+ if (test.LinkType?.HasAncestor("exist") != true) continue;
+
+ bool not = test.LinkType.HasAncestor("not");
+ Thought? testType = test.LinkType.LinksTo.FindFirst(x =>
+ string.Equals(x.LinkType?.Label, "is", StringComparison.OrdinalIgnoreCase) &&
+ x.To?.Label != "EXIST")?.To;
+ Thought? src = resolveIndirection(test.From);
+ if (src is null) continue;
+
+ if (test.LinkType.HasAncestor("same") ||
+ test.LinkType.Label.Contains("same", StringComparison.OrdinalIgnoreCase))
+ {
+ Thought? target = resolveIndirection(test.To);
+ if (!not && src == target) weight += l.Weight;
+ if (not && src != target) weight += l.Weight;
+ }
+ else if (test.To?.Label == "??")
+ {
+ if (!not && src.HasLink(testType) is not null) weight += l.Weight * test.Weight;
+ if (not && src.HasLink(testType) is null) weight += l.Weight * test.Weight;
+ }
+ else
+ {
+ Thought? target = resolveIndirection(test.To);
+ if (!not && src.HasLink(testType, target) is not null) weight += l.Weight * test.Weight;
+ if (not && src.HasLink(testType, target) is null) weight += l.Weight * test.Weight;
+ }
+ }
+
+ return weight;
+ }
+
+ ///
+ /// Keep inherited links from the winning context's preferred is-a category; locals always kept.
+ ///
+ public List FilterLinksByContext(
+ List links,
+ Thought subject,
+ Thought contextRoot,
+ Func resolveIndirection)
+ {
+ var best = SelectBestContextCase(contextRoot, resolveIndirection);
+ if (best?.Case is null) return links;
+
+ Thought? preferredCategory = InferCategoryFromCase(best.Case, subject, resolveIndirection);
+ if (preferredCategory is null) return links;
+
+ return links.Where(l =>
+ l.InheritanceDepth == 0 ||
+ l.InheritedFromCategory is null ||
+ l.InheritedFromCategory == preferredCategory).ToList();
+ }
+
+ public Thought? InferCategoryFromCase(
+ Thought caseThought,
+ Thought subject,
+ Func resolveIndirection)
+ {
+ if (caseThought is null) return null;
+
+ foreach (Link l in caseThought.LinksTo.Where(x => x.LinkType?.Label == "has"))
+ {
+ if (l.To is not Link test) continue;
+ if (test.LinkType?.HasAncestor("exist") != true) continue;
+
+ Thought? testType = test.LinkType.LinksTo.FindFirst(x =>
+ string.Equals(x.LinkType?.Label, "is", StringComparison.OrdinalIgnoreCase) &&
+ x.To?.Label != "EXIST")?.To;
+ if (testType is null ||
+ !string.Equals(testType.Label, "is-a", StringComparison.OrdinalIgnoreCase))
+ continue;
+
+ Thought? src = resolveIndirection(test.From);
+ if (src != subject) continue;
+
+ Thought? target = resolveIndirection(test.To);
+ if (target is not null) return target;
+ }
+
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/UKS/UKS.DiscreteAttributeDecoder.cs b/UKS/UKS.DiscreteAttributeDecoder.cs
new file mode 100644
index 0000000..c63a1e1
--- /dev/null
+++ b/UKS/UKS.DiscreteAttributeDecoder.cs
@@ -0,0 +1,108 @@
+/*
+ * Brain Simulator Thought — Ch.4 analog→discrete sensory decode (Fig 4.2).
+ * Quantizes RGBI channels to level Thoughts; black uses affirmative low-brightness.
+ */
+
+namespace UKS;
+
+/// Discrete RGBI level Thoughts selected from analog input.
+public sealed class DiscreteColorDecodeResult
+{
+ public Thought RedLevel { get; set; } = null!;
+ public Thought GreenLevel { get; set; } = null!;
+ public Thought BlueLevel { get; set; } = null!;
+ public Thought BrightnessLevel { get; set; } = null!;
+ public Thought? AffirmativeBlack { get; set; }
+ public Thought? AffirmativeLowBrightness { get; set; }
+
+ public IEnumerable AllLevels
+ {
+ get
+ {
+ yield return RedLevel;
+ yield return GreenLevel;
+ yield return BlueLevel;
+ yield return BrightnessLevel;
+ if (AffirmativeBlack is not null) yield return AffirmativeBlack;
+ if (AffirmativeLowBrightness is not null) yield return AffirmativeLowBrightness;
+ }
+ }
+}
+
+/// Maps normalized/byte color channels to discrete UKS level Thoughts.
+public static class DiscreteAttributeDecoder
+{
+ public const int LevelCount = 8;
+
+ /// Map 0–255 channel to level 1 (min) … 8 (max).
+ public static int QuantizeToLevel(int channelValue0to255)
+ {
+ if (channelValue0to255 <= 0) return 1;
+ if (channelValue0to255 >= 255) return LevelCount;
+ return (int)Math.Clamp((channelValue0to255 * LevelCount) / 256 + 1, 1, LevelCount);
+ }
+
+ public static Thought GetLevelThought(UKS uks, string channel, int level) =>
+ uks.Labeled($"{channel}-level-{level}")!;
+
+ public static DiscreteColorDecodeResult DecodeRgb(byte r, byte g, byte b, UKS uks)
+ {
+ int luminance = (int)(0.299 * r + 0.587 * g + 0.114 * b);
+ luminance = Math.Clamp(luminance, 0, 255);
+
+ var result = new DiscreteColorDecodeResult
+ {
+ RedLevel = GetLevelThought(uks, "red", QuantizeToLevel(r)),
+ GreenLevel = GetLevelThought(uks, "green", QuantizeToLevel(g)),
+ BlueLevel = GetLevelThought(uks, "blue", QuantizeToLevel(b)),
+ BrightnessLevel = GetLevelThought(uks, "brightness", QuantizeToLevel(luminance)),
+ };
+
+ if (r == 0 && g == 0 && b == 0)
+ {
+ result.AffirmativeBlack = uks.Labeled("black");
+ result.AffirmativeLowBrightness = uks.Labeled("low-brightness");
+ }
+
+ return result;
+ }
+
+ public static void FireLevels(DiscreteColorDecodeResult decoded)
+ {
+ foreach (Thought level in decoded.AllLevels)
+ level.Fire();
+ }
+
+ public static void ApplyHasLinks(Thought objectThought, DiscreteColorDecodeResult decoded, UKS uks)
+ {
+ Thought? has = uks.Labeled("has");
+ if (has is null) return;
+
+ foreach (Thought level in decoded.AllLevels)
+ uks.AddStatement(objectThought, has, level);
+ }
+
+ /// Decode, fire level Thoughts, write has-links, optionally seed TraversalContext.
+ public static DiscreteColorDecodeResult DecodeAndApply(
+ Thought objectThought,
+ byte r,
+ byte g,
+ byte b,
+ UKS uks,
+ TraversalContext? ctx = null)
+ {
+ DiscreteColorDecodeResult decoded = DecodeRgb(r, g, b, uks);
+ FireLevels(decoded);
+ ApplyHasLinks(objectThought, decoded, uks);
+
+ if (ctx is not null)
+ {
+ ctx.Activate(objectThought);
+ Thought? has = uks.Labeled("has");
+ if (has is not null)
+ ctx.ActivateRelationship(has);
+ }
+
+ return decoded;
+ }
+}
\ No newline at end of file
diff --git a/UKS/UKS.Exclusivity.cs b/UKS/UKS.Exclusivity.cs
index bf88f57..79fcd6e 100644
--- a/UKS/UKS.Exclusivity.cs
+++ b/UKS/UKS.Exclusivity.cs
@@ -56,19 +56,22 @@ private bool LinksAreExclusive(Link r1, Link r2)
if (r2.HasProperty("isResult")) return false;
if (r2.HasProperty("isCondition")) return false;
- if (r1.From == r2.From ||
+ if (LinkTypesAreExclusive(r1, r2))
+ return true;
+
+ if (r1.From is not null && r2.From is not null &&
+ (r1.From == r2.From ||
r1.From.AncestorsWithSelf.Contains(r2.From) ||
r2.From.AncestorsWithSelf.Contains(r1.From) ||
- FindCommonParents(r1.From, r1.From).Count() > 0)
+ FindCommonParents(r1.From, r2.From).Count > 0))
{
- IReadOnlyList r1LinkiProps = r1.LinkType.GetAttributes();
- IReadOnlyList r2LinkProps = r2.LinkType.GetAttributes();
+ IReadOnlyList r1LinkiProps = r1.LinkType is { } r1LinkType ? r1LinkType.GetAttributes() : Array.Empty();
+ IReadOnlyList r2LinkProps = r2.LinkType is { } r2LinkType ? r2LinkType.GetAttributes() : Array.Empty();
//handle case with properties of the target
if (r1.To is not null && r1.To == r2.To &&
(r1.To.AncestorsWithSelf.Contains(r2.To) ||
- r2.To.AncestorsWithSelf.Contains(r1.To) ||
- FindCommonParents(r1.To, r1.To).Count() > 0))
+ r2.To.AncestorsWithSelf.Contains(r1.To)))
{
IReadOnlyList r1TargetProps = r1.To.GetAttributes();
IReadOnlyList r2TargetProps = r2.To.GetAttributes();
@@ -86,7 +89,7 @@ private bool LinksAreExclusive(Link r1, Link r2)
//handle case with conflicting targets
if (r1.To is not null && r2.To is not null)
{
- List commonParents = FindCommonParents(r1.To, r2.To);
+ List commonParents = FindCommonParents(r1.To!, r2.To!);
foreach (Thought t3 in commonParents)
{
if (HasProperty(t3, "isexclusive") || HasProperty(t3, "allowMultiple"))
@@ -116,9 +119,10 @@ private bool LinksAreExclusive(Link r1, Link r2)
return true;
//if one of the linkypes contains negation and not the other
- Thought r1Not = r1LinkiProps.FindFirst(x => x.Label.ToLower() == "not" || x.Label.ToLower() == "no");
- Thought r2Not = r2LinkProps.FindFirst(x => x.Label.ToLower() == "not" || x.Label.ToLower() == "no");
- if ((r1.From == r2.From || r1.From.Ancestors.Contains(r2.From) ||
+ Thought? r1Not = r1LinkiProps.FindFirst(x => x.Label.ToLower() == "not" || x.Label.ToLower() == "no");
+ Thought? r2Not = r2LinkProps.FindFirst(x => x.Label.ToLower() == "not" || x.Label.ToLower() == "no");
+ if (r1.From is not null && r2.From is not null &&
+ (r1.From == r2.From || r1.From.Ancestors.Contains(r2.From) ||
r2.From.Ancestors.Contains(r1.From)) &&
r1.To == r2.To &&
(r1Not is null && r2Not is not null || r1Not is not null && r2Not is null))
@@ -127,7 +131,7 @@ private bool LinksAreExclusive(Link r1, Link r2)
else
{
//this appears to duplicate code at line 226
- List commonParents = FindCommonParents(r1.To, r2.To);
+ List commonParents = FindCommonParents(r1.To!, r2.To!);
foreach (Thought t3 in commonParents)
{
if (HasProperty(t3, "isexclusive"))
@@ -142,10 +146,11 @@ private bool LinksAreExclusive(Link r1, Link r2)
private bool LinkTypesAreExclusive(Link r1, Link r2)
{
+ if (r1.LinkType is null || r2.LinkType is null) return false;
IReadOnlyList r1RelProps = r1.LinkType.GetAttributes();
IReadOnlyList r2RelProps = r2.LinkType.GetAttributes();
- Thought r1Not = r1RelProps.FindFirst(x => x.Label == "not" || x.Label == "no");
- Thought r2Not = r2RelProps.FindFirst(x => x.Label == "not" || x.Label == "no");
+ Thought? r1Not = r1RelProps.FindFirst(x => x.Label == "not" || x.Label == "no");
+ Thought? r2Not = r2RelProps.FindFirst(x => x.Label == "not" || x.Label == "no");
if (r1.To == r2.To &&
(r1Not is null && r2Not is not null || r1Not is not null && r2Not is null))
return true;
@@ -157,19 +162,19 @@ private bool HasAttribute(Thought t, string name)
if (t is null) return false;
foreach (Link r in t.LinksTo)
{
- if (r.LinkType is not null && r.LinkType.Label == "is" && r.To.Label == name)
+ if (r.LinkType is not null && r.LinkType.Label == "is" && r.To?.Label == name)
return true;
}
return false;
}
- private Thought ThoughtFromString(string label, string defaultParent, Thought source = null)
+ private Thought? ThoughtFromString(string label, string defaultParent, Thought? source = null)
{
GetOrAddThought("Thought"); //safety
GetOrAddThought("Unknown", "Thought"); //safety
if (string.IsNullOrEmpty(label)) return null;
if (label == "") return null;
- Thought t = Labeled(label);
+ Thought? t = Labeled(label);
if (t is null)
{
@@ -182,7 +187,7 @@ private Thought ThoughtFromString(string label, string defaultParent, Thought so
return t;
}
- private Thought ThoughtFromObject(object o, string parentLabel = "", Thought source = null)
+ private Thought? ThoughtFromObject(object? o, string parentLabel = "", Thought? source = null)
{
if (parentLabel == "")
parentLabel = "Unknown";
diff --git a/UKS/UKS.Query.cs b/UKS/UKS.Query.cs
index 6077e6c..d15cc2e 100644
--- a/UKS/UKS.Query.cs
+++ b/UKS/UKS.Query.cs
@@ -35,14 +35,18 @@ public List GetAllLinks(List sources) //with inheritance, conflic
{
Thought t = sources[i];
foreach (Thought child in t.Children)
- if (child.HasProperty("isInstance"))
+ {
+ Thought? isInstance = "isInstance";
+ if (isInstance is not null && child.HasProperty(isInstance))
sources.Add(child);
+ }
}
+ var querySources = sources.ToList();
var result1 = BuildSearchList(sources);
result2 = GetAllLinksInternal(result1);
if (result2.Count < 200) //the conflict-remover is really slow on large numbers
- RemoveConflictingResults(result2);
+ RemoveConflictingResults(result2, querySources);
RemoveFalseConditionals(result2);
SortLinks(ref result2);
return result2;
@@ -56,12 +60,13 @@ private void SortLinks(ref List result2)
//This is used to store temporary content during queries
private class ThoughtWithQueryParams
{
- public Thought thought;
+ public Thought thought = null!;
public int hopCount;
public int haveCount = 1;
public int hitCount = 1;
public float weight;
- public Thought reachedWith = null;
+ public Thought? reachedWith;
+ public Thought? querySource;
public bool corner = false;
public override string ToString()
{
@@ -70,54 +75,65 @@ public override string ToString()
}
}
- //this follows "inheritable" links...should it follow transitive too?
+ // BFS along inheritable links (is-a chains, etc.) with a hop cap for transitive inheritance.
private List BuildSearchList(List q)
{
+ const int maxHops = 8;
List thoughtsToExamine = new();
- int maxHops = 8;
- int hopCount = 0;
+ HashSet seen = new();
+
foreach (Thought t in q)
+ {
+ if (t is null || !seen.Add(t)) continue;
thoughtsToExamine.Add(new ThoughtWithQueryParams
{
thought = t,
- hopCount = hopCount,
+ hopCount = 0,
weight = 1,
- reachedWith = null
+ reachedWith = null,
+ querySource = t
});
- hopCount++;
- int currentEnd = thoughtsToExamine.Count;
+ }
+
for (int i = 0; i < thoughtsToExamine.Count; i++)
{
- Thought t = thoughtsToExamine[i].thought;
- float curWeight = thoughtsToExamine[i].weight;
- int curCount = thoughtsToExamine[i].haveCount;
- Thought reachedWith = thoughtsToExamine[i].reachedWith;
+ ThoughtWithQueryParams entry = thoughtsToExamine[i];
+ Thought t = entry.thought;
+ if (t is null) continue;
+
+ int nextHop = entry.hopCount + 1;
+ if (nextHop > maxHops) continue;
- foreach (Link r in t.LinksTo) //has-child et al
+ foreach (Link r in t.LinksTo)
{
- if (r.LinkType?.HasProperty("inheritable") == true)
+ Thought? inheritable = "inheritable";
+ if (inheritable is null || r.LinkType?.HasProperty(inheritable) != true || r.To is null)
+ continue;
+
+ if (thoughtsToExamine.FindFirst(x => x.thought == r.To) is ThoughtWithQueryParams twgp)
{
- if (thoughtsToExamine.FindFirst(x => x.thought == r.To) is ThoughtWithQueryParams twgp)
- twgp.hitCount++;//thought is in the list, increment its count
- else
- {//thought is not in the list, add it
- bool corner = !ThoughtInTree(r.LinkType, thoughtsToExamine[i].reachedWith) &&
- thoughtsToExamine[i].reachedWith is not null;
- if (corner)
- { } //TODO: corners are the reasons in a logic progression
- thoughtsToExamine[i].corner |= corner;
- ThoughtWithQueryParams thoughtToAdd = new ThoughtWithQueryParams
- {
- thought = r.To,
- hopCount = hopCount,
- weight = curWeight * r.Weight,
- reachedWith = r.LinkType,
- };
- thoughtsToExamine.Add(thoughtToAdd);
- //JUST FOR FUN: if thoughts have counts, the counts are multiplied... 2hands * 5 fingers/hand = 10 fingers
- int val = GetCount(r.LinkType);
- thoughtToAdd.haveCount = curCount * val;
- }
+ twgp.hitCount++;
+ if (nextHop < twgp.hopCount)
+ twgp.hopCount = nextHop;
+ }
+ else
+ {
+ bool corner = entry.reachedWith is not null &&
+ r.LinkType is not null &&
+ !ThoughtInTree(r.LinkType, entry.reachedWith);
+ entry.corner |= corner;
+ ThoughtWithQueryParams thoughtToAdd = new()
+ {
+ thought = r.To,
+ hopCount = nextHop,
+ weight = entry.weight * r.Weight,
+ reachedWith = r.LinkType,
+ querySource = entry.querySource ?? entry.thought,
+ };
+ int val = GetCount(r.LinkType);
+ thoughtToAdd.haveCount = entry.haveCount * val;
+ thoughtsToExamine.Add(thoughtToAdd);
+ seen.Add(r.To);
}
}
}
@@ -146,42 +162,59 @@ private List GetAllLinksInternal(List thoughtsToEx
Thought t = thoughtsToExamine[i].thought;
if (t is null) continue; //safety
int haveCount = thoughtsToExamine[i].haveCount;
+ int inheritanceDepth = thoughtsToExamine[i].hopCount;
+ Thought? querySource = thoughtsToExamine[i].querySource ?? t;
foreach (Link r in t.LinksTo)
{
if (r.LinkType == Thought.IsA) continue;
//only add the new relationship to the list if it is not already in the list
bool ignoreSource = thoughtsToExamine[i].hopCount > 1;
- Link existing = result.FindFirst(x => LinksAreEqual(x, r, ignoreSource));
+ Link? existing = result.FindFirst(x => LinksAreEqual(x, r, ignoreSource));
if (existing is not null) continue;
- if (haveCount > 1 && r.LinkType?.HasAncestor("has") is not null)
+ Thought? hasAncestor = "has";
+ if (haveCount > 1 && hasAncestor is not null && r.LinkType?.HasAncestor(hasAncestor) == true)
{
- Link r1 = new Link(r.From, r.LinkType, r.To)
+ if (r.From is null || r.LinkType is null || r.To is null) continue;
+ Link r1 = new Link(querySource, r.LinkType, r.To)
{
- Weight = r.Weight * thoughtsToExamine[i].weight
+ Weight = r.Weight * thoughtsToExamine[i].weight,
+ InheritanceDepth = inheritanceDepth,
+ InheritedFromCategory = inheritanceDepth > 0 ? t : null
};
- Thought newCountType = GetOrAddThought((GetCount(r.LinkType) * haveCount).ToString(), "number");
+ Thought? newCountType = GetOrAddThought((GetCount(r.LinkType) * haveCount).ToString(), "number");
//hack for numeric labels
- Thought rootThought = r1.LinkType;
+ Thought? rootThought = r1.LinkType;
if (r.LinkType.Label.Contains("."))
rootThought = GetOrAddThought(r.LinkType.Label.Substring(0, r.LinkType.Label.IndexOf(".")));
- Thought bestMatch = r.LinkType;
+ Thought? bestMatch = r.LinkType;
List missingAttributes = new();
- Thought newLinkType = SubclassExists(rootThought, new List { newCountType }, ref bestMatch, ref missingAttributes);
- if (newLinkType is null)
- newLinkType = CreateSubclass(rootThought, new List { newCountType });
+ Thought? newLinkType = null;
+ if (rootThought is not null && newCountType is not null)
+ {
+ newLinkType = SubclassExists(rootThought, new List { newCountType }, ref bestMatch, ref missingAttributes);
+ if (newLinkType is null)
+ newLinkType = CreateSubclass(rootThought, new List { newCountType });
+ }
+ if (newLinkType is null) continue;
r1.LinkType = newLinkType;
result.Add(r1);
}
else
{
- Link r1 = new Link(r.From, r.LinkType, r.To)
+ if (r.From is null || r.LinkType is null || r.To is null) continue;
+ Link r1 = new Link(querySource, r.LinkType, r.To)
{
- Weight = r.Weight * thoughtsToExamine[i].weight
+ Weight = r.Weight * thoughtsToExamine[i].weight,
+ InheritanceDepth = inheritanceDepth,
+ InheritedFromCategory = inheritanceDepth > 0 ? t : null
};
foreach (Link r3 in r.LinksTo.Where(x => x.LinkType?.Label != "is-a"))
- r1.AddLink(r3.LinkType, r3.To);
+ {
+ if (r3.LinkType is not null)
+ r1.AddLink(r3.LinkType, r3.To);
+ }
result.Add(r1);
}
}
@@ -189,7 +222,9 @@ private List GetAllLinksInternal(List thoughtsToEx
return result;
}
- private void RemoveConflictingResults(List result)
+ // Ch.5 exception rule: more-specific (lower inheritance depth) wins over inherited defaults.
+ // Tie-break: link From on a query source, then higher Weight.
+ private void RemoveConflictingResults(List result, List querySources)
{
for (int i = 0; i < result.Count; i++)
{
@@ -205,29 +240,44 @@ private void RemoveConflictingResults(List result)
for (int j = i + 1; j < result.Count; j++)
{
Link r2 = result[j];
- //are the results the same?
- if (r1.LinkType == r2.LinkType && r1.To == r2.To)
+ bool duplicate = r1.LinkType == r2.LinkType && r1.To == r2.To;
+ bool exclusive = LinksAreExclusive(r1, r2);
+ if (!duplicate && !exclusive) continue;
+
+ Link keep = PreferMoreSpecificLink(r1, r2, querySources);
+ Link drop = keep == r1 ? r2 : r1;
+ int dropIndex = drop == r1 ? i : j;
+ result.RemoveAt(dropIndex);
+ if (dropIndex == i)
{
- result.RemoveAt(j);
- j--;
- }
- //if (r1.LinkType?.Label.Contains(".") == true && r2.LinkType?.Label.Contains(".") == true)
- if (LinksAreExclusive(r1, r2))
- {
- //if two links are in conflict, delete the 2nd one (First takes priority)
- result.RemoveAt(j);
- j--;
+ i--;
+ break;
}
+ j--;
}
}
}
+ private static Link PreferMoreSpecificLink(Link r1, Link r2, List querySources)
+ {
+ if (r1.InheritanceDepth != r2.InheritanceDepth)
+ return r1.InheritanceDepth < r2.InheritanceDepth ? r1 : r2;
+
+ bool r1OnSource = querySources.Contains(r1.From);
+ bool r2OnSource = querySources.Contains(r2.From);
+ if (r1OnSource != r2OnSource)
+ return r1OnSource ? r1 : r2;
+
+ return r1.Weight >= r2.Weight ? r1 : r2;
+ }
+
private void RemoveFalseConditionals(List result)
{
for (int i = 0; i < result.Count; i++)
{
Link r1 = result[i];
- if (!r1.HasProperty("isResult")) continue;
+ Thought? isResult = "isResult";
+ if (isResult is null || !r1.HasProperty(isResult)) continue;
if (!ConditionsAreMet(r1))
{
failedConditions.Add(r1);
@@ -281,12 +331,14 @@ int GetCount(Thought t)
bool ConditionsAreMet(Link r)
{
+ Thought? isResult = "isResult";
+ Thought? isCondition = "isCondition";
foreach (Link r1 in r.LinksTo)
{
- if (r1.From?.HasProperty("isResult") != true) continue;
- if (r1.To?.HasProperty("isCondition") != true) continue;
+ if (isResult is null || r1.From?.HasProperty(isResult) != true) continue;
+ if (isCondition is null || r1.To?.HasProperty(isCondition) != true) continue;
- Link r2 = r1.To as Link;
+ Link? r2 = r1.To as Link;
//is r1 true?
if (GetUnconditionalLink(r2) is null)
return false;
@@ -294,14 +346,15 @@ bool ConditionsAreMet(Link r)
return true;
}
- Link GetUnconditionalLink(Link r)
+ Link? GetUnconditionalLink(Link? r)
{
if (r?.From is null) return null;
+ Thought? isCondition = "isCondition";
foreach (Link r1 in r.From.LinksTo)
{
if (Equals(r, r1))
{
- if (!r1.HasProperty("isCondition"))
+ if (isCondition is null || !r1.HasProperty(isCondition))
return r1;
}
}
@@ -326,7 +379,7 @@ public List Why()
return succeededConditions;
}
- Dictionary searchCandidates;
+ Dictionary searchCandidates = null!;
///
/// Search for the Thought which most closely resembles the target Thought based on the attributes of the target.
@@ -350,13 +403,17 @@ public List Why()
if (r.To is SeqElement s)
{
var x = FlattenSequence(s); //if this is a sequence fragment, try to get the whole sequence
+ if (r.LinkType is null) continue;
var y = HasSequence(x, r.LinkType);
foreach (var z in y)
{
foreach (var w in z.seqNode.LinksFrom.Where(x => x.From != target))
{
+ if (w.From is null) continue;
var existing = thoughtsToSearch.FindFirst(x => x == w.From);
- if ((w.LinkType == r.LinkType || w.LinkType?.HasAncestor(r.LinkType) == true) && r.To == r.To && existing is null)
+ if (r.LinkType is not null &&
+ (w.LinkType == r.LinkType || w.LinkType?.HasAncestor(r.LinkType) == true) &&
+ r.To == r.To && existing is null)
{
thoughtsToSearch.Add(w.From);
if (!searchCandidates.ContainsKey(w.From))
@@ -372,9 +429,10 @@ public List Why()
}
foreach (Link r1 in r.To?.LinksFrom ?? Enumerable.Empty())
{
- if (r1.From == target) continue;
+ if (r1.From == target || r1.From is null) continue;
var existing = thoughtsToSearch.FindFirst(x => x == r1.From);
- if ((r1.LinkType == r.LinkType || r1.LinkType?.HasAncestor(r.LinkType) == true) &&
+ if (r.LinkType is not null &&
+ (r1.LinkType == r.LinkType || r1.LinkType?.HasAncestor(r.LinkType) == true) &&
r1.From.HasAncestor(root) &&
r1.To == r.To && existing is null)
{
@@ -397,8 +455,9 @@ public List Why()
alreadySearched.Add(t);
foreach (Link r in t.LinksFrom)
{
- if (r.LinkType?.HasProperty("inheritable") != true) continue;
- if (r.From == target) continue;
+ Thought? inheritable = "inheritable";
+ if (inheritable is null || r.LinkType?.HasProperty(inheritable) != true) continue;
+ if (r.From == target || r.From is null) continue;
AddToQueues(t, r.From);
//TODO fix this to handle isSimilarTo (and transitive...?)
//var similarThoughts = GetListOfSimilarThoughts(r.source);
@@ -473,6 +532,7 @@ private bool ThoughtsHaveConflictingLink(Thought source, Thought target)
private bool LinksAreSimilar(Link r1, Link r2)
{
if (r1.LinkType != r2.LinkType) return false;
+ if (r1.To is null || r2.To is null) return false;
if (FindCommonParents(r1.To, r2.To).Count == 0) return false;
return true;
}
@@ -502,13 +562,13 @@ public bool ThoughtsHaveSimilarLink(Thought source, Thought target)
public List SearchForRelationships(Link l)
{
List results = new List();
- Thought from = l.From?.Label.Contains("??") is true ? null : l.From;
- Thought linkType = l.LinkType?.Label.Contains("??") is true ? null : l.LinkType;
- Thought to = l.To?.Label.Contains("??") is true ? null : l.To;
+ Thought? from = l.From?.Label.Contains("??") is true ? null : l.From;
+ Thought? linkType = l.LinkType?.Label.Contains("??") is true ? null : l.LinkType;
+ Thought? to = l.To?.Label.Contains("??") is true ? null : l.To;
if (from is null && to is null && linkType is null) return results;
// hack to handle is-a searches
- if (linkType.Label == "is-a" && from is not null)
+ if (linkType is not null && linkType.Label == "is-a" && from is not null)
{
foreach (Thought child in from.Parents)
{
@@ -517,25 +577,33 @@ public List SearchForRelationships(Link l)
return results;
}
// If from is specified, start there for efficiency (most constrained search)
- if (from != null)
+ if (from is not null)
{
var attribs = GetAllLinks(new List { from });
foreach (Link link in attribs)
{
- if ((linkType == null || link.LinkType.HasAncestor(linkType)) &&
- (to == null || link.To == to))
+ if ((linkType is null || link.LinkType?.HasAncestor(linkType) == true) &&
+ (to is null || link.To == to))
{
// results.Add(link);
- results.Add(new Link { From = from, LinkType = link.LinkType, To = link.To } );
+ results.Add(new Link
+ {
+ From = from,
+ LinkType = link.LinkType,
+ To = link.To,
+ Weight = link.Weight,
+ InheritanceDepth = link.InheritanceDepth,
+ InheritedFromCategory = link.InheritedFromCategory
+ });
}
}
}
// If from is null but to is specified, search backwards from to
- else if (to != null)
+ else if (to is not null)
{
foreach (Link link in to.LinksFrom)
{
- if (linkType == null || link.LinkType == linkType)
+ if (linkType is null || link.LinkType == linkType)
{
results.Add(link);
}
diff --git a/UKS/UKS.Sequence.cs b/UKS/UKS.Sequence.cs
index d57d18b..75aa05d 100644
--- a/UKS/UKS.Sequence.cs
+++ b/UKS/UKS.Sequence.cs
@@ -11,6 +11,8 @@
* See the LICENSE file in the project root for full license information.
*/
+#nullable disable
+
using Microsoft.VisualBasic;
using System.Runtime.InteropServices;
using static UKS.UKS;
@@ -23,11 +25,11 @@ public class SeqElement : Thought
/// Default constructor for sequence element placeholder.
///
public SeqElement() { }
- public SeqElement? FRST
+ public SeqElement FRST
{
get
{
- Link? nxt = LinksToWriteable.FindFirst(x => x.LinkType?.Label == "FRST");
+ Link nxt = LinksToWriteable.FindFirst(x => x.LinkType?.Label == "FRST");
return nxt?.To as SeqElement;
}
set
@@ -40,11 +42,11 @@ public SeqElement? FRST
AddLink(nxtType, value);
}
}
- public SeqElement? NXT
+ public SeqElement NXT
{
get
{
- Link? nxt = LinksTo.FindFirst(x => x.LinkType?.Label == "NXT");
+ Link nxt = LinksTo.FindFirst(x => x.LinkType?.Label == "NXT");
return nxt?.To as SeqElement;
}
set
@@ -57,11 +59,11 @@ public SeqElement? NXT
AddLink(nxtType, value);
}
}
- public Thought? VLU
+ public Thought VLU
{
get
{
- Link? nxt = LinksTo.FindFirst(x => x.LinkType?.Label == "VLU");
+ Link nxt = LinksTo.FindFirst(x => x.LinkType?.Label == "VLU");
return nxt?.To;
}
set
@@ -201,7 +203,21 @@ public SeqElement InsertElement(SeqElement prevElementIn, Thought value)
}
else
{
- throw new NotImplementedException();
+ SeqElement predecessor = prevElementIn.FRST;
+ while (predecessor?.NXT is not null && predecessor.NXT != prevElementIn)
+ predecessor = predecessor.NXT;
+ if (predecessor is null || predecessor.NXT != prevElementIn)
+ throw new ArgumentException("prevElementIn is not in its FRST chain", nameof(prevElementIn));
+
+ SeqElement newNode = new()
+ {
+ Label = prevElementIn.Label + "*",
+ FRST = prevElementIn.FRST,
+ NXT = prevElementIn,
+ };
+ newNode.AddLink("VLU", value);
+ predecessor.NXT = newNode;
+ return prevElementIn.FRST ?? first;
}
return first;
}
@@ -308,7 +324,7 @@ public int GetHashCode(IReadOnlyList list)
}
public SeqElement AddSequence(string label, List targets, bool allowCompression = true)
{
- if (targets.Count < 1) return null; //a sequence must have at least 2 elements
+ if (targets.Count < 2) return null; //a sequence must have at least 2 elements
List resolvedTargets = new(targets);
@@ -370,7 +386,7 @@ public SeqElement AddSequence(string label, List targets, bool allowCom
/// The first node of the created or reused sequence, or null if insufficient targets.
public SeqElement AddSequenceAndLink(Thought source, Thought linkType, List targets, float baseWeight = 1.0f)
{
- if (targets.Count < 1) return null; //a sequence must have at least 2 elements
+ if (targets.Count < 2) return null; //a sequence must have at least 2 elements
//clear out any existing sequence links of this type
source.RemoveLinks(linkType); //TODO delete the sequence
@@ -414,7 +430,10 @@ public Thought GetReferrer(SeqElement seqNode, Thought linkType)
public List<(SeqElement seqNode, float confidence)> HasSequence(List targets, Thought linkType,
bool mustMatchFirst = false, bool mustMatchLast = false, bool circularSearch = false, bool allowOutOfOrder = false)
{
- //this function searches the UKS for sequences matching the specified pattern in targets.
+ if (circularSearch || allowOutOfOrder)
+ throw new NotSupportedException("circularSearch and allowOutOfOrder are not yet implemented.");
+
+ //this function searches the UKS for sequences matching the specified pattern in targets.
//If circularSearch is true, then the search will consider sequences that wrap around from end to start Thought.
//If firstLastPriority is true, then matches that have the first and last elements matching will be given higher confidence
@@ -449,7 +468,7 @@ public Thought GetReferrer(SeqElement seqNode, Thought linkType)
// These are potential starting points for matching sequences
// When this returns, seqNode is the first matching node. curPos.Current is the last
- List<(SeqElement seqNode, IEnumerator? curPos, int matchCount)> searchCandidates = RawSearchExact(targets);
+ List<(SeqElement seqNode, IEnumerator curPos, int matchCount)> searchCandidates = RawSearchExact(targets);
if (searchCandidates.Count == 0) return retVal;
//Do we want to follow up the chain of referrers?
@@ -628,9 +647,9 @@ private static float ComputeOrderPairFraction(List seq, List t
float score = count / (Math.Max(seq.Count, targets.Count) - 1);
return score;
}
- public List<(SeqElement seqNode, IEnumerator? curPos, int matchCount)> RawSearchExact(List targets)
+ public List<(SeqElement seqNode, IEnumerator curPos, int matchCount)> RawSearchExact(List targets)
{
- List<(SeqElement seqNode, IEnumerator? curPos, int matchCount)> searchCandidates = new();
+ List<(SeqElement seqNode, IEnumerator