add: Generator

This commit is contained in:
h z
2025-01-22 09:46:30 +00:00
parent 081863bde2
commit f428c6f3a5
6 changed files with 137 additions and 28 deletions

View File

@@ -3,7 +3,9 @@
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Nullable>disable</Nullable>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Version>0.0.2</Version>
</PropertyGroup>
</Project>

View File

@@ -20,8 +20,8 @@ public class Board
public required int Height { get; init; }
public required bool QuotientX { get; init; }
public required bool QuotientY { get; init; }
public HashSet<(int, int)> Lives { get; init; }
public CellTracer? Tracer { get; init; }
public HashSet<(int, int)> Lives { get; internal set; }
public CellTracer Tracer { get; init; }
public void Toggle(int x, int y, bool initialConfig=false)
{

View File

@@ -292,7 +292,7 @@ public class Gene
return res;
}
public static Gene Hybrid(Gene a, Gene b, double[]? weightA = null, double[]? weightB = null)
public static Gene Hybrid(Gene a, Gene b, double[] weightA = null, double[] weightB = null)
{
if (a.Resolution != b.Resolution)
throw new ArgumentException("Resolutions of both genes must match.");
@@ -312,7 +312,9 @@ public class Gene
{
byte[] ra = a[i];
byte[] rb = b[i];
double ratio = weightA[i] / (weightA[i] + weightB[i] + 1E-5d);
double ratio = 0.5d;
if(weightA[i] + weightB[i] != 0)
ratio = weightA[i] / (weightA[i] + weightB[i]);
res[i] = random.NextDouble() < ratio ? ra : rb;
}

62
src/Generator.cs Normal file
View File

@@ -0,0 +1,62 @@
namespace InverseOfLife;
public class Generator
{
public static Dictionary<(int, int), float>[] Generate(Board target, int layers, int steps, int split=1, string mode="tp_only")
{
HashSet<(int, int)>[] targetCell = new HashSet<(int, int)> [split];
Board[] targets = new Board[split];
HashSet<(int, int)> alives = target.Lives;
for (int i = 0; i < split; i++)
targetCell[i] = new HashSet<(int, int)>();
int idx = 0;
foreach ((int, int) cell in alives)
{
idx = (idx + 1) % split;
targetCell[idx].Add(cell);
}
for (int i = 0; i < split; i++)
{
targets[i] = new Board(target.Width, target.Height, target.QuotientX, target.QuotientY);
targets[i].Lives = targetCell[i];
}
idx = 0;
Board[] res = new Board[layers * split];
foreach (Board t in targets)
{
Solver s = new Solver(t, steps);
for (int i = 0; i < layers; i++)
{
if (idx > layers * split)
break;
(Gene g, double x) = s.Solve(mode: mode);
res[idx] = g.Restore(target.Width, target.Height, target.QuotientX, target.QuotientY);
idx += 1;
Console.WriteLine($"Progress: {idx}/{layers * split}");
}
}
Dictionary<(int, int), float>[] result = new Dictionary<(int, int), float>[steps];
for (int i = 0; i < steps; i++)
{
result[i] = new Dictionary<(int, int), float>();
foreach (Board b in res)
{
foreach ((int, int) cell in b.Lives)
{
if(!result[i].Keys.Contains(cell))
result[i][cell] = 0f;
result[i][cell] += 1f/(layers * split);
}
b.Evaluate();
}
}
return result;
}
}

View File

@@ -1,3 +1,5 @@
using System.Diagnostics.CodeAnalysis;
namespace InverseOfLife;
public class Solver
@@ -6,12 +8,14 @@ public class Solver
{
Target = target;
Steps = steps;
YS = target.ToString();
}
public Board Target { get; init; }
private int Steps { get; set; }
private string YS { get; set; }
private static void SortBest((Board? b, Gene? g, double s)[] best)
private static void SortBest((Board b, Gene g, double s)[] best)
{
int c = best.Length-1;
while (c != 0)
@@ -25,15 +29,23 @@ public class Solver
}
public (Gene, double) Solve(int withResolution=2, int maxGenerations=50, int topN=10, float mutationRate=0.1f)
private static byte[] RandomBytes(int length)
{
byte[] res = new byte[length];
Random.Shared.NextBytes(res);
return res;
}
[SuppressMessage("ReSharper.DPA", "DPA0002: Excessive memory allocations in SOH", MessageId = "type: Entry[System.Int32,System.Double][]; size: 14391MB")]
public (Gene, double) Solve(int withResolution=1, int maxGenerations=50, int topN=10, float mutationRate=0.1f, string mode="tp_only")
{
List<(Board, Gene)> currentGeneration = GetInitialGeneration()
.Select(x => (x, new Gene(withResolution, x)))
.ToList();
Gene res;
Random random = new();
(int, int) selectParent() => (random.Next() % topN, random.Next() % topN);
(Board? b, Gene? gene, double score)[] best = new (Board?, Gene?, double)[topN + 1];
(int, int) SelectParent() => (random.Next() % topN, random.Next() % topN);
(Board b, Gene gene, double score)[] best = new (Board, Gene, double)[topN + 1];
for (int i = 0; i < topN + 1; i++)
best[i] = (null, null, 0);
for (int gen = 0; gen < maxGenerations; gen++)
@@ -42,23 +54,31 @@ public class Solver
foreach ((Board b, Gene g) in currentGeneration)
{
b.Evaluate(Steps);
double score = Summarizer.Score(b, Target, false);
double score = Summarizer.Score(b, Target, mode);
best[topN] = (b, g, score);
SortBest(best);
}
for (int i = 0; i < maxGenerations; i++)
{
(int pa, int pb) = selectParent();
(int pa, int pb) = SelectParent();
double[] weightA = Summarizer.GetWeight(best[pa].b!, Target, withResolution, true);
double[] weightB = Summarizer.GetWeight(best[pb].b!, Target, withResolution, true);
Gene parentA = best[pa].gene!.Mutate(weightA, mutationRate);
Gene parentB = best[pb].gene!.Mutate(weightB, mutationRate);
double[] weightA = Summarizer.GetWeight(best[pa].b, Target, withResolution, mode);
double[] weightB = Summarizer.GetWeight(best[pb].b, Target, withResolution, mode);
Gene parentA = best[pa].gene.Mutate(weightA, mutationRate);
Gene parentB = best[pb].gene.Mutate(weightB, mutationRate);
Gene newGene = Gene.Hybrid(parentA, parentB, weightA, weightB);
Board b = newGene.Restore(Target.Width, Target.Height, Target.QuotientX, Target.QuotientY, true);
nextGeneration.Add((b, newGene));
}
for (int i = 0; i < maxGenerations / 5; i++)
{
Gene ng = new Gene(withResolution, RandomBytes(best[0].gene.RiboseSequence.Length));
Board b = ng.Restore(Target.Width, Target.Height, Target.QuotientX, Target.QuotientY, true);
nextGeneration.Add((b, ng));
}
Console.WriteLine($"--------{best[0].score}");
currentGeneration = nextGeneration.ToList();
}

View File

@@ -33,31 +33,48 @@ public static class Summarizer
return res;
}
public static double Score(Board yAct, Board yGnd, bool tpOnly = false)
public static double Score(Board yAct, Board yGnd, string mode = "tp_only")
{
if (yAct.Height != yGnd.Height || yAct.Width != yGnd.Width || yAct.QuotientX != yGnd.QuotientX ||
yAct.QuotientY != yGnd.QuotientY)
throw new Exception();
bool tpOnly = mode == "tp_only";
bool tnOnly = mode == "tn_only";
bool dc = mode == "dynamic_combine";
double sx = yGnd.Width * yGnd.Height;
double p = yGnd.Lives.Count;
double n = sx - p;
double tp = 0;
HashSet<(int, int)> allAlives = yAct.Lives.Union(yGnd.Lives).ToHashSet();
double tn = yGnd.Height * yGnd.Width - allAlives.Count;
double tn = sx - allAlives.Count;
foreach ((int, int) cell in yAct.Lives)
{
if (yGnd.Lives.Contains(cell))
tp += 1d;
}
double txp = yGnd.Lives.Count > 0 ? tp / p : 1d;
if (tpOnly)
return txp;
double txn = tn / n;
if (tnOnly)
return txn;
if (dc)
{
if (yGnd.Lives.Count > 0)
return tp / yGnd.Lives.Count;
return 1d;
double rp = n / sx;
double rn = p / sx;
return txp * rp + txn * rn;
}
return (tp + tn) / (yGnd.Height * yGnd.Width);
}
public static double[] ScoreBySubGrid(Board yAct, Board yGnd, int resolution, bool tpOnly = false)
public static double[] ScoreBySubGrid(Board yAct, Board yGnd, int resolution, string mode="tp_only")
{
if (yAct.Height != yGnd.Height || yAct.Width != yGnd.Width || yAct.QuotientX != yGnd.QuotientX ||
yAct.QuotientY != yGnd.QuotientY)
@@ -96,6 +113,9 @@ public static class Summarizer
}
bool tpOnly = mode == "tp_only";
bool tnOnly = mode == "tn_only";
bool dc = mode == "dynamic_combine";
for (int subGridY = 0; subGridY < subGridHeight; subGridY++)
{
for (int subGridX = 0; subGridX < subGridWidth; subGridX++)
@@ -106,16 +126,19 @@ public static class Summarizer
int subGridStartY = subGridY * resolution;
int subGridEndX = Math.Min(subGridStartX + resolution, yAct.Width);
int subGridEndY = Math.Min(subGridStartY + resolution, yAct.Height);
int cellsInSubGrid = (subGridEndX - subGridStartX) * (subGridEndY - subGridStartY);
double tn = cellsInSubGrid - actAlive[subGridIdx].Union(gndAlive[subGridIdx]).Count();
double tp = tps[subGridIdx];
double txp = gndAlive[subGridIdx].Count > 0 ? tp / gndAlive[subGridIdx].Count : 1d;
double txn = tn / (cellsInSubGrid - gndAlive[subGridIdx].Count);
if(tpOnly)
res[subGridIdx] = txp;
else if (tnOnly)
res[subGridIdx] = txn;
else if (dc)
{
if (gndAlive[subGridIdx].Count > 0)
res[subGridIdx] = tp / gndAlive[subGridIdx].Count;
else
res[subGridIdx] = 1d;
}
else
res[subGridIdx] = (tp + tn) / cellsInSubGrid;
@@ -124,12 +147,12 @@ public static class Summarizer
return res;
}
public static double[] GetWeight(Board yAct, Board yGnd, int resolution, bool tpOnly=false)
public static double[] GetWeight(Board yAct, Board yGnd, int resolution, string mode = "tp_only")
{
if (yAct.Tracer is null)
throw new Exception("tracer was not enabled");
Dictionary<int, Dictionary<int, double>> contribution = ContributionBySubGrid(yAct, resolution);
double[] score = ScoreBySubGrid(yAct, yGnd, resolution, tpOnly);
double[] score = ScoreBySubGrid(yAct, yGnd, resolution, mode);
int subgridWidth = (yAct.Width + resolution - 1) / resolution;
int subgridHeight = (yAct.Height + resolution - 1) / resolution;
double[] res = new double[subgridWidth * subgridHeight];