commit 081863bde25c493320bbb927412ecd4c86a0afe3 Author: hzhang Date: Sat Jan 18 08:16:40 2025 +0000 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..add57be --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +bin/ +obj/ +/packages/ +riderModule.iml +/_ReSharper.Caches/ \ No newline at end of file diff --git a/.idea/.idea.InverseGameOfLife/.idea/.gitignore b/.idea/.idea.InverseGameOfLife/.idea/.gitignore new file mode 100644 index 0000000..3b74fd7 --- /dev/null +++ b/.idea/.idea.InverseGameOfLife/.idea/.gitignore @@ -0,0 +1,13 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/projectSettingsUpdater.xml +/modules.xml +/contentModel.xml +/.idea.InverseGameOfLife.iml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/.idea.InverseGameOfLife/.idea/encodings.xml b/.idea/.idea.InverseGameOfLife/.idea/encodings.xml new file mode 100644 index 0000000..df87cf9 --- /dev/null +++ b/.idea/.idea.InverseGameOfLife/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/.idea.InverseGameOfLife/.idea/indexLayout.xml b/.idea/.idea.InverseGameOfLife/.idea/indexLayout.xml new file mode 100644 index 0000000..7b08163 --- /dev/null +++ b/.idea/.idea.InverseGameOfLife/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.InverseGameOfLife/.idea/inspectionProfiles/Project_Default.xml b/.idea/.idea.InverseGameOfLife/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..8d66637 --- /dev/null +++ b/.idea/.idea.InverseGameOfLife/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/.idea.InverseGameOfLife/.idea/vcs.xml b/.idea/.idea.InverseGameOfLife/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/.idea.InverseGameOfLife/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/.idea.InverseOfLife/.idea/.gitignore b/.idea/.idea.InverseOfLife/.idea/.gitignore new file mode 100644 index 0000000..faef621 --- /dev/null +++ b/.idea/.idea.InverseOfLife/.idea/.gitignore @@ -0,0 +1,13 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/contentModel.xml +/projectSettingsUpdater.xml +/modules.xml +/.idea.InverseOfLife.iml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/.idea.InverseOfLife/.idea/encodings.xml b/.idea/.idea.InverseOfLife/.idea/encodings.xml new file mode 100644 index 0000000..df87cf9 --- /dev/null +++ b/.idea/.idea.InverseOfLife/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/.idea.InverseOfLife/.idea/indexLayout.xml b/.idea/.idea.InverseOfLife/.idea/indexLayout.xml new file mode 100644 index 0000000..7b08163 --- /dev/null +++ b/.idea/.idea.InverseOfLife/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/InverseOfLife.csproj b/InverseOfLife.csproj new file mode 100644 index 0000000..3a63532 --- /dev/null +++ b/InverseOfLife.csproj @@ -0,0 +1,9 @@ + + + + net8.0 + enable + enable + + + diff --git a/InverseOfLife.sln b/InverseOfLife.sln new file mode 100644 index 0000000..99e88b6 --- /dev/null +++ b/InverseOfLife.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InverseOfLife", "InverseOfLife.csproj", "{4DEC9892-1EE7-4054-9C9F-4ED9E270BF91}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {4DEC9892-1EE7-4054-9C9F-4ED9E270BF91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4DEC9892-1EE7-4054-9C9F-4ED9E270BF91}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4DEC9892-1EE7-4054-9C9F-4ED9E270BF91}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4DEC9892-1EE7-4054-9C9F-4ED9E270BF91}.Release|Any CPU.Build.0 = Release|Any CPU + {736B41B9-3DE3-47AC-A4EC-95C68DF07040}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {736B41B9-3DE3-47AC-A4EC-95C68DF07040}.Debug|Any CPU.Build.0 = Debug|Any CPU + {736B41B9-3DE3-47AC-A4EC-95C68DF07040}.Release|Any CPU.ActiveCfg = Release|Any CPU + {736B41B9-3DE3-47AC-A4EC-95C68DF07040}.Release|Any CPU.Build.0 = Release|Any CPU + {F4D733EA-AEDD-4676-9494-A6F6F88EE206}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F4D733EA-AEDD-4676-9494-A6F6F88EE206}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F4D733EA-AEDD-4676-9494-A6F6F88EE206}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F4D733EA-AEDD-4676-9494-A6F6F88EE206}.Release|Any CPU.Build.0 = Release|Any CPU + {60B6F87D-0805-4F1A-9706-48297A3013E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {60B6F87D-0805-4F1A-9706-48297A3013E1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {60B6F87D-0805-4F1A-9706-48297A3013E1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {60B6F87D-0805-4F1A-9706-48297A3013E1}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/src/Board.cs b/src/Board.cs new file mode 100644 index 0000000..e4b83c9 --- /dev/null +++ b/src/Board.cs @@ -0,0 +1,142 @@ +using System.Diagnostics.CodeAnalysis; + +namespace InverseOfLife; + +public class Board +{ + [SetsRequiredMembers] + public Board(int w, int h, bool qx = false, bool qy = false, bool useTracer = false) + { + Width = w; + Height = h; + QuotientX = qx; + QuotientY = qy; + Lives = new(); + if (useTracer) + Tracer = new CellTracer(); + } + + public required int Width { get; init; } + 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 void Toggle(int x, int y, bool initialConfig=false) + { + bool added = Lives.Add((x, y)); + if (!added) + { + Lives.Remove((x, y)); + if (initialConfig && Tracer is not null) + Tracer.Contribution.Remove((x, y)); + } + else if (initialConfig && Tracer is not null) + { + Tracer.Contribution[(x, y)] = new Dictionary<(int, int), double> + { + { (x, y), 1d } + }; + } + } + + public void Toggle((int x, int y) cell, bool initialConfig = false) => Toggle(cell.x, cell.y, initialConfig); + + public void Evaluate(int steps) + { + for(int i = 0; i< steps; i++) + Evaluate(); + } + + public void Evaluate() + { + Dictionary<(int, int), int> neighbourCount = new(); + foreach ((int x, int y) in Lives) + { + if (!neighbourCount.ContainsKey((x, y))) + neighbourCount[(x, y)] = 0; + Populate(x, y, neighbourCount); + } + + foreach ((int x, int y) in neighbourCount.Keys) + { + if (Lives.Contains((x, y))) + { + if (neighbourCount[(x, y)] != 2 && neighbourCount[(x, y)] != 3) + { + Lives.Remove((x, y)); + if (Tracer is not null) + Tracer.DeferredRemove((x, y)); + } + } + else + { + if (neighbourCount[(x, y)] == 3) + { + Lives.Add((x, y)); + if (Tracer is not null) + Tracer.DeferredAdd((x, y)); + } + } + } + + if (Tracer is not null) + Tracer.BatchProcess(); + } + + public void Populate(int x, int y, Dictionary<(int, int), int> count) + { + for (int dx = -1; dx <= 1; dx++) + { + for (int dy = -1; dy <= 1; dy++) + { + if (dx == 0 && dy == 0) + continue; + int nx = QuotientX ? (x + dx + Width) % Width : x + dx; + int ny = QuotientY ? (y + dy + Height) % Height : y + dy; + if(nx >= Width || nx < 0) + continue; + if(ny >= Height || ny < 0) + continue; + if (!count.ContainsKey((nx, ny))) + count[(nx, ny)] = 0; + count[(nx, ny)] += 1; + if (Tracer is not null) + { + if (!Tracer.Parents.ContainsKey((nx, ny))) + Tracer.Parents[(nx, ny)] = new HashSet<(int, int)>(); + Tracer.Parents[(nx, ny)].Add((x, y)); + } + } + } + } + + public override string ToString() + { + + var builder = new System.Text.StringBuilder(); + + for (int y = 0; y < Height; y++) + { + for (int x = 0; x < Width; x++) + builder.Append(Lives.Contains((x, y)) ? 'o' : ' '); + + builder.AppendLine(); + } + + return builder.ToString(); + } + + + public void Play(int generations, int delay = 200) + { + for (int i = 0; i < generations; i++) + { + Console.Clear(); + Console.WriteLine(ToString()); + Evaluate(); + Thread.Sleep(delay); + } + } +} \ No newline at end of file diff --git a/src/CellTracer.cs b/src/CellTracer.cs new file mode 100644 index 0000000..6b94654 --- /dev/null +++ b/src/CellTracer.cs @@ -0,0 +1,57 @@ +namespace InverseOfLife; + +public class CellTracer +{ + public Dictionary<(int, int), Dictionary<(int, int), double>> Contribution { get; init; } + + public CellTracer() + { + Contribution = new(); + ToAdd = new(); + ToRemove = new(); + Parents = new(); + } + + public void AddCell((int, int) cell, IEnumerable<(int, int)> parents) + { + //double[] contribute = new double[]; + Dictionary<(int, int), double> contribute = new(); + + foreach ((int, int) parent in parents) + { + if (!Contribution.ContainsKey(parent)) + throw new Exception($"parent {parent} must exist"); + foreach ((int, int) root in Contribution[parent].Keys) + { + contribute.TryAdd(root, 0); + contribute[root] += Contribution[parent][root] / 3d; + } + } + + Contribution[cell] = contribute; + } + + public void RemoveCell((int, int) cell) + { + Contribution.Remove(cell); + } + + // + private HashSet<(int, int)> ToRemove { get; set; } + private HashSet<(int, int)> ToAdd { get; set; } + public Dictionary<(int, int), HashSet<(int, int)>> Parents { get; set; } + public void DeferredAdd((int, int) cell) => ToAdd.Add(cell); + public void DeferredRemove((int, int) cell) => ToRemove.Add(cell); + + + public void BatchProcess() + { + foreach ((int, int) cell in ToAdd) + AddCell(cell, Parents[cell]); + foreach ((int, int) cell in ToRemove) + RemoveCell(cell); + ToAdd = new(); + ToRemove = new(); + Parents = new(); + } +} \ No newline at end of file diff --git a/src/Gene.cs b/src/Gene.cs new file mode 100644 index 0000000..79986e7 --- /dev/null +++ b/src/Gene.cs @@ -0,0 +1,323 @@ +using System.Diagnostics.CodeAnalysis; + +namespace InverseOfLife; + +public class Gene +{ + + public int Length => Granularity switch + { + < 0 => -RiboseSequence.Length * Granularity, + _ => RiboseSequence.Length / Granularity + }; + public byte[] this[int idx] + { + get + { + if (Granularity > 0) + { + int byteStart = idx * Granularity; + if (byteStart + Granularity > RiboseSequence.Length) + throw new IndexOutOfRangeException("Index out of bounds for RiboseSequence."); + + return RiboseSequence.Skip(byteStart).Take(Granularity).ToArray(); + } + if (Granularity < 0) + { + int bitsPerGene = 8 / -Granularity; + int bitStart = idx * bitsPerGene; + int byteStart = bitStart / 8; + int bitOffset = bitStart % 8; + + byte[] result = new byte[(bitsPerGene + 7) / 8]; + int bitsRead = 0; + + while (bitsRead < bitsPerGene) + { + int currentByte = RiboseSequence[byteStart]; + int remainingBitsInByte = 8 - bitOffset; + int bitsToCopy = Math.Min(bitsPerGene - bitsRead, remainingBitsInByte); + + int mask = ((1 << bitsToCopy) - 1) << bitOffset; + int extractedBits = (currentByte & mask) >> bitOffset; + + result[bitsRead / 8] |= (byte)(extractedBits << (bitsRead % 8)); + + bitsRead += bitsToCopy; + bitOffset = 0; + byteStart++; + } + + return result; + } + throw new InvalidOperationException("Invalid Granularity value."); + } + set + { + if (Granularity > 0) + { + int byteStart = idx * Granularity; + if (byteStart + Granularity > RiboseSequence.Length) + throw new IndexOutOfRangeException("Index out of bounds for RiboseSequence."); + if (value.Length > Granularity) + throw new ArgumentException($"Input byte array length exceeds Granularity ({Granularity} bytes)."); + + Array.Copy(value, 0, RiboseSequence, byteStart, value.Length); + } + else if (Granularity < 0) + { + int bitsPerGene = 8 / -Granularity; + int bitStart = idx * bitsPerGene; + int byteStart = bitStart / 8; + int bitOffset = bitStart % 8; + + int bitsWritten = 0; + int byteIndex = 0; + + while (bitsWritten < bitsPerGene) + { + int remainingBitsInByte = 8 - bitOffset; + int bitsToWrite = Math.Min(bitsPerGene - bitsWritten, remainingBitsInByte); + + int mask = ((1 << bitsToWrite) - 1) << bitOffset; + int newBits = (value[byteIndex] >> (bitsWritten % 8)) & ((1 << bitsToWrite) - 1); + + RiboseSequence[byteStart] &= (byte)~mask; + RiboseSequence[byteStart] |= (byte)(newBits << bitOffset); + + bitsWritten += bitsToWrite; + bitOffset = 0; + byteStart++; + if (bitsWritten % 8 == 0) byteIndex++; + } + } + else + { + throw new InvalidOperationException("Invalid Granularity value."); + } + } + } + + [SetsRequiredMembers] + public Gene(int resolution, byte[] riboseSequence) + { + Resolution = resolution; + RiboseSequence = riboseSequence; + } + + [SetsRequiredMembers] + public Gene(int resolution, Board board) + { + Resolution = resolution; + + int subGridBits = resolution * resolution; + int subGridBytes = (subGridBits + 7) / 8; + int subGridWidth = (board.Width + resolution - 1) / resolution; + int subGridHeight = (board.Height + resolution - 1) / resolution; + + RiboseSequence = subGridBits switch + { + 1 => new byte[(board.Width * board.Height + 7) / 8], + 4 => new byte[(subGridWidth * subGridHeight + 1) / 2], + _ => new byte[subGridWidth * subGridHeight * subGridBytes] + }; + + foreach (var (x, y) in board.Lives) + { + EncodeCell(x, y, board.Width, subGridBytes); + } + } + + public Board Restore(int width, int height, bool qX = false, bool qY = false, bool useTracer = false) + { + var board = new Board(width, height, qX, qY, useTracer); + + for (int subGridIndex = 0; subGridIndex < Length; subGridIndex++) + DecodeSubGrid(subGridIndex, width, board); + + return board; + } + + private void EncodeCell(int x, int y, int width, int subGridBytes) + { + int subGridX = x / Resolution; + int subGridY = y / Resolution; + int subGridWidth = (width + Resolution - 1) / Resolution; + + if (Resolution == 1) + { + int idx = y * width + x; + int bIdx = idx / 8; + int offset = idx % 8; + RiboseSequence[bIdx] |= (byte)(1 << offset); + return; + } + + if (Resolution == 2) + { + int idx = subGridY * subGridWidth + subGridX; + int bIdx = idx / 2; + int offset = (idx % 2) * 4; + int lX = x % 2; + int lY = y % 2; + int bitIdx = lY * 2 + lX; + RiboseSequence[bIdx] |= (byte)(1 << (offset + bitIdx)); + return; + } + + + int subGridIndex = subGridY * subGridWidth + subGridX; + int subGridByteOffset = subGridIndex * subGridBytes; + + int localX = x % Resolution; + int localY = y % Resolution; + int bitIndex = localY * Resolution + localX; + + int byteIndex = subGridByteOffset + (bitIndex / 8); + int bitOffset = bitIndex % 8; + + RiboseSequence[byteIndex] |= (byte)(1 << bitOffset); + } + + private void DecodeSubGrid(int subGridIndex, int width, Board board) + { + int subGridWidth = (width + Resolution - 1) / Resolution; + int subGridX = subGridIndex % subGridWidth; + int subGridY = subGridIndex / subGridWidth; + + if (Resolution == 1) + { + int idx = subGridIndex; + int byteIndex = idx / 8; + int bitOffset = idx % 8; + + if ((RiboseSequence[byteIndex] & (1 << bitOffset)) != 0) + { + int globalX = idx % width; + int globalY = idx / width; + board.Toggle(globalX, globalY, true); + } + + return; + } + + if (Resolution == 2) + { + int byteIndex = subGridIndex / 2; + int bitOffset = (subGridIndex % 2) * 4; + + for (int bitIndex = 0; bitIndex < 4; bitIndex++) + { + if ((RiboseSequence[byteIndex] & (1 << (bitOffset + bitIndex))) != 0) + { + int localX = bitIndex % 2; + int localY = bitIndex / 2; + + int globalX = subGridX * Resolution + localX; + int globalY = subGridY * Resolution + localY; + + board.Toggle(globalX, globalY, true); + } + } + + return; + } + + int subGridBytes = (Resolution * Resolution + 7) / 8; + int subGridByteOffset = subGridIndex * subGridBytes; + + for (int bitIndex = 0; bitIndex < Resolution * Resolution; bitIndex++) + { + int byteIndex = subGridByteOffset + (bitIndex / 8); + int bitOffset = bitIndex % 8; + + if ((RiboseSequence[byteIndex] & (1 << bitOffset)) != 0) + { + int localX = bitIndex % Resolution; + int localY = bitIndex / Resolution; + + int globalX = subGridX * Resolution + localX; + int globalY = subGridY * Resolution + localY; + + board.Toggle(globalX, globalY, true); + } + } + } + + public required int Resolution { get; init; } + public byte[] RiboseSequence { get; private set; } + + public int Granularity => Resolution switch + { + 1 => -8, + 2 => -2, + _ => (Resolution * Resolution + 7) / 8 + }; + + public Gene Mutate(double[] weight, float rate = 0.1f, bool inPlace = false) + { + if (weight.Length != Length) + throw new Exception(); + double sx = weight.Sum() + 0.01; + Random random = new(); + Gene res = inPlace ? this : new Gene(Resolution, (byte[])RiboseSequence.Clone()); + for (int i = 0; i < Length; i++) + { + if (random.NextDouble() > weight[i] / sx && random.NextDouble() > weight[i] / sx) + res = res.Mutate(i, rate, true); + } + + return res; + } + + public Gene Mutate(int riboseIndex, float rate = 0.1f, bool inPlace = false) + { + + Gene res = inPlace ? this: new Gene(Resolution, (byte[])RiboseSequence.Clone()); + byte[] ribose = res[riboseIndex]; + Random random = new(); + + for (int i = 0; i < ribose.Length; i++) + { + for (int bit = 0; bit < 8; bit++) + { + if (random.NextDouble() < rate) + { + ribose[i] ^= (byte)(1 << bit); + } + } + } + + res[riboseIndex] = ribose; + return res; + } + + 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."); + if (weightA == null || weightB == null) + { + weightA = Enumerable.Repeat(1.0, a.Length).ToArray(); + weightB = Enumerable.Repeat(1.0, b.Length).ToArray(); + } + else if (weightA.Length != a.Length || weightB.Length != b.Length) + throw new ArgumentException("Weight arrays must match the number of riboses."); + + if (a.Length != b.Length) + throw new ArgumentException("Gene must have same size"); + Random random = new(); + Gene res = new Gene(a.Resolution, (byte[])a.RiboseSequence.Clone()); + for (int i = 0; i < res.Length; i++) + { + byte[] ra = a[i]; + byte[] rb = b[i]; + double ratio = weightA[i] / (weightA[i] + weightB[i] + 1E-5d); + res[i] = random.NextDouble() < ratio ? ra : rb; + } + + return res; + } + + +} \ No newline at end of file diff --git a/src/Solver.cs b/src/Solver.cs new file mode 100644 index 0000000..ee232db --- /dev/null +++ b/src/Solver.cs @@ -0,0 +1,87 @@ +namespace InverseOfLife; + +public class Solver +{ + public Solver(Board target, int steps) + { + Target = target; + Steps = steps; + } + + public Board Target { get; init; } + private int Steps { get; set; } + + private static void SortBest((Board? b, Gene? g, double s)[] best) + { + int c = best.Length-1; + while (c != 0) + { + if (best[c].s > best[c - 1].s) + (best[c], best[c - 1]) = (best[c - 1], best[c]); + else + return; + c--; + } + + } + + public (Gene, double) Solve(int withResolution=2, int maxGenerations=50, int topN=10, float mutationRate=0.1f) + { + 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]; + for (int i = 0; i < topN + 1; i++) + best[i] = (null, null, 0); + for (int gen = 0; gen < maxGenerations; gen++) + { + HashSet<(Board, Gene)> nextGeneration = new(); + foreach ((Board b, Gene g) in currentGeneration) + { + b.Evaluate(Steps); + double score = Summarizer.Score(b, Target, false); + best[topN] = (b, g, score); + SortBest(best); + } + + for (int i = 0; i < maxGenerations; i++) + { + (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); + 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)); + } + Console.WriteLine($"--------{best[0].score}"); + currentGeneration = nextGeneration.ToList(); + } + + return (best[0].gene!, best[0].score); + } + + private IEnumerable GetInitialGeneration(int amount=50, float operationRatio=0.25f) + { + int totalCells = Target.Height * Target.Width; + int activeCells = (int)Math.Ceiling(totalCells * operationRatio); + if (activeCells <= 0) + activeCells = 1; + Random random = new Random(); + + (int, int) randomCell() => (random.Next() % Target.Width, random.Next() % Target.Height); + + for (int i = 0; i < amount; i++) + { + Board b = new Board(Target.Width, Target.Height, Target.QuotientX, Target.QuotientY, true); + for (int j = 0; j < activeCells; j++) + b.Toggle(randomCell(), true); + yield return b; + } + } +} \ No newline at end of file diff --git a/src/Summarizer.cs b/src/Summarizer.cs new file mode 100644 index 0000000..822c3e8 --- /dev/null +++ b/src/Summarizer.cs @@ -0,0 +1,151 @@ +namespace InverseOfLife; + +public static class Summarizer +{ + public static Dictionary> ContributionBySubGrid(Board board, int resolution) + { + if (board.Tracer is null) + throw new Exception("tracer not enabled"); + Dictionary> res = new(); + + int mapper(int x, int y) + { + int sgw = (board.Width + resolution - 1) / resolution; + int subX = x / resolution; + int subY = y / resolution; + return subY * sgw + subX; + } + + foreach ((int cellX, int cellY) in board.Tracer.Contribution.Keys) + { + int targetSGid = mapper(cellX, cellY); + if (!res.ContainsKey(targetSGid)) + res[targetSGid] = new Dictionary(); + foreach (((int, int)sourceCell, double sourceContribution) in board.Tracer.Contribution[ + (cellX, cellY)]) + { + int sourceSGid = mapper(sourceCell.Item1, sourceCell.Item2); + if (!res[targetSGid].ContainsKey(sourceSGid)) + res[targetSGid][sourceSGid] = 0; + res[targetSGid][sourceSGid] += sourceContribution; + } + } + return res; + } + + public static double Score(Board yAct, Board yGnd, bool tpOnly = false) + { + if (yAct.Height != yGnd.Height || yAct.Width != yGnd.Width || yAct.QuotientX != yGnd.QuotientX || + yAct.QuotientY != yGnd.QuotientY) + throw new Exception(); + double tp = 0; + HashSet<(int, int)> allAlives = yAct.Lives.Union(yGnd.Lives).ToHashSet(); + double tn = yGnd.Height * yGnd.Width - allAlives.Count; + foreach ((int, int) cell in yAct.Lives) + { + if (yGnd.Lives.Contains(cell)) + tp += 1d; + } + + if (tpOnly) + { + if (yGnd.Lives.Count > 0) + return tp / yGnd.Lives.Count; + return 1d; + } + return (tp + tn) / (yGnd.Height * yGnd.Width); + } + + + public static double[] ScoreBySubGrid(Board yAct, Board yGnd, int resolution, bool tpOnly = false) + { + if (yAct.Height != yGnd.Height || yAct.Width != yGnd.Width || yAct.QuotientX != yGnd.QuotientX || + yAct.QuotientY != yGnd.QuotientY) + throw new ArgumentException("Boards must have the same dimensions and wrapping properties."); + int subGridWidth = (yAct.Width + resolution - 1) / resolution; + int subGridHeight = (yAct.Height + resolution - 1) / resolution; + double[] res = new double[subGridWidth * subGridHeight]; + + int mapper(int x, int y) + { + int subX = x / resolution; + int subY = y / resolution; + return subY * subGridWidth + subX; + } + + double[] tps = new double[subGridHeight * subGridWidth]; + HashSet<(int, int)>[] actAlive = new HashSet<(int, int)>[subGridHeight * subGridWidth]; + HashSet<(int, int)>[] gndAlive = new HashSet<(int, int)>[subGridWidth * subGridHeight]; + for (int i = 0; i < subGridHeight * subGridWidth; i++) + { + actAlive[i] = new HashSet<(int, int)>(); + gndAlive[i] = new HashSet<(int, int)>(); + } + foreach ((int x, int y) in yAct.Lives) + { + int subGridIdx = mapper(x, y); + actAlive[subGridIdx].Add((x, y)); + if (yGnd.Lives.Contains((x, y))) + tps[subGridIdx] += 1d; + } + + foreach ((int x, int y) in yGnd.Lives) + { + int subGridIdx = mapper(x, y); + gndAlive[subGridIdx].Add((x, y)); + + } + + for (int subGridY = 0; subGridY < subGridHeight; subGridY++) + { + for (int subGridX = 0; subGridX < subGridWidth; subGridX++) + { + int subGridIdx = subGridY * subGridWidth + subGridX; + + int subGridStartX = subGridX * resolution; + 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]; + if(tpOnly) + { + if (gndAlive[subGridIdx].Count > 0) + res[subGridIdx] = tp / gndAlive[subGridIdx].Count; + else + res[subGridIdx] = 1d; + } + else + res[subGridIdx] = (tp + tn) / cellsInSubGrid; + } + } + return res; + } + + public static double[] GetWeight(Board yAct, Board yGnd, int resolution, bool tpOnly=false) + { + if (yAct.Tracer is null) + throw new Exception("tracer was not enabled"); + Dictionary> contribution = ContributionBySubGrid(yAct, resolution); + double[] score = ScoreBySubGrid(yAct, yGnd, resolution, tpOnly); + int subgridWidth = (yAct.Width + resolution - 1) / resolution; + int subgridHeight = (yAct.Height + resolution - 1) / resolution; + double[] res = new double[subgridWidth * subgridHeight]; + for (int i = 0; i < res.Length; i++) + { + double weight = 0; + foreach (int targetSubGridIdx in contribution.Keys) + { + double subGridScore = score[targetSubGridIdx]; + if (!contribution[targetSubGridIdx].ContainsKey(i)) + continue; + weight += contribution[targetSubGridIdx][i] * subGridScore; + } + res[i] = weight; + } + return res; + } + +} \ No newline at end of file