diff --git a/InverseOfLife.csproj b/InverseOfLife.csproj index 3a63532..b55ac43 100644 --- a/InverseOfLife.csproj +++ b/InverseOfLife.csproj @@ -3,7 +3,9 @@ net8.0 enable - enable + disable + true + 0.0.2 diff --git a/src/Board.cs b/src/Board.cs index e4b83c9..d245af4 100644 --- a/src/Board.cs +++ b/src/Board.cs @@ -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) { diff --git a/src/Gene.cs b/src/Gene.cs index 79986e7..8563177 100644 --- a/src/Gene.cs +++ b/src/Gene.cs @@ -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; } diff --git a/src/Generator.cs b/src/Generator.cs new file mode 100644 index 0000000..ccd5cc2 --- /dev/null +++ b/src/Generator.cs @@ -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; + } + +} \ No newline at end of file diff --git a/src/Solver.cs b/src/Solver.cs index ee232db..c1e07dc 100644 --- a/src/Solver.cs +++ b/src/Solver.cs @@ -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(); } diff --git a/src/Summarizer.cs b/src/Summarizer.cs index 822c3e8..c50fc0b 100644 --- a/src/Summarizer.cs +++ b/src/Summarizer.cs @@ -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> 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];