/*
* Symbolic Morphology Engine — Latin verb morphology from raw letters to Boolean grammar
* Copyright (C) 2026 Ahmad Ali Parr
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
namespace Sovereign.Engine;
///
/// SYMBOLIC LAYER — Boolean grammatical feature space.
///
/// 23 Boolean features organized into 6 grammatical categories.
/// The network outputs continuous values in [0,1]; these are thresholded
/// to produce discrete Boolean grammatical states.
///
public static class Features
{
public static readonly string[] Names =
{
"PERSON_1", "PERSON_2", "PERSON_3",
"SINGULAR", "PLURAL",
"PRESENT", "IMPERFECT", "FUTURE", "PERFECT",
"INDICATIVE", "SUBJUNCTIVE","IMPERATIVE",
"ACTIVE", "PASSIVE",
"CONJ_1", "CONJ_2", "CONJ_3",
};
public static readonly (string Category, string[] FeatureNames)[] Groups =
{
("PERSON", new[] { "PERSON_1", "PERSON_2", "PERSON_3" }),
("NUMBER", new[] { "SINGULAR", "PLURAL" }),
("TENSE", new[] { "PRESENT", "IMPERFECT", "FUTURE", "PERFECT" }),
("MOOD", new[] { "INDICATIVE", "SUBJUNCTIVE", "IMPERATIVE" }),
("VOICE", new[] { "ACTIVE", "PASSIVE" }),
("CONJUGATION", new[] { "CONJ_1", "CONJ_2", "CONJ_3" }),
};
public const int Count = 23;
public const double BooleanThreshold = 0.5;
static readonly Dictionary Index = BuildIndex();
static Dictionary BuildIndex()
{
var idx = new Dictionary();
for (int i = 0; i < Names.Length; i++)
idx[Names[i]] = i;
return idx;
}
///
/// Build a target vector from named feature assignments.
/// Each entry maps a category to either a feature name (string)
/// or a positional index (int) within that category.
///
public static double[] MakeTarget(params (string Category, object Value)[] specs)
{
var t = new double[Count];
foreach (var (category, value) in specs)
{
var group = Groups.First(g => g.Category == category);
int fi;
if (value is string s)
fi = Array.IndexOf(group.FeatureNames, s);
else if (value is int i)
fi = i;
else
throw new ArgumentException($"Value must be string or int, got {value.GetType()}");
if (fi < 0 || fi >= group.FeatureNames.Length)
throw new ArgumentException($"Invalid feature '{value}' in category '{category}'");
t[Index[group.FeatureNames[fi]]] = 1.0;
}
return t;
}
public static string FormatPrediction(double[] values)
{
var lines = new List();
foreach (var (category, featureNames) in Groups)
{
lines.Add($" {category}:");
foreach (var name in featureNames)
{
int idx = Index[name];
double v = values[idx];
bool b = v >= BooleanThreshold;
lines.Add($" {name,-14} = {v,8:F5} -> {(b ? "TRUE" : "FALSE")}");
}
}
return string.Join("\n", lines);
}
public static int CountCorrect(double[] predicted, double[] target)
{
int correct = 0;
for (int i = 0; i < Count; i++)
{
bool pred = predicted[i] >= BooleanThreshold;
bool tgt = target[i] >= 0.5;
if (pred == tgt) correct++;
}
return correct;
}
}