Maximum lifespan on torus

For discussion of specific patterns or specific families of patterns, both newly-discovered and well-known.
User avatar
Vort
Posts: 84
Joined: May 14th, 2024, 6:35 am

Re: 249 generations, 10x10 torus

Post by Vort » June 3rd, 2024, 11:12 am

iddi01 wrote:
June 3rd, 2024, 8:27 am
Also, anyone else thinks this thread belongs better in Patterns forum?
It's fine to move it to more appropriate place.
iddi01 wrote:
June 3rd, 2024, 8:27 am
Using my methuselah searcher, i got 249 generations on 10x10 torus:
How fast your script simulate 10x10 torus soups?
Mine is getting approximately 8000 soups/sec on single core of i5-4690.
After ~1.5 million soups lifespan of 490 was achieved, after 4.4 million, lifespan of 491:

Code: Select all

x = 10, y = 10, rule = B3/S23:T10,10
7bo$2o4b2obo$o$2o3bo$bo$b3obo2bo$bobobo$4b2o3bo$2obobo$bo4bo2bo!
Code:

Code: Select all

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace TorusLife
{
    class Pattern : IEquatable<Pattern>
    {
        public const int GridWidth = 10;
        public const int GridHeight = 10;
        int[,] cells;

        public Pattern()
        {
            cells = new int[GridWidth, GridHeight];
        }

        public Pattern(Pattern source)
        {
            cells = new int[GridWidth, GridHeight];
            for (int y = 0; y < GridHeight; y++)
                for (int x = 0; x < GridWidth; x++)
                    cells[x, y] = source.cells[x, y];
        }

        public Pattern Generate()
        {
            var result = new Pattern();
            for (int y = 0; y < GridHeight; y++)
            {
                int ym1 = y - 1;
                int yp1 = y + 1;
                if (ym1 < 0)
                    ym1 = GridHeight - 1;
                else if (ym1 >= GridHeight)
                    ym1 = 0;
                if (yp1 < 0)
                    yp1 = GridHeight - 1;
                else if (yp1 >= GridHeight)
                    yp1 = 0;
                for (int x = 0; x < GridWidth; x++)
                {
                    int xm1 = x - 1;
                    int xp1 = x + 1;
                    if (xm1 < 0)
                        xm1 = GridWidth - 1;
                    else if (xm1 >= GridWidth)
                        xm1 = 0;
                    if (xp1 < 0)
                        xp1 = GridWidth - 1;
                    else if (xp1 >= GridWidth)
                        xp1 = 0;
                    int neighbours =
                        cells[xm1, ym1] +
                        cells[x, ym1] +
                        cells[xp1, ym1] +
                        cells[xm1, y] +
                        cells[xp1, y] +
                        cells[xm1, yp1] +
                        cells[x, yp1] +
                        cells[xp1, yp1];

                    if (cells[x, y] == 1)
                        result.cells[x, y] = (neighbours == 2 || neighbours == 3) ? 1 : 0;
                    else
                        result.cells[x, y] = neighbours == 3 ? 1 : 0;
                }
            }
            return result;
        }

        public void Randomize(Random rnd)
        {
            for (int y = 0; y < GridHeight; y++)
                for (int x = 0; x < GridWidth; x++)
                    cells[x, y] = rnd.Next(100) < 35 ? 1 : 0;
        }

        public void Measure(out int lifespan, out int period)
        {
            Pattern pattern = this;
            var patterns = new Dictionary<Pattern, int>();
            patterns.Add(this, 0);

            for (int i = 0; ; i++)
            {
                pattern = pattern.Generate();
                int patternIndex;
                if (patterns.TryGetValue(pattern, out patternIndex))
                {
                    period = patterns.Count - patternIndex;
                    lifespan = i - period + 1;
                    return;
                }
                patterns.Add(pattern, i + 1);
            }
        }

        public void WriteRLE(string fileName, string comment = null)
        {
            var sb = new StringBuilder();
            if (comment != null)
                sb.AppendLine($"#C {comment}");
            sb.AppendLine($"x = {GridWidth}, y = {GridHeight}, rule = B3/S23:T{GridWidth},{GridHeight}");
            for (int y = 0; y < GridHeight; y++)
            {
                for (int x = 0; x < GridWidth; x++)
                    sb.Append(cells[x, y] == 1 ? 'o' : 'b');
                sb.AppendLine(y == GridHeight - 1 ? "!" : "$");
            }
            File.WriteAllText(fileName, sb.ToString());
        }

        public void ReadRLE(string[] lines)
        {
            int x = 0;
            int y = 0;

            string scount = "";

            foreach (var line in lines)
            {
                if (line.StartsWith("#"))
                    continue;
                if (line.StartsWith("x"))
                {
                    if (line.Split(':')[1] != $"T{GridWidth},{GridHeight}")
                        throw new Exception();
                    continue;
                }

                for (int i = 0; i < line.Length; i++)
                {
                    char c = line[i];
                    if (c >= '0' && c <= '9')
                    {
                        scount += c;
                    }
                    else
                    {
                        if (c == '$')
                        {
                            x = 0;
                            int count = 1;
                            if (scount != "")
                                count = int.Parse(scount);
                            y += count;
                            scount = "";
                        }
                        else if (c == '!')
                            break;
                        else if (c == 'o' || c == 'b')
                        {
                            int count = 1;
                            if (scount != "")
                                count = int.Parse(scount);
                            for (int k = 0; k < count; k++)
                            {
                                cells[x, y] = c == 'o' ? 1 : 0;
                                x++;
                                WrapCoordinates(ref x, ref y);
                            }
                            scount = "";
                        }
                    }
                }
            }
        }

        public void ReadRLE(string fileName)
        {
            ReadRLE(File.ReadAllLines(fileName));
        }

        public override int GetHashCode()
        {
            int hash = 0;
            int shift = 0;
            for (int y = 0; y < GridHeight; y++)
                for (int x = 0; x < GridWidth; x++)
                {
                    hash = hash ^ cells[x, y] << shift;
                    shift = (shift + 1) & 31;
                }
            return hash;
        }

        public bool Equals(Pattern other)
        {
            for (int y = 0; y < GridHeight; y++)
                for (int x = 0; x < GridWidth; x++)
                    if (cells[x, y] != other.cells[x, y])
                        return false;
            return true;
        }

        void WrapCoordinates(ref int x, ref int y)
        {
            if (x < 0)
                x += GridWidth;
            else if (x >= GridWidth)
                x -= GridWidth;
            if (y < 0)
                y += GridHeight;
            else if (y >= GridHeight)
                y -= GridWidth;
        }
    }

    class Program
    {
        Program()
        {
            Random rnd = new Random();

            int period;
            int lifespan;
            int totalMax = 0;
            int countMax = 0;
            int bestLifespan = 0;
            int bestLifespanLocal = 0;
            for (int i = 0; ; i++)
            {
                Pattern pattern = new Pattern();
                pattern.Randomize(rnd);
                pattern.Measure(out lifespan, out period);
                if (i % 100000 == 0 && i != 0)
                {
                    totalMax += bestLifespanLocal;
                    countMax++;
                    int avgMax = totalMax / countMax;
                    Console.WriteLine($"Iteration: {i,6}, lifespan: {bestLifespan,4} | {bestLifespanLocal,4} | {avgMax,4}");
                    bestLifespanLocal = 0;
                }
                if (lifespan > bestLifespan)
                {
                    bestLifespan = lifespan;
                    pattern.WriteRLE(
                        $"{Pattern.GridWidth}x{Pattern.GridHeight}_{lifespan}.rle",
                        $"lifespan: {lifespan}, period: {period}");
                }
                if (lifespan > bestLifespanLocal)
                    bestLifespanLocal = lifespan;
            }
        }

        static void Main(string[] args)
        {
            new Program();
        }
    }
}

User avatar
Vort
Posts: 84
Joined: May 14th, 2024, 6:35 am

Re: Maximum lifespan on torus

Post by Vort » June 3rd, 2024, 11:31 am

vilc wrote:
May 26th, 2024, 4:02 pm
And finally, statistics over 100,000,000 random soups on 6x6, 7x7 and 8x8 tori :
Your statistics show oscillations at highest lifespans.
I wonder if this is how various "inventions" manifest themselves.
(soup is able to jump lifespan gap if it "invents" something)

iddi01
Posts: 187
Joined: January 24th, 2024, 5:14 am
Location: B3-n/S1e2-a3-e4e

Re: 249 generations, 10x10 torus

Post by iddi01 » June 3rd, 2024, 11:33 am

Vort wrote:
June 3rd, 2024, 11:12 am
How fast your script simulate 10x10 torus soups?
Mine is getting approximately 8000 soups/sec on single core of i5-4690.
After ~1.5 million soups lifespan of 490 was achieved, after 4.4 million, lifespan of 491:
Unfortunately only 86 soups/second, nearly inactive compared to yours. (i use i3, probably still only around 250/sec on your computer)

That's an excellent demonstration of just how slow Python is compared to other programming languages, but it's the only one i'm reasonably good with.
Wiki: User:iddi01

I'm making a poll. please contribute.

First gun i constructed:

Code: Select all

x = 69, y = 69, rule = B3-n/S1e2-a3-e4e
2$32b3o$32bobo$32bobo$32b3o27$63b4o$b4o58bo2bo$bo2bo23bo4b2o28b4o$b4o
21bobo$28bo21$35bo$34b3o6$33b3o$33bobo$33bobo$33b3o!

User avatar
Vort
Posts: 84
Joined: May 14th, 2024, 6:35 am

Re: 249 generations, 10x10 torus

Post by Vort » June 3rd, 2024, 11:39 am

iddi01 wrote:
June 3rd, 2024, 11:33 am
That's an excellent demonstration of just how slow Python is compared to other programming languages...
Also large amount of abstraction layers may cause a slowdown.

I was worried that C# is not the best choice, C++ code will be probably 5-10 times faster.
However, getting higher and higher lifespans requires exponentially more resources, so I decided to stay with C# for now.

User avatar
confocaloid
Posts: 3524
Joined: February 8th, 2022, 3:15 pm

Re: Maximum lifespan on torus

Post by confocaloid » June 11th, 2024, 11:32 am

Related forum thread: viewtopic.php?f=7&t=3383 "Small Tori in B3/S23"
Vort wrote:
June 3rd, 2024, 11:12 am
iddi01 wrote:
June 3rd, 2024, 8:27 am
Also, anyone else thinks this thread belongs better in Patterns forum?
It's fine to move it to more appropriate place.
[...]
I think this discussion fits equally in either of two subforums. Note that the other thread is also in "General discussion".

Patterns and reactions on tori (and/or other universes with topology other than the infinite square tiling on the Euclidean plane) feel like an interesting topic for general discussion. (While the "Patterns" subforum is almost completely focused on the usual topology, without any wraparound or boundaries.)

User avatar
Vort
Posts: 84
Joined: May 14th, 2024, 6:35 am

Re: Maximum lifespan on torus

Post by Vort » June 14th, 2024, 11:06 am

vilc wrote:
May 20th, 2024, 4:19 pm
Engineered methuselah in a 40x40 torus with lifespan 10242 (using a p1344 gun). There is a lot of space that the glider stream cannot reach, significant improvements might come from a gun with a better shape.
I found that small modification allowed similar gun, p8256, to fit into 40x40 torus as well.
Combined with junk generation, it made possible to get lifespan of 47325 ticks:

Code: Select all

x = 40, y = 40, rule = B3/S23:T40,40
17b2o8b2o3b2o$17b2o6b2o5b2o$26bo$20b2o4bobo$20bobo4b2o$o21bo16bo$o14bo
6b3o14bo$14bobo3b2o3bo7b2o$6bo8b2o2bo2b4o6b2obo$5bobo11bobo10bo2bo$5bo
bo10b2ob2ob2o5b6o$o3b2ob2o9bo6bo2b4obob5o$o2bo6bo9b2ob2o3b6obob3o$3b2o
b2ob2o10bobo8b6o$7bobo6bo4bobo9bo2bo$3b4o2bo5bobo4bo10bob2o$3bo3b2o6b
2o17b2o$4b3o21b2o$6bo11bo9b2o$2o4bobo8bobo$obo4b2o9bo$2bo$2b2o31b2o$
11b2o22b2o$2b2o7b2o$3bo$bo$b2o30bo$3bo28bobo$b3o29bo$o16b2o17bo$2o15b
2o16bobo$36bo$28b2o$29bo4bo$8b2o9bo6b3o4bobo$7bobo9bo6bo7b2o$4bo4bo9bo
7b2o$4b2o22bo$27bo!
Updated code of junk generator:

Code: Select all

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace TorusLife
{
    class Pattern : IEquatable<Pattern>
    {
        public const int GridWidth = 40;
        public const int GridHeight = 40;
        int[,] cells;

        public Pattern()
        {
            cells = new int[GridWidth, GridHeight];
        }

        public Pattern(Pattern source)
        {
            cells = new int[GridWidth, GridHeight];
            for (int y = 0; y < GridHeight; y++)
                for (int x = 0; x < GridWidth; x++)
                    cells[x, y] = source.cells[x, y];
        }

        public Pattern Generate()
        {
            var result = new Pattern();
            for (int y = 0; y < GridHeight; y++)
            {
                int ym1 = y - 1;
                int yp1 = y + 1;
                if (ym1 < 0)
                    ym1 = GridHeight - 1;
                else if (ym1 >= GridHeight)
                    ym1 = 0;
                if (yp1 < 0)
                    yp1 = GridHeight - 1;
                else if (yp1 >= GridHeight)
                    yp1 = 0;
                for (int x = 0; x < GridWidth; x++)
                {
                    int xm1 = x - 1;
                    int xp1 = x + 1;
                    if (xm1 < 0)
                        xm1 = GridWidth - 1;
                    else if (xm1 >= GridWidth)
                        xm1 = 0;
                    if (xp1 < 0)
                        xp1 = GridWidth - 1;
                    else if (xp1 >= GridWidth)
                        xp1 = 0;
                    int neighbours =
                        cells[xm1, ym1] +
                        cells[x, ym1] +
                        cells[xp1, ym1] +
                        cells[xm1, y] +
                        cells[xp1, y] +
                        cells[xm1, yp1] +
                        cells[x, yp1] +
                        cells[xp1, yp1];

                    if (cells[x, y] == 1)
                        result.cells[x, y] = (neighbours == 2 || neighbours == 3) ? 1 : 0;
                    else
                        result.cells[x, y] = neighbours == 3 ? 1 : 0;
                }
            }
            return result;
        }

        public void PlaceJunk(Random rnd, Pattern mask)
        {
            int[][,] stamps = new int[][,]
            {
                new int[7, 7] {
                    { 0, 0, 0, 0, 0, 0, 0 },
                    { 0, 0, 0, 0, 0, 0, 0 },
                    { 0, 0, 0, 1, 0, 0, 0 },
                    { 0, 0, 1, 0, 1, 0, 0 },
                    { 0, 0, 0, 1, 0, 0, 0 },
                    { 0, 0, 0, 0, 0, 0, 0 },
                    { 0, 0, 0, 0, 0, 0, 0 },
                },
                new int[7, 7] {
                    { 0, 0, 0, 0, 0, 0, 0 },
                    { 0, 0, 0, 0, 0, 0, 0 },
                    { 0, 0, 0, 1, 0, 0, 0 },
                    { 0, 0, 1, 0, 1, 0, 0 },
                    { 0, 0, 0, 1, 1, 0, 0 },
                    { 0, 0, 0, 0, 0, 0, 0 },
                    { 0, 0, 0, 0, 0, 0, 0 },
                },
                new int[7, 7] {
                    { 0, 0, 0, 0, 0, 0, 0 },
                    { 0, 0, 0, 0, 0, 0, 0 },
                    { 0, 0, 0, 1, 1, 0, 0 },
                    { 0, 0, 1, 0, 1, 0, 0 },
                    { 0, 0, 0, 1, 0, 0, 0 },
                    { 0, 0, 0, 0, 0, 0, 0 },
                    { 0, 0, 0, 0, 0, 0, 0 },
                },
                new int[7, 7] {
                    { 0, 0, 0, 0, 0, 0, 0 },
                    { 0, 0, 0, 0, 0, 0, 0 },
                    { 0, 0, 1, 1, 0, 0, 0 },
                    { 0, 0, 1, 0, 1, 0, 0 },
                    { 0, 0, 0, 1, 0, 0, 0 },
                    { 0, 0, 0, 0, 0, 0, 0 },
                    { 0, 0, 0, 0, 0, 0, 0 },
                },
                new int[7, 7] {
                    { 0, 0, 0, 0, 0, 0, 0 },
                    { 0, 0, 0, 0, 0, 0, 0 },
                    { 0, 0, 0, 1, 0, 0, 0 },
                    { 0, 0, 1, 0, 1, 0, 0 },
                    { 0, 0, 1, 1, 0, 0, 0 },
                    { 0, 0, 0, 0, 0, 0, 0 },
                    { 0, 0, 0, 0, 0, 0, 0 },
                }
            };

            int[][,] stampMasks = new int[][,]
            {
                new int[7, 7] {
                    { 0, 0, 1, 1, 1, 0, 0 },
                    { 0, 1, 1, 1, 1, 1, 0 },
                    { 1, 1, 1, 1, 1, 1, 1 },
                    { 1, 1, 1, 1, 1, 1, 1 },
                    { 1, 1, 1, 1, 1, 1, 1 },
                    { 0, 1, 1, 1, 1, 1, 0 },
                    { 0, 0, 1, 1, 1, 0, 0 },
                },
                new int[7, 7] {
                    { 0, 0, 1, 1, 1, 0, 0 },
                    { 0, 1, 1, 1, 1, 1, 0 },
                    { 1, 1, 1, 1, 1, 1, 1 },
                    { 1, 1, 1, 1, 1, 1, 1 },
                    { 1, 1, 1, 1, 1, 1, 1 },
                    { 0, 1, 1, 1, 1, 1, 1 },
                    { 0, 0, 1, 1, 1, 1, 0 },
                },
                new int[7, 7] {
                    { 0, 0, 1, 1, 1, 1, 0 },
                    { 0, 1, 1, 1, 1, 1, 1 },
                    { 1, 1, 1, 1, 1, 1, 1 },
                    { 1, 1, 1, 1, 1, 1, 1 },
                    { 1, 1, 1, 1, 1, 1, 1 },
                    { 0, 1, 1, 1, 1, 1, 0 },
                    { 0, 0, 1, 1, 1, 0, 0 },
                },
                new int[7, 7] {
                    { 0, 1, 1, 1, 1, 0, 0 },
                    { 1, 1, 1, 1, 1, 1, 0 },
                    { 1, 1, 1, 1, 1, 1, 1 },
                    { 1, 1, 1, 1, 1, 1, 1 },
                    { 1, 1, 1, 1, 1, 1, 1 },
                    { 0, 1, 1, 1, 1, 1, 0 },
                    { 0, 0, 1, 1, 1, 0, 0 },
                },
                new int[7, 7] {
                    { 0, 0, 1, 1, 1, 0, 0 },
                    { 0, 1, 1, 1, 1, 1, 0 },
                    { 1, 1, 1, 1, 1, 1, 1 },
                    { 1, 1, 1, 1, 1, 1, 1 },
                    { 1, 1, 1, 1, 1, 1, 1 },
                    { 1, 1, 1, 1, 1, 1, 0 },
                    { 0, 1, 1, 1, 1, 0, 0 },
                }
            };

            for (int i = 0; i < GridWidth * GridHeight / 8; i++)
            {
                int stampIndex = rnd.Next(stamps.Length);
                int[,] stamp = stamps[stampIndex];
                int[,] stampMask = stampMasks[stampIndex];
                int stampWidth = stamp.GetLength(0);
                int stampHeight = stamp.GetLength(1);
                int offsetX = rnd.Next(GridWidth);
                int offsetY = rnd.Next(GridHeight);
                bool collided = false;
                for (int y = 0; y < stampHeight; y++)
                    for (int x = 0; x < stampWidth; x++)
                    {
                        int sX = x + offsetX;
                        int sY = y + offsetY;
                        WrapCoordinates(ref sX, ref sY);

                        if (stampMask[x, y] == 1 &&
                            (mask.cells[sX, sY] == 1 ||
                            cells[sX, sY] == 1))
                        {
                            collided = true;
                            break;
                        }
                        if (collided)
                            break;
                    }
                if (!collided)
                {
                    for (int y = 0; y < stampHeight; y++)
                        for (int x = 0; x < stampWidth; x++)
                        {
                            int sX = x + offsetX;
                            int sY = y + offsetY;
                            WrapCoordinates(ref sX, ref sY);
                            cells[sX, sY] |= stamp[x, y];
                        }
                }
            }
        }

        public void Measure(out int lifespan, out int period)
        {
            Pattern pattern = this;
            var patterns = new Dictionary<Pattern, int>();
            patterns.Add(this, 0);

            for (int i = 0; ; i++)
            {
                pattern = pattern.Generate();
                int patternIndex;
                if (patterns.TryGetValue(pattern, out patternIndex))
                {
                    period = patterns.Count - patternIndex;
                    lifespan = i - period + 1;
                    return;
                }
                patterns.Add(pattern, i + 1);
            }
        }

        public Pattern GetMask(int ticks)
        {
            Pattern mask = new Pattern();
            Pattern current = this;
            for (int i = 0; i < ticks; i++)
            {
                for (int y = 0; y < GridHeight; y++)
                    for (int x = 0; x < GridWidth; x++)
                        mask.cells[x, y] |= current.cells[x, y];
                current = current.Generate();
            }
            return mask;
        }

        public void Merge(Pattern pattern)
        {
            for (int y = 0; y < GridHeight; y++)
                for (int x = 0; x < GridWidth; x++)
                    cells[x, y] |= pattern.cells[x, y];
        }

        public void WriteRLE(string fileName, string comment = null)
        {
            var sb = new StringBuilder();
            if (comment != null)
                sb.AppendLine($"#C {comment}");
            sb.AppendLine($"x = {GridWidth}, y = {GridHeight}, rule = B3/S23:T{GridWidth},{GridHeight}");
            for (int y = 0; y < GridHeight; y++)
            {
                for (int x = 0; x < GridWidth; x++)
                    sb.Append(cells[x, y] == 1 ? 'o' : 'b');
                sb.AppendLine(y == GridHeight - 1 ? "!" : "$");
            }
            File.WriteAllText(fileName, sb.ToString());
        }

        public void ReadRLE(string[] lines)
        {
            int x = 0;
            int y = 0;

            string scount = "";

            foreach (var line in lines)
            {
                if (line.StartsWith("#"))
                    continue;
                if (line.StartsWith("x"))
                {
                    if (line.Split(':')[1] != $"T{GridWidth},{GridHeight}")
                        throw new Exception();
                    continue;
                }

                for (int i = 0; i < line.Length; i++)
                {
                    char c = line[i];
                    if (c >= '0' && c <= '9')
                    {
                        scount += c;
                    }
                    else
                    {
                        if (c == '$')
                        {
                            x = 0;
                            int count = 1;
                            if (scount != "")
                                count = int.Parse(scount);
                            y += count;
                            scount = "";
                        }
                        else if (c == '!')
                            break;
                        else if (c == 'o' || c == 'b')
                        {
                            int count = 1;
                            if (scount != "")
                                count = int.Parse(scount);
                            for (int k = 0; k < count; k++)
                            {
                                cells[x, y] = c == 'o' ? 1 : 0;
                                x++;
                                WrapCoordinates(ref x, ref y);
                            }
                            scount = "";
                        }
                    }
                }
            }
        }

        public void ReadRLE(string fileName)
        {
            ReadRLE(File.ReadAllLines(fileName));
        }

        public override int GetHashCode()
        {
            int hash = 0;
            int shift = 0;
            for (int y = 0; y < GridHeight; y++)
                for (int x = 0; x < GridWidth; x++)
                {
                    hash = hash ^ cells[x, y] << shift;
                    shift = (shift + 1) & 31;
                }
            return hash;
        }

        public bool Equals(Pattern other)
        {
            for (int y = 0; y < GridHeight; y++)
                for (int x = 0; x < GridWidth; x++)
                    if (cells[x, y] != other.cells[x, y])
                        return false;
            return true;
        }

        void WrapCoordinates(ref int x, ref int y)
        {
            if (x < 0)
                x += GridWidth;
            else if (x >= GridWidth)
                x -= GridWidth;
            if (y < 0)
                y += GridHeight;
            else if (y >= GridHeight)
                y -= GridWidth;
        }
    }

    class Program
    {
        Program()
        {
            Pattern source = new Pattern();
            source.ReadRLE(new string[] {
                "x = 40, y = 40, rule = B3/S23:T40,40",
                "17b2o8b2o3b2o$17b2o6b2o5b2o$26bo$20b2o4bobo$20bobo4b2o$o21bo16bo$o21b",
                "3o14bo$20b2o3bo7b2o$6bo12bo2b4o6b2obo$5bobo11bobo10bo2bo$5bobo10b2ob2o",
                "b2o5b6o$o3b2ob2o9bo6bo2b4obob5o$o2bo6bo9b2ob2o3b6obob3o$3b2ob2ob2o10bo",
                "bo8b6o$7bobo11bobo9bo2bo$3b4o2bo12bo10bob2o$3bo3b2o25b2o$4b3o21b2o$6bo",
                "21b2o$2o4bobo$obo4b2o$2bo$2b2o31b2o$11b2o22b2o$2b2o7b2o$3bo$bo$b2o30bo",
                "$3bo28bobo$b3o29bo$o16b2o17bo$2o15b2o16bobo$36bo$28b2o$29bo$8b2o9bo6b",
                "3o$7bobo9bo6bo$4bo4bo9bo7b2o$4b2o22bo$27bo!"
            });
            int unstableTicks = 41340;
            Pattern mask = source.GetMask(unstableTicks);
            Pattern unstableSource = new Pattern(source);
            for (int i = 0; i < unstableTicks; i++)
                unstableSource = unstableSource.Generate();

            int period;
            int lifespan;
            int totalMax = 0;
            int countMax = 0;
            int bestLifespan = 0;
            int bestLifespanLocal = 0;

            Random rnd = new Random();
            for (int i = 0; ; i++)
            {
                Pattern junk = new Pattern();
                junk.PlaceJunk(rnd, mask);
                Pattern pattern = new Pattern(unstableSource);
                pattern.Merge(junk);
                pattern.Measure(out lifespan, out period);
                lifespan += unstableTicks;

                if (i % 50 == 0 && i != 0)
                {
                    totalMax += bestLifespanLocal;
                    countMax++;
                    int avgMax = totalMax / countMax;
                    Console.WriteLine($"Iteration: {i,6}, lifespan: {bestLifespan,4} | {bestLifespanLocal,4} | {avgMax,4}");
                    bestLifespanLocal = 0;
                }
                if (lifespan > bestLifespan)
                {
                    bestLifespan = lifespan;
                    pattern = new Pattern(source);
                    pattern.Merge(junk);
                    pattern.WriteRLE(
                        $"{Pattern.GridWidth}x{Pattern.GridHeight}_{lifespan}.rle",
                        $"lifespan: {lifespan}, period: {period}");
                }
                if (lifespan > bestLifespanLocal)
                    bestLifespanLocal = lifespan;
            }
        }

        static void Main(string[] args)
        {
            new Program();
        }
    }
}

vilc
Posts: 68
Joined: March 20th, 2024, 4:36 pm

Re: Maximum lifespan on torus

Post by vilc » June 14th, 2024, 6:02 pm

Vort wrote:
June 14th, 2024, 11:06 am
I found that small modification allowed similar gun, p8256, to fit into 40x40 torus as well.
Combined with junk generation, it made possible to get lifespan of 47325 ticks:

Code: Select all

x = 40, y = 40, rule = B3/S23:T40,40
17b2o8b2o3b2o$17b2o6b2o5b2o$26bo$20b2o4bobo$20bobo4b2o$o21bo16bo$o14bo
6b3o14bo$14bobo3b2o3bo7b2o$6bo8b2o2bo2b4o6b2obo$5bobo11bobo10bo2bo$5bo
bo10b2ob2ob2o5b6o$o3b2ob2o9bo6bo2b4obob5o$o2bo6bo9b2ob2o3b6obob3o$3b2o
b2ob2o10bobo8b6o$7bobo6bo4bobo9bo2bo$3b4o2bo5bobo4bo10bob2o$3bo3b2o6b
2o17b2o$4b3o21b2o$6bo11bo9b2o$2o4bobo8bobo$obo4b2o9bo$2bo$2b2o31b2o$
11b2o22b2o$2b2o7b2o$3bo$bo$b2o30bo$3bo28bobo$b3o29bo$o16b2o17bo$2o15b
2o16bobo$36bo$28b2o$29bo4bo$8b2o9bo6b3o4bobo$7bobo9bo6bo7b2o$4bo4bo9bo
7b2o$4b2o22bo$27bo!
It can be improved to 60728 ticks by adding one more glider-absorbing still-life and welding a failed eater 5. The catalysis may allow for one more absorbed glider, but I couldn't find a way to do it.
I also shifted your pattern so that the empty space for glider absorption is in the middle and annotated it with LifeSuper.

Code: Select all

x = 38, y = 40, rule = LifeSuper:T40,40
11.2A14.3A$11.2A16.A$23.2A4.A.A4.2pA$23.A.A4.2A4.2pA$25.A$18.2A5.2A$
18.2A14.2K$25.2K7.2K$26.K$16.Q7.K$15.Q.Q6.2K$16.Q9.K$19.Q4.3K$2K16.Q.
Q2.Q$2K17.Q2.Q.Q$15.2Q6.Q$11.2K3.Q$12.K3.Q.Q$2.K6.3K5.2Q2.2Q8.2K$2.K
6.K11.Q8.K.K$2.K7.2K10.Q4.K4.K$11.K9.2Q4.2K$10.K$2K8.2K3.2A$2K6.2A5.
2A$9.A$3.2A4.A.A$3.A.A4.2A$5.A16.2A$5.3A14.2A$3.2A3.A7.2A$2.A2.4A6.2A
.A10.A$2.A.A10.A2.A9.A.A$.2A.2A.2A5.6A8.A.A$.A6.A2.4A.A.6A3.2A.2A$3.
2A.2A3.6A.A.4A2.A6.A$4.A.A8.6A5.2A.2A.2A$4.A.A9.A2.A10.A.A$5.A10.A.2A
6.4A2.A$17.2A7.A3.2A!

Turquoise is the rephased 51P384, green is the p43 dot sparker, orange is the glider absorption mechanism and red-magenta is the junk (a single block here, unoptimized).

User avatar
Vort
Posts: 84
Joined: May 14th, 2024, 6:35 am

Re: Maximum lifespan on torus

Post by Vort » June 15th, 2024, 5:54 am

Vort wrote:
May 26th, 2024, 11:56 am
It was interesting for me to see how function of lifespan depending on world size looks like.
Here is how it looks like with data available so far for 1x1 .. 80x80 square tori.
torus_lifespan_v3.png
torus_lifespan_v3.png (5.49 KiB) Viewed 2150 times
Green dots are results from exhaustive (full) searches.
Gray dots are from random soups.
Blue dots are from engineered patterns.

Code: Select all

df={{1,1},{2,1},{3,2},{4,9},{5,51},{6,90}};(*full enumeration*)
dr={{7,216},{8,327},{10,491},{15,1130},{20,2292},{30,4335},{40,7938},{60,7215},{80,10478}};(*random*)
de={{40,60728},{80,19237820}};(*engineered*)
ListLogPlot[{df,dr,de},ImageSize->{640,Automatic},AspectRatio->9/16,PlotRange->Full,AxesLabel->{"size","lifespan"},PlotStyle->{RGBColor[13/255,217/255,13/255],RGBColor[164/255,153/255,134/255],RGBColor[27/255,157/255,254/255]}]

User avatar
Vort
Posts: 84
Joined: May 14th, 2024, 6:35 am

Re: Maximum lifespan on torus

Post by Vort » June 15th, 2024, 10:06 am

I think it worth to explore intersection between engineered (blue) and random (gray) line:
torus_lifespan_v9.png
torus_lifespan_v9.png (5.94 KiB) Viewed 1956 times
Looks like lifespans of patterns made with different methods are close to each other with 30x30 torus.
Here is my attempt at creating engineered 30x30 pattern, most likely not optimal (2720):

Code: Select all

x = 30, y = 30, rule = LifeSuper:T30,30
$A18.2A8.A$19.2A2$16.2A5.2A$16.2A5.2A7$10.Q5.Q$9.Q.Q3.Q.Q$10.Q5.Q$13.
Q5.Q$A11.Q.Q3.Q.Q2.Q3.A.A$A12.Q5.Q2.Q.Q2.2A$23.Q$18.Q$17.Q.Q$18.Q10.A
$.A26.A$2.A19.2A3.A$2.A19.2A4.2A$3A23.2A!
Random soup result (4335):

Code: Select all

x = 30, y = 30, rule = B3/S23:T30,30
bo2bobobo5b2o8bo3bo$bo11b3obo2bo2bo$2bo3bo2bobob2ob3o7bob2o$bo4b2o2bo
2bob2o5bo5bo$7bobobo3b2o2bo5bobobo$o2b2o2bobob2o2b2o4bo2bob2obo$bob2ob
2o2bo10b4ob4o$o4bo3bo2bobo2b2o4b2ob3o$b4o2b3o6bo2b3o5bobo$o2bo13bob2ob
obobo$bo2bo5bob2o2b3obo5b4o$b5obob4o6b2o2bobobo$obo2bob2ob5o4b5o3bo$2b
2o3bo2b2o2b2o8bob4o$3bo2b3ob2o2b2o2bobob2o2bobo$2bo7bob3o4bo2bob3o2bo$
o3bob6o5bo8b2o$bob2o3bobo4b2obo5bo2bobo$4b2ob2o3bo2bobobo2b2o4bo$3b2ob
obo2bobobo2b3obobo4bo$o6bobo2b2o4bobo3b2obobo$2bo3b2obo5bo4b6o3bo$obo
4bo5b2o8b2o2bo$bo3bob2o2bo4b3o10bo$5b3o4bo3bo3bob2o3b3o$3bobo4bo2bobo
2b4o3bobobo$b3obob2o6b2obobob2o2bobo$4b2o6bo3bobobobob2ob3o$o6b3o2b3ob
2ob3o2b2o3bo$o9b2ob2obobo4bo2b2o!
Next I tried to improve engineered result with random junk (5598):

Code: Select all

x = 30, y = 30, rule = LifeSuper:T30,30
$A18.2A8.A$19.2A2$16.2A5.2A$16.2A5.2A4$3.2pA22.pA$4.pA17.2pA2.pA.pA$
2.pA19.2pA3.pA$2.2pA6.Q5.Q$9.Q.Q3.Q.Q$10.Q5.Q$13.Q5.Q$A11.Q.Q3.Q.Q2.Q
3.A.A$A12.Q5.Q2.Q.Q2.2A$23.Q$18.Q$17.Q.Q$18.Q10.A$.A26.A$2.A19.2A3.A$
2.A19.2A4.2A$3A23.2A!
For patterns, which do not fall into engineered or random category, I added one more category, "other", with purple dots.
Not much difference there so far, however.

One more pattern is 25x25 engineered (344), to see blue line better:

Code: Select all

x = 25, y = 25, rule = LifeSuper:T25,25
4.5A6.A$5.3A9.A$6.A4.2A3.2A$11.A.A$13.A$13.2A2$.3A$3A$16.Q$11.Q3.Q.Q$
10.Q.Q3.Q$11.Q3$.A8.2Q$.3A6.2Q5.2A$4.A9.2A2.A$3.2A9.2A.A$17.2A$13.3A.
A$3.A3.2A4.3A.A$2.A3.2A4.A3.2A.A$2.A10.2A3.2A$3.A11.2A!

Code: Select all

df={{1,1},{2,1},{3,2},{4,9},{5,51},{6,90}};(*full enumeration*)
dr={{7,216},{8,327},{10,491},{15,1130},{20,2292},{25,2916},{30,4335},{35,5073},{40,7938},{50,6618},{60,7711},{80,10478}};(*random*)
de={{25,520},{30,2840},{40,60728},{80,171836799}};(*engineered*)
do={{25,3264},{30,6543}};(*other*)
ListLogPlot[{df,do,dr,de},ImageSize->{640,Automatic},AspectRatio->9/16,PlotRange->Full,AxesLabel->{"size","lifespan"},PlotStyle->{RGBColor[13/255,217/255,13/255],RGBColor[160/255,86/255,252/255],RGBColor[164/255,153/255,134/255],RGBColor[27/255,157/255,254/255]}]
upd. Changed 30x30 patterns with better versions: 2480 -> 2720, 4646 -> 5598.
upd2. Changed dots for 30x30 patterns: 2720 -> 2840, 5598 -> 6543.
upd3. Changed dot for 80x80 pattern: 19237820 -> 171836799.
upd4. For 25x25 world, 1 pattern changed: 344 -> 440 and 1 pattern added: 3264.
upd5. Changed dots for engineered 25x25: 440 -> 520, random 35x35: 4433 -> 5073, 50x50: 6154 -> 6618, 60x60: 7215 -> 7711.
Last edited by Vort on June 18th, 2024, 9:38 am, edited 6 times in total.

User avatar
tommyaweosme
Posts: 1096
Joined: January 15th, 2024, 9:37 am
Location: platformer mode
Contact:

Re: Maximum lifespan on torus

Post by tommyaweosme » June 15th, 2024, 10:55 am

engineered patterns will eventually come out of randomness
i can solve a rubix cube in 3 minutes or less with my new speedcube

vilc
Posts: 68
Joined: March 20th, 2024, 4:36 pm

Re: Maximum lifespan on torus

Post by vilc » June 15th, 2024, 5:33 pm

Vort wrote:
June 15th, 2024, 10:06 am
Here is my attempt at creating engineered 30x30 pattern, most likely not optimal (2720):

Code: Select all

x = 30, y = 30, rule = LifeSuper:T30,30
$A18.2A8.A$19.2A2$16.2A5.2A$16.2A5.2A7$10.Q5.Q$9.Q.Q3.Q.Q$10.Q5.Q$13.
Q5.Q$A11.Q.Q3.Q.Q2.Q3.A.A$A12.Q5.Q2.Q.Q2.2A$23.Q$18.Q$17.Q.Q$18.Q10.A
$.A26.A$2.A19.2A3.A$2.A19.2A4.2A$3A23.2A!
You can add 120 more ticks to the lifespan of your "strictly engineered" 30x30 pattern by adding a beehive close to the gun.

Code: Select all

x = 30, y = 25, rule = B3/S23Super:T30,30
A18.2A8.A$19.2A2$16.2A5.2A$16.2A5.2A4$6.2Q$5.Q2.Q$6.2Q$10.Q5.Q$9.Q.Q3.
Q.Q$10.Q5.Q$13.Q5.Q$A.2A8.Q.Q3.Q.Q2.Q3.A.A$A.A.A8.Q5.Q2.Q.Q2.2A$3.A19.
Q$18.Q$17.Q.Q$18.Q10.A$.A26.A$2.A19.2A3.A$2.A19.2A4.2A$3A23.2A!

User avatar
Vort
Posts: 84
Joined: May 14th, 2024, 6:35 am

Re: Maximum lifespan on torus

Post by Vort » June 16th, 2024, 3:32 am

vilc wrote:
June 15th, 2024, 5:33 pm
You can add 120 more ticks to the lifespan of your "strictly engineered" 30x30 pattern by adding a beehive close to the gun.
I significantly rewrote junk generator to allow more compact placement of still lifes and here's the result:
2840 + junk = 6543

Code: Select all

x = 30, y = 30, rule = LifeSuper:T30,30
3$A18.2A8.A$19.2A2$16.2A5.2A$16.2A5.2A4$6.2Q17.pA$pA4.Q2.Q14.3pA3.pA$
pA5.2Q14.pA6.pA$10.Q5.Q5.2pA$9.Q.Q3.Q.Q$10.Q5.Q$13.Q5.Q$A.2A8.Q.Q3.Q.
Q2.Q3.A.A$A.A.A8.Q5.Q2.Q.Q2.2A$3.A19.Q$18.Q$17.Q.Q$18.Q10.A$.A26.A$2.
A19.2A3.A$2.A19.2A4.2A$3A23.2A!

Code: Select all

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace TorusLife
{
    class Pattern : IEquatable<Pattern>
    {
        public readonly int Width;
        public readonly int Height;
        int[,] cells;

        public Pattern(int width, int height)
        {
            Width = width;
            Height = height;
            cells = new int[Width, Height];
        }

        public Pattern(Pattern source)
        {
            Width = source.Width;
            Height = source.Height;
            cells = new int[Width, Height];
            for (int y = 0; y < Height; y++)
                for (int x = 0; x < Width; x++)
                    cells[x, y] = source.cells[x, y];
        }

        public Pattern Generate()
        {
            var result = new Pattern(Width, Height);
            for (int y = 0; y < Height; y++)
            {
                int ym1 = y - 1;
                int yp1 = y + 1;
                if (ym1 < 0)
                    ym1 = Height - 1;
                else if (ym1 >= Height)
                    ym1 = 0;
                if (yp1 < 0)
                    yp1 = Height - 1;
                else if (yp1 >= Height)
                    yp1 = 0;
                for (int x = 0; x < Width; x++)
                {
                    int xm1 = x - 1;
                    int xp1 = x + 1;
                    if (xm1 < 0)
                        xm1 = Width - 1;
                    else if (xm1 >= Width)
                        xm1 = 0;
                    if (xp1 < 0)
                        xp1 = Width - 1;
                    else if (xp1 >= Width)
                        xp1 = 0;
                    int neighbours =
                        cells[xm1, ym1] +
                        cells[x, ym1] +
                        cells[xp1, ym1] +
                        cells[xm1, y] +
                        cells[xp1, y] +
                        cells[xm1, yp1] +
                        cells[x, yp1] +
                        cells[xp1, yp1];

                    if (cells[x, y] == 1)
                        result.cells[x, y] = (neighbours == 2 || neighbours == 3) ? 1 : 0;
                    else
                        result.cells[x, y] = neighbours == 3 ? 1 : 0;
                }
            }
            return result;
        }

        public void PlaceJunk(Random rnd, Pattern mask)
        {
            var baseStillLifes = new List<Pattern>();

            // 4 cells
            Pattern block = new Pattern(2, 2);
            block.ReadRLE(new string[] { "x = 2, y = 2, rule = B3/S23", "2o$2o!" });
            baseStillLifes.Add(block);
            Pattern tub = new Pattern(3, 3);
            tub.ReadRLE(new string[] { "x = 3, y = 3, rule = B3/S23", "bo$obo$bo!" });
            baseStillLifes.Add(tub);
            // 5 cells
            Pattern boat = new Pattern(3, 3);
            boat.ReadRLE(new string[] { "x = 3, y = 3, rule = B3/S23", "bo$obo$2o!" });
            baseStillLifes.Add(boat);
            // 6 cells
            Pattern carrier = new Pattern(4, 3);
            carrier.ReadRLE(new string[] { "x = 4, y = 3, rule = B3/S23", "2b2o$o2bo$2o!" });
            baseStillLifes.Add(carrier);
            Pattern barge = new Pattern(4, 4);
            barge.ReadRLE(new string[] { "x = 4, y = 4, rule = B3/S23", "2bo$bobo$obo$bo!" });
            baseStillLifes.Add(barge);
            Pattern beehive = new Pattern(4, 3);
            beehive.ReadRLE(new string[] { "x = 4, y = 3, rule = B3/S23", "b2o$o2bo$b2o!" });
            baseStillLifes.Add(beehive);
            Pattern ship = new Pattern(3, 3);
            ship.ReadRLE(new string[] { "x = 3, y = 3, rule = B3/S23", "b2o$obo$2o!" });
            baseStillLifes.Add(ship);
            Pattern snake = new Pattern(4, 2);
            snake.ReadRLE(new string[] { "x = 4, y = 2, rule = B3/S23", "2obo$ob2o!" });
            baseStillLifes.Add(snake);
            // 7 cells
            Pattern eater1 = new Pattern(4, 4);
            eater1.ReadRLE(new string[] { "x = 4, y = 4, rule = B3/S23", "2o$obo$2bo$2b2o!" });
            baseStillLifes.Add(eater1);
            Pattern loaf = new Pattern(4, 4);
            loaf.ReadRLE(new string[] { "x = 4, y = 4, rule = B3/S23", "b2o$o2bo$bobo$2bo!" });
            baseStillLifes.Add(loaf);
            Pattern longBoat = new Pattern(4, 4);
            longBoat.ReadRLE(new string[] { "x = 4, y = 4, rule = B3/S23", "2bo$bobo$obo$2o!" });
            baseStillLifes.Add(longBoat);
            Pattern longSnake = new Pattern(5, 3);
            longSnake.ReadRLE(new string[] { "x = 5, y = 3, rule = B3/S23", "2o$obobo$3b2o!" });
            baseStillLifes.Add(longSnake);

            var stillLifesSet = new HashSet<Pattern>();
            foreach (var baseStillLife in baseStillLifes)
            {
                for (int swapxy = 0; swapxy < 2; swapxy++)
                    for (int reverseX = 0; reverseX < 2; reverseX++)
                        for (int reverseY = 0; reverseY < 2; reverseY++)
                        {
                            int newWidth = swapxy == 0 ? baseStillLife.Width : baseStillLife.Height;
                            int newHeight = swapxy == 0 ? baseStillLife.Height : baseStillLife.Width;
                            var transformed = new Pattern(newWidth, newHeight);
                            for (int y = 0; y < baseStillLife.Height; y++)
                            {
                                for (int x = 0; x < baseStillLife.Width; x++)
                                {
                                    transformed.cells[swapxy == 0 ? x : y, swapxy == 0 ? y : x] =
                                        baseStillLife.cells[reverseX == 0 ? x : baseStillLife.Width - x - 1,
                                            reverseY == 0 ? y : baseStillLife.Height - y - 1];
                                }
                            }
                            stillLifesSet.Add(transformed);
                        }
            }

            Pattern[] stamps = stillLifesSet.ToArray();

            for (int i = 0; i < Width * Height / 4; i++)
            {
                int stampIndex = rnd.Next(stamps.Length);
                Pattern stamp = stamps[stampIndex];
                int offsetX = rnd.Next(Width);
                int offsetY = rnd.Next(Height);
                bool collided = false;
                for (int y = -1; y < stamp.Height + 1; y++)
                {
                    for (int x = -1; x < stamp.Width + 1; x++)
                    {
                        int maskCells = 0;
                        int centerState = 0;
                        int neighbourCount1 = 0;
                        int neighbourCount2 = 0;
                        for (int dy = -1; dy < 2; dy++)
                        {
                            for (int dx = -1; dx < 2; dx++)
                            {
                                int sX = x + dx;
                                int sY = y + dy;
                                int pX = sX + offsetX;
                                int pY = sY + offsetY;
                                WrapCoordinates(ref pX, ref pY);
                                maskCells += mask.cells[pX, pY];

                                int combinedState = cells[pX, pY];
                                if (sX >= 0 && sX < stamp.Width &&
                                    sY >= 0 && sY < stamp.Height)
                                {
                                    neighbourCount1 += stamp.cells[sX, sY];
                                    combinedState |= stamp.cells[sX, sY];
                                }
                                if (maskCells > 0 && neighbourCount1 > 0 &&
                                    maskCells + neighbourCount1 > 2)
                                {
                                    collided = true;
                                    break;
                                }
                                if (dx == 0 && dy == 0)
                                    centerState = combinedState;
                                else
                                    neighbourCount2 += combinedState;
                            }
                            if (collided)
                                break;
                        }
                        if (collided)
                            break;
                        if ((centerState == 0 && neighbourCount2 == 3) ||
                            (centerState == 1 && (neighbourCount2 < 2 || neighbourCount2 > 3)))
                        {
                            collided = true;
                            break;
                        }
                    }
                    if (collided)
                        break;
                }
                if (!collided)
                {
                    for (int y = 0; y < stamp.Height; y++)
                        for (int x = 0; x < stamp.Width; x++)
                        {
                            int sX = x + offsetX;
                            int sY = y + offsetY;
                            WrapCoordinates(ref sX, ref sY);
                            cells[sX, sY] |= stamp.cells[x, y];
                        }
                }
            }
        }

        public void Measure(out int lifespan, out int period)
        {
            Pattern pattern = this;
            var patterns = new Dictionary<Pattern, int>();
            patterns.Add(this, 0);

            for (int i = 0; ; i++)
            {
                pattern = pattern.Generate();
                int patternIndex;
                if (patterns.TryGetValue(pattern, out patternIndex))
                {
                    period = patterns.Count - patternIndex;
                    lifespan = i - period + 1;
                    return;
                }
                patterns.Add(pattern, i + 1);
            }
        }

        public Pattern GetMask(int ticks)
        {
            Pattern mask = new Pattern(Width, Height);
            Pattern current = this;
            for (int i = 0; i < ticks; i++)
            {
                for (int y = 0; y < Height; y++)
                    for (int x = 0; x < Width; x++)
                        mask.cells[x, y] |= current.cells[x, y];
                current = current.Generate();
            }
            return mask;
        }

        public void Merge(Pattern pattern)
        {
            for (int y = 0; y < Height; y++)
                for (int x = 0; x < Width; x++)
                    cells[x, y] |= pattern.cells[x, y];
        }

        public void WriteRLE(string fileName, string comment = null)
        {
            var sb = new StringBuilder();
            if (comment != null)
                sb.AppendLine($"#C {comment}");
            sb.AppendLine($"x = {Width}, y = {Height}, rule = B3/S23:T{Width},{Height}");
            for (int y = 0; y < Height; y++)
            {
                for (int x = 0; x < Width; x++)
                    sb.Append(cells[x, y] == 1 ? 'o' : 'b');
                sb.AppendLine(y == Height - 1 ? "!" : "$");
            }
            File.WriteAllText(fileName, sb.ToString());
        }

        public void ReadRLE(string[] lines)
        {
            int x = 0;
            int y = 0;

            string scount = "";

            foreach (var line in lines)
            {
                if (line.StartsWith("#"))
                    continue;
                if (line.StartsWith("x"))
                    continue;

                for (int i = 0; i < line.Length; i++)
                {
                    char c = line[i];
                    if (c >= '0' && c <= '9')
                    {
                        scount += c;
                    }
                    else
                    {
                        if (c == '$')
                        {
                            x = 0;
                            int count = 1;
                            if (scount != "")
                                count = int.Parse(scount);
                            y += count;
                            scount = "";
                        }
                        else if (c == '!')
                            break;
                        else if (c == 'o' || c == 'b')
                        {
                            int count = 1;
                            if (scount != "")
                                count = int.Parse(scount);
                            for (int k = 0; k < count; k++)
                            {
                                cells[x, y] = c == 'o' ? 1 : 0;
                                x++;
                                WrapCoordinates(ref x, ref y);
                            }
                            scount = "";
                        }
                    }
                }
            }
        }

        public void ReadRLE(string fileName)
        {
            ReadRLE(File.ReadAllLines(fileName));
        }

        public override int GetHashCode()
        {
            int shift = 0;
            int hash = Width << 16 | Height;
            for (int y = 0; y < Height; y++)
                for (int x = 0; x < Width; x++)
                {
                    hash = hash ^ cells[x, y] << shift;
                    shift = (shift + 1) & 31;
                }
            return hash;
        }

        public bool Equals(Pattern other)
        {
            if (Width != other.Width || Height != other.Height)
                return false;
            for (int y = 0; y < Height; y++)
                for (int x = 0; x < Width; x++)
                    if (cells[x, y] != other.cells[x, y])
                        return false;
            return true;
        }

        void WrapCoordinates(ref int x, ref int y)
        {
            if (x < 0)
                x += Width;
            else if (x >= Width)
                x -= Width;
            if (y < 0)
                y += Height;
            else if (y >= Height)
                y -= Width;
        }
    }

    class Program
    {
        Program()
        {
            Pattern source = new Pattern(30, 30);
            source.ReadRLE(new string[] {
                "x = 30, y = 30, rule = B3/S23:T30,30",
                "3$o18b2o8bo$19b2o2$16b2o5b2o$16b2o5b2o4$6b2o$5bo2bo$6b2o$10bo5bo$9bobo",
                "3bobo$10bo5bo$13bo5bo$ob2o8bobo3bobo2bo3bobo$obobo8bo5bo2bobo2b2o$3bo",
                "19bo$18bo$17bobo$18bo10bo$bo26bo$2bo19b2o3bo$2bo19b2o4b2o$3o23b2o!"
            });
            int unstableTicks = 1471;
            Pattern mask = source.GetMask(unstableTicks);
            Pattern unstableSource = new Pattern(source);
            for (int i = 0; i < unstableTicks; i++)
                unstableSource = unstableSource.Generate();

            int period;
            int lifespan;
            int totalMax = 0;
            int countMax = 0;
            int bestLifespan = 0;
            int bestLifespanLocal = 0;

            Random rnd = new Random();
            for (int i = 0; ; i++)
            {
                Pattern junk = new Pattern(source.Width, source.Height);
                junk.PlaceJunk(rnd, mask);
                Pattern pattern = new Pattern(unstableSource);
                pattern.Merge(junk);
                pattern.Measure(out lifespan, out period);
                lifespan += unstableTicks;

                if (i % 100 == 0 && i != 0)
                {
                    totalMax += bestLifespanLocal;
                    countMax++;
                    int avgMax = totalMax / countMax;
                    Console.WriteLine($"Iteration: {i,6}, lifespan: {bestLifespan,4} | {bestLifespanLocal,4} | {avgMax,4}");
                    bestLifespanLocal = 0;
                }
                if (lifespan > bestLifespan)
                {
                    bestLifespan = lifespan;
                    pattern = new Pattern(source);
                    pattern.Merge(junk);
                    pattern.WriteRLE(
                        $"{source.Width}x{source.Height}_{lifespan}.rle",
                        $"lifespan: {lifespan}, period: {period}");
                }
                if (lifespan > bestLifespanLocal)
                    bestLifespanLocal = lifespan;
            }
        }

        static void Main(string[] args)
        {
            new Program();
        }
    }
}
I will update previous message with new plot soon.

vilc
Posts: 68
Joined: March 20th, 2024, 4:36 pm

Re: Maximum lifespan on torus

Post by vilc » June 17th, 2024, 3:22 pm

80x80 methuselah with lifespan 171,836,799 (an order of magnitude better than the previous record) :

Code: Select all

x = 80, y = 80, rule = LifeSuper:T80,80
38.Q5.Q5.3U$35.Q5.Q8.U23.U$34.Q.Q3.Q.Q30.U.U$35.Q5.Q31.U.U$2U30.Q5.Q
35.U$2U29.Q.Q3.Q.Q15.2U$32.Q5.Q16.U12.U$U34.Q7.U12.U10.U.U$.U28.Q3.Q.
Q6.3U7.4U10.U.U9.U$2.U27.Q4.Q10.U6.U14.U10.U$U2.U14.2A5.2A3.Q14.2U9.
3U$18.2A5.2A29.U2.U$U2.U10.A15.A27.2U$2U11.A.A13.A.A5.2U2.U25.2U.2U$
13.2A15.2A5.U2.U.U20.2U3.U.2U2.2U$38.U.2U21.U2.U.U6.U$39.U25.2U.7U$
40.2U8.2U15.U$41.U8.U.U14.U.4U$39.U11.U16.2U2.U$20.2A.2A14.2U4.2U$19.
A5.A19.2U2$18.A7.A23.2U$3.4U11.A2.A.A2.A23.U$7.U10.3A3.3A24.3U24.2U$
2.2U3.U45.U24.2U$2.2U2.U4$8.2U66.2U$8.U.U64.U2.U$10.U65.2U$10.U.2U4.
2A5.2A14.2K$7.2U.U.U5.2A5.2A14.K.K.2K.K$7.2U.U.U30.K.K.2K$.2U7.U.2U
29.2K$U2.U3.4U2.U21.K35.2U$.2U4.U3.2U22.K22.4U8.U.U$9.2U24.K21.U7.2U
3.U$10.U31.K14.U3.2U2.2U2.2U$8.U32.3K14.U2.2U$8.2U30.5K$39.K5.4K$38.
2K5.K3.K$37.2K10.K5.2U10.2U$38.K.K3.K.K.K5.U.U9.U2.U$39.2K3.2K8.U12.
2U$51.2U.U$52.U.U.2U$31.2K19.U.U.2U$31.2K18.2U.U7.2U$35.3K13.U2.4U3.U
2.U7.2U$47.K4.2U3.U4.2U8.U.U$47.K6.2U18.U$5.4U45.U19.2U$2U7.U41.2U.U
2.U$2U2.2U3.U35.2K5.U2.3U$4.2U2.U36.2K5.U$36.2K12.2U.7U$32.2K.K.K10.U
2.U.U6.U$32.K.2K.K.K8.2U3.U.2U2.2U$10.2U26.2K12.2U.2U8.2U11.2U$U9.U.U
52.U2.U8.U$12.U65.2U$12.U.2U49.U2.U$9.2U.U.U38.U10.U2.U$9.2U.U.U37.U.
U9.U.U$3.2U7.U.2U36.U.U10.U$2.U2.U3.4U2.U37.U19.2U$3.2U4.U3.2U50.2U5.
U.U$11.2U31.Q14.U5.2U5.U$12.U30.Q.Q12.U.U10.2U$10.U33.Q13.U.U$10.2U
29.Q17.U5.U$40.Q.Q22.3U$41.Q26.U$38.Q5.Q7.2U13.2U$37.Q.Q3.Q.Q7.U!
Pulse-dividers are in yellow-green.

Golly is much slower at simulating on a torus than on an infinite plane, so I couldn't run this pattern for more than 8,000,000 ticks. If you want to check the lifespan, paste the following pattern in an empty infinite plan, then go to generation 171,835,392 (4416 * 2 * 4^5 * 19) with HashLife, cut a 80x80 square near the middle and paste it in a toric universe to simulate the 1407 remaining ticks.

Code: Select all

x = 208, y = 253, rule = B3/S23
2b2o78b2o78b2o$2bo32b4o43bo32b4o43bo32b4o$3b3o24b2o7bo43b3o24b2o7bo43b
3o24b2o7bo$5bo24b2o2b2o3bo45bo24b2o2b2o3bo45bo24b2o2b2o3bo$34b2o2bo75b
2o2bo75b2o2bo4$28b2o10b2o66b2o10b2o66b2o10b2o$27bo2bo9bobo64bo2bo9bobo
64bo2bo9bobo$28b2o12bo65b2o12bo65b2o12bo$42bob2o76bob2o76bob2o$39b2obo
bo74b2obobo74b2obobo$39b2obobo74b2obobo74b2obobo$33b2o7bob2o67b2o7bob
2o67b2o7bob2o$23b2o7bo2bo3b4o2bo57b2o7bo2bo3b4o2bo57b2o7bo2bo3b4o2bo$
10b4o8bobo8b2o4bo3b2o45b4o8bobo8b2o4bo3b2o45b4o8bobo8b2o4bo3b2o$9bo7b
2o3bo18b2o46bo7b2o3bo18b2o46bo7b2o3bo18b2o$9bo3b2o2b2o2b2o19bo46bo3b2o
2b2o2b2o19bo46bo3b2o2b2o2b2o19bo$10bo2b2o25bo49bo2b2o25bo49bo2b2o25bo$
40b2o78b2o78b2o3$7b2o10b2o66b2o10b2o66b2o10b2o$6bobo9bo2bo64bobo9bo2bo
64bobo9bo2bo$6bo12b2o65bo12b2o65bo12b2o$3b2obo76b2obo76b2obo$4bobob2o
74bobob2o74bobob2o$4bobob2o74bobob2o74bobob2o$3b2obo7b2o67b2obo7b2o67b
2obo7b2o$3bo2b4o3bo2bo7b2o57bo2b4o3bo2bo7b2o57bo2b4o3bo2bo7b2o$4b2o3bo
4b2o8bobo57b2o3bo4b2o8bobo57b2o3bo4b2o8bobo$6b2o18bo59b2o18bo59b2o18bo
$6bo19b2o9b4o45bo19b2o9b4o45bo19b2o9b4o$3b2obo2bo22b2o7bo41b2obo2bo22b
2o7bo41b2obo2bo22b2o7bo$4bo2b3o22b2o2b2o3bo42bo2b3o22b2o2b2o3bo42bo2b
3o22b2o2b2o3bo$4bo31b2o2bo43bo31b2o2bo43bo31b2o2bo$2b2ob7o70b2ob7o70b
2ob7o$o2bobo6bo67bo2bobo6bo67bo2bobo6bo$2o3bob2o2b2o67b2o3bob2o2b2o67b
2o3bob2o2b2o$4b2ob2o8b2o11b2o10b2o40b2ob2o8b2o11b2o10b2o40b2ob2o8b2o
11b2o10b2o$17bo2bo8bo2bo9bobo52bo2bo8bo2bo9bobo52bo2bo8bo2bo9bobo$30b
2o12bo65b2o12bo65b2o12bo$17bo2bo23bob2o49bo2bo23bob2o49bo2bo23bob2o$5b
o10bo2bo21b2obobo38bo10bo2bo21b2obobo38bo10bo2bo21b2obobo$4bobo9bobo
22b2obobo37bobo9bobo22b2obobo37bobo9bobo22b2obobo$4bobo10bo17b2o7bob2o
36bobo10bo17b2o7bob2o36bobo10bo17b2o7bob2o$5bo19b2o7bo2bo3b4o2bo37bo
19b2o7bo2bo3b4o2bo37bo19b2o7bo2bo3b4o2bo$17b2o5bobo8b2o4bo3b2o50b2o5bo
bo8b2o4bo3b2o50b2o5bobo8b2o4bo3b2o$11bo5b2o5bo18b2o31bo14bo5b2o5bo18b
2o31bo14bo5b2o5bo18b2o$10bobo10b2o19bo30bobo12bobo10b2o19bo30bobo12bob
o10b2o19bo$10bobo29bo33bo13bobo29bo33bo13bobo29bo$11bo5bo24b2o29bo17bo
5bo24b2o29bo17bo5bo24b2o$17b3o52bobo22b3o52bobo22b3o$20bo52bo26bo52bo
26bo$4b2o13b2o49bo5bo7b2o13b2o49bo5bo7b2o13b2o$5bo63bobo3bobo7bo63bobo
3bobo7bo$2b3o65bo5bo5b3o65bo5bo5b3o$2bo23bo40bo5bo8bo23bo40bo5bo8bo23b
o$25bobo38bobo3bobo30bobo38bobo3bobo30bobo$25bobo39bo5bo31bobo39bo5bo
31bobo$26bo5b2o30bo5bo35bo5b2o30bo5bo35bo5b2o$7b2o23b2o29bobo3bobo15b
2o23b2o29bobo3bobo15b2o23b2o$7bo12bo43bo5bo16bo12bo43bo5bo16bo12bo$8bo
10bobo10bo34bo7bo12bo10bobo10bo34bo7bo12bo10bobo10bo$5b4o10bobo9bobo
28bo3bobo6b3o7b4o10bobo9bobo28bo3bobo6b3o7b4o10bobo9bobo$5bo14bo10bo2b
o27bo4bo10bo6bo14bo10bo2bo27bo4bo10bo6bo14bo10bo2bo$8b3o21bo2bo14b2o5b
2o3bo14b2o9b3o21bo2bo14b2o5b2o3bo14b2o9b3o21bo2bo$8bo2bo38b2o5b2o29bo
2bo38b2o5b2o29bo2bo$10b2o20bo2bo10bo15bo27b2o20bo2bo10bo15bo27b2o20bo
2bo$19b2ob2o8b2o11bobo13bobo5b2o2bo25b2ob2o8b2o11bobo13bobo5b2o2bo25b
2ob2o8b2o$15b2o3bob2o2b2o17b2o15b2o5bo2bobo20b2o3bob2o2b2o17b2o15b2o5b
o2bobo20b2o3bob2o2b2o$15bo2bobo6bo42bob2o21bo2bobo6bo42bob2o21bo2bobo
6bo$17b2ob7o44bo25b2ob7o44bo25b2ob7o$2b2o15bo52b2o8b2o15bo52b2o8b2o15b
o$2bobo14bob4o48bo8bobo14bob4o48bo8bobo14bob4o$3bo16b2o2bo46bo11bo16b
2o2bo46bo11bo16b2o2bo$52b2ob2o14b2o4b2o53b2ob2o14b2o4b2o$51bo5bo19b2o
52bo5bo19b2o2$2b2o46bo7bo23b2o46bo7bo23b2o$2bo32b4o11bo2bobo2bo23bo32b
4o11bo2bobo2bo23bo32b4o$3b3o24b2o7bo10b3o3b3o24b3o24b2o7bo10b3o3b3o24b
3o24b2o7bo$5bo24b2o2b2o3bo45bo24b2o2b2o3bo45bo24b2o2b2o3bo$34b2o2bo75b
2o2bo75b2o2bo4$28b2o10b2o66b2o10b2o66b2o10b2o$27bo2bo9bobo64bo2bo9bobo
64bo2bo9bobo$28b2o12bo65b2o12bo65b2o12bo$42bob2o4b2o5b2o14b2o47bob2o4b
2o5b2o14b2o47bob2o$39b2obobo5b2o5b2o14bobob2obo38b2obobo5b2o5b2o14bobo
b2obo38b2obobo$39b2obobo30bobob2o38b2obobo30bobob2o38b2obobo$33b2o7bob
2o29b2o36b2o7bob2o29b2o36b2o7bob2o$23b2o7bo2bo3b4o2bo21bo35b2o7bo2bo3b
4o2bo21bo35b2o7bo2bo3b4o2bo$10b4o8bobo8b2o4bo3b2o22bo22b4o8bobo8b2o4bo
3b2o22bo22b4o8bobo8b2o4bo3b2o$9bo7b2o3bo18b2o24bo21bo7b2o3bo18b2o24bo
21bo7b2o3bo18b2o$9bo3b2o2b2o2b2o19bo31bo14bo3b2o2b2o2b2o19bo31bo14bo3b
2o2b2o2b2o19bo$10bo2b2o25bo32b3o14bo2b2o25bo32b3o14bo2b2o25bo$40b2o30b
5o43b2o30b5o43b2o$71bo5b4o70bo5b4o$70b2o5bo3bo68b2o5bo3bo$7b2o10b2o48b
2o10bo5b2o10b2o48b2o10bo5b2o10b2o$6bobo9bo2bo48bobo3bobobo5bobo9bo2bo
48bobo3bobobo5bobo9bo2bo$6bo12b2o50b2o3b2o8bo12b2o50b2o3b2o8bo12b2o$3b
2obo76b2obo76b2obo$4bobob2o74bobob2o74bobob2o$4bobob2o53b2o19bobob2o
53b2o19bobob2o$3b2obo7b2o47b2o18b2obo7b2o47b2o18b2obo7b2o$3bo2b4o3bo2b
o7b2o41b3o13bo2b4o3bo2bo7b2o41b3o13bo2b4o3bo2bo7b2o$4b2o3bo4b2o8bobo
52bo4b2o3bo4b2o8bobo52bo4b2o3bo4b2o8bobo$6b2o18bo52bo6b2o18bo52bo6b2o
18bo$6bo19b2o9b4o45bo19b2o9b4o45bo19b2o9b4o$3b2obo2bo22b2o7bo41b2obo2b
o22b2o7bo41b2obo2bo22b2o7bo$4bo2b3o22b2o2b2o3bo35b2o5bo2b3o22b2o2b2o3b
o35b2o5bo2b3o22b2o2b2o3bo$4bo31b2o2bo36b2o5bo31b2o2bo36b2o5bo31b2o2bo$
2b2ob7o56b2o12b2ob7o56b2o12b2ob7o$o2bobo6bo51b2obobo10bo2bobo6bo51b2ob
obo10bo2bobo6bo$2o3bob2o2b2o51bob2obobo8b2o3bob2o2b2o51bob2obobo8b2o3b
ob2o2b2o$4b2ob2o8b2o11b2o10b2o26b2o12b2ob2o8b2o11b2o10b2o26b2o12b2ob2o
8b2o11b2o10b2o$17bo2bo8bo2bo9bobo52bo2bo8bo2bo9bobo52bo2bo8bo2bo9bobo$
30b2o12bo65b2o12bo65b2o12bo$17bo2bo23bob2o49bo2bo23bob2o49bo2bo23bob2o
$5bo10bo2bo21b2obobo38bo10bo2bo21b2obobo38bo10bo2bo21b2obobo$4bobo9bob
o22b2obobo37bobo9bobo22b2obobo37bobo9bobo22b2obobo$4bobo10bo17b2o7bob
2o36bobo10bo17b2o7bob2o36bobo10bo17b2o7bob2o$5bo19b2o7bo2bo3b4o2bo37bo
19b2o7bo2bo3b4o2bo37bo19b2o7bo2bo3b4o2bo$17b2o5bobo8b2o4bo3b2o50b2o5bo
bo8b2o4bo3b2o50b2o5bobo8b2o4bo3b2o$11bo5b2o5bo18b2o31bo14bo5b2o5bo18b
2o31bo14bo5b2o5bo18b2o$10bobo10b2o19bo30bobo12bobo10b2o19bo30bobo12bob
o10b2o19bo$10bobo29bo33bo13bobo29bo33bo13bobo29bo$11bo5bo24b2o29bo17bo
5bo24b2o29bo17bo5bo24b2o$17b3o52bobo22b3o52bobo22b3o$20bo52bo26bo52bo
26bo$4b2o13b2o49bo5bo7b2o13b2o49bo5bo7b2o13b2o$5bo63bobo3bobo7bo63bobo
3bobo7bo$2b3o65bo5bo5b3o65bo5bo5b3o$2bo23bo40bo5bo8bo23bo40bo5bo8bo23b
o$25bobo38bobo3bobo30bobo38bobo3bobo30bobo$25bobo39bo5bo31bobo39bo5bo
31bobo$26bo5b2o30bo5bo35bo5b2o30bo5bo35bo5b2o$7b2o23b2o29bobo3bobo15b
2o23b2o29bobo3bobo15b2o23b2o$7bo12bo43bo5bo16bo12bo43bo5bo16bo12bo$8bo
10bobo10bo34bo7bo12bo10bobo10bo34bo7bo12bo10bobo10bo$5b4o10bobo9bobo
28bo3bobo6b3o7b4o10bobo9bobo28bo3bobo6b3o7b4o10bobo9bobo$5bo14bo10bo2b
o27bo4bo10bo6bo14bo10bo2bo27bo4bo10bo6bo14bo10bo2bo$8b3o21bo2bo14b2o5b
2o3bo14b2o9b3o21bo2bo14b2o5b2o3bo14b2o9b3o21bo2bo$8bo2bo38b2o5b2o29bo
2bo38b2o5b2o29bo2bo$10b2o20bo2bo10bo15bo27b2o20bo2bo10bo15bo27b2o20bo
2bo$19b2ob2o8b2o11bobo13bobo5b2o2bo25b2ob2o8b2o11bobo13bobo5b2o2bo25b
2ob2o8b2o$15b2o3bob2o2b2o17b2o15b2o5bo2bobo20b2o3bob2o2b2o17b2o15b2o5b
o2bobo20b2o3bob2o2b2o$15bo2bobo6bo42bob2o21bo2bobo6bo42bob2o21bo2bobo
6bo$17b2ob7o44bo25b2ob7o44bo25b2ob7o$2b2o15bo52b2o8b2o15bo52b2o8b2o15b
o$2bobo14bob4o48bo8bobo14bob4o48bo8bobo14bob4o$3bo16b2o2bo46bo11bo16b
2o2bo46bo11bo16b2o2bo$52b2ob2o14b2o4b2o53b2ob2o14b2o4b2o$51bo5bo19b2o
52bo5bo19b2o2$2b2o46bo7bo23b2o46bo7bo23b2o$2bo32b4o11bo2bobo2bo23bo32b
4o11bo2bobo2bo23bo32b4o$3b3o24b2o7bo10b3o3b3o24b3o24b2o7bo10b3o3b3o24b
3o24b2o7bo$5bo24b2o2b2o3bo45bo24b2o2b2o3bo45bo24b2o2b2o3bo$34b2o2bo75b
2o2bo75b2o2bo4$28b2o10b2o66b2o10b2o66b2o10b2o$27bo2bo9bobo64bo2bo9bobo
64bo2bo9bobo$28b2o12bo65b2o12bo65b2o12bo$42bob2o4b2o5b2o14b2o47bob2o4b
2o5b2o14b2o47bob2o$39b2obobo5b2o5b2o14bobob2obo38b2obobo5b2o5b2o14bobo
b2obo38b2obobo$39b2obobo30bobob2o38b2obobo30bobob2o38b2obobo$33b2o7bob
2o29b2o36b2o7bob2o29b2o36b2o7bob2o$23b2o7bo2bo3b4o2bo21bo35b2o7bo2bo3b
4o2bo21bo35b2o7bo2bo3b4o2bo$10b4o8bobo8b2o4bo3b2o22bo22b4o8bobo8b2o4bo
3b2o22bo22b4o8bobo8b2o4bo3b2o$9bo7b2o3bo18b2o24bo21bo7b2o3bo18b2o24bo
21bo7b2o3bo18b2o$9bo3b2o2b2o2b2o19bo31bo14bo3b2o2b2o2b2o19bo31bo14bo3b
2o2b2o2b2o19bo$10bo2b2o25bo32b3o14bo2b2o25bo32b3o14bo2b2o25bo$40b2o30b
5o43b2o30b5o43b2o$71bo5b4o70bo5b4o$70b2o5bo3bo68b2o5bo3bo$7b2o10b2o48b
2o10bo5b2o10b2o48b2o10bo5b2o10b2o$6bobo9bo2bo48bobo3bobobo5bobo9bo2bo
48bobo3bobobo5bobo9bo2bo$6bo12b2o50b2o3b2o8bo12b2o50b2o3b2o8bo12b2o$3b
2obo76b2obo76b2obo$4bobob2o74bobob2o74bobob2o$4bobob2o53b2o19bobob2o
53b2o19bobob2o$3b2obo7b2o47b2o18b2obo7b2o47b2o18b2obo7b2o$3bo2b4o3bo2b
o7b2o41b3o13bo2b4o3bo2bo7b2o41b3o13bo2b4o3bo2bo7b2o$4b2o3bo4b2o8bobo
52bo4b2o3bo4b2o8bobo52bo4b2o3bo4b2o8bobo$6b2o18bo52bo6b2o18bo52bo6b2o
18bo$6bo19b2o9b4o45bo19b2o9b4o45bo19b2o9b4o$3b2obo2bo22b2o7bo41b2obo2b
o22b2o7bo41b2obo2bo22b2o7bo$4bo2b3o22b2o2b2o3bo35b2o5bo2b3o22b2o2b2o3b
o35b2o5bo2b3o22b2o2b2o3bo$4bo31b2o2bo36b2o5bo31b2o2bo36b2o5bo31b2o2bo$
2b2ob7o56b2o12b2ob7o56b2o12b2ob7o$o2bobo6bo51b2obobo10bo2bobo6bo51b2ob
obo10bo2bobo6bo$2o3bob2o2b2o51bob2obobo8b2o3bob2o2b2o51bob2obobo8b2o3b
ob2o2b2o$4b2ob2o8b2o11b2o10b2o26b2o12b2ob2o8b2o11b2o10b2o26b2o12b2ob2o
8b2o11b2o10b2o$17bo2bo8bo2bo9bobo52bo2bo8bo2bo9bobo52bo2bo8bo2bo9bobo$
30b2o12bo65b2o12bo65b2o12bo$17bo2bo23bob2o49bo2bo23bob2o49bo2bo23bob2o
$5bo10bo2bo21b2obobo38bo10bo2bo21b2obobo38bo10bo2bo21b2obobo$4bobo9bob
o22b2obobo37bobo9bobo22b2obobo37bobo9bobo22b2obobo$4bobo10bo17b2o7bob
2o36bobo10bo17b2o7bob2o36bobo10bo17b2o7bob2o$5bo19b2o7bo2bo3b4o2bo37bo
19b2o7bo2bo3b4o2bo37bo19b2o7bo2bo3b4o2bo$17b2o5bobo8b2o4bo3b2o50b2o5bo
bo8b2o4bo3b2o50b2o5bobo8b2o4bo3b2o$11bo5b2o5bo18b2o31bo14bo5b2o5bo18b
2o31bo14bo5b2o5bo18b2o$10bobo10b2o19bo30bobo12bobo10b2o19bo30bobo12bob
o10b2o19bo$10bobo29bo33bo13bobo29bo33bo13bobo29bo$11bo5bo24b2o29bo17bo
5bo24b2o29bo17bo5bo24b2o$17b3o52bobo22b3o52bobo22b3o$20bo52bo26bo52bo
26bo$4b2o13b2o49bo5bo7b2o13b2o49bo5bo7b2o13b2o$5bo63bobo3bobo7bo63bobo
3bobo7bo$2b3o65bo5bo5b3o65bo5bo5b3o$2bo23bo40bo5bo8bo23bo40bo5bo8bo23b
o$25bobo38bobo3bobo30bobo38bobo3bobo30bobo$25bobo39bo5bo31bobo39bo5bo
31bobo$26bo5b2o30bo5bo35bo5b2o30bo5bo35bo5b2o$7b2o23b2o29bobo3bobo15b
2o23b2o29bobo3bobo15b2o23b2o$7bo12bo43bo5bo16bo12bo43bo5bo16bo12bo$8bo
10bobo10bo34bo7bo12bo10bobo10bo34bo7bo12bo10bobo10bo$5b4o10bobo9bobo
28bo3bobo6b3o7b4o10bobo9bobo28bo3bobo6b3o7b4o10bobo9bobo$5bo14bo10bo2b
o27bo4bo10bo6bo14bo10bo2bo27bo4bo10bo6bo14bo10bo2bo$8b3o21bo2bo14b2o5b
2o3bo14b2o9b3o21bo2bo14b2o5b2o3bo14b2o9b3o21bo2bo$8bo2bo38b2o5b2o29bo
2bo38b2o5b2o29bo2bo$10b2o20bo2bo10bo15bo27b2o20bo2bo10bo15bo27b2o20bo
2bo$19b2ob2o8b2o11bobo13bobo5b2o2bo25b2ob2o8b2o11bobo13bobo5b2o2bo25b
2ob2o8b2o$15b2o3bob2o2b2o17b2o15b2o5bo2bobo20b2o3bob2o2b2o17b2o15b2o5b
o2bobo20b2o3bob2o2b2o$15bo2bobo6bo42bob2o21bo2bobo6bo42bob2o21bo2bobo
6bo$17b2ob7o44bo25b2ob7o44bo25b2ob7o$2b2o15bo52b2o8b2o15bo52b2o8b2o15b
o$2bobo14bob4o48bo8bobo14bob4o48bo8bobo14bob4o$3bo16b2o2bo46bo11bo16b
2o2bo46bo11bo16b2o2bo$52b2ob2o14b2o4b2o53b2ob2o14b2o4b2o$51bo5bo19b2o
52bo5bo19b2o2$50bo7bo23b2o46bo7bo23b2o$50bo2bobo2bo23bo47bo2bobo2bo23b
o$50b3o3b3o24b3o44b3o3b3o24b3o$85bo79bo8$50b2o5b2o71b2o5b2o$50b2o5b2o
71b2o5b2o!

User avatar
Vort
Posts: 84
Joined: May 14th, 2024, 6:35 am

Re: Maximum lifespan on torus

Post by Vort » June 18th, 2024, 4:04 am

Because guns don't work very well in 25x25 torus, I tried alternative method.
Glider can be reflected back and forth few times and then trigger explosion.
Engineered, 440:

Code: Select all

x = 25, y = 25, rule = LifeSuper:T25,25
$.A5.2A$2.2A3.2A$.2A$12.2A$12.2A2$3.2A$3.2A10.2pA$15.2pA$8.2A$8.2A10.
2A$20.2A3$14.2A$14.2A7.2A$23.2A3$17.2A$17.2A!
Engineered + junk, 3264:

Code: Select all

x = 25, y = 25, rule = LifeSuper:T25,25
12.2pA$.A5.2A$2.2A3.2A$.2A19.pA$12.2A7.pA.pA$12.2A6.pA.pA$20.2pA$3.2A
12.pA$3.2A11.pA.pA$15.pA.pA$8.2A5.2pA$8.2A10.2A$20.2A$.2pA$.2pA$10.2pA
2.2A$4.2pA4.pA3.2A7.2A$5.pA5.pA11.2A$5.pA.pA4.pA$6.2pA3.2pA$17.2A$17.
2A$14.pA$13.pA.pA$12.pA.pA!

User avatar
confocaloid
Posts: 3524
Joined: February 8th, 2022, 3:15 pm

Re: Maximum lifespan on torus

Post by confocaloid » June 18th, 2024, 8:14 am

Vort wrote:
June 18th, 2024, 4:04 am
[...]
Engineered, 440:

Code: Select all

x = 25, y = 25, rule = LifeSuper:T25,25
$.A5.2A$2.2A3.2A$.2A$12.2A$12.2A2$3.2A$3.2A10.2pA$15.2pA$8.2A$8.2A10.
2A$20.2A3$14.2A$14.2A7.2A$23.2A3$17.2A$17.2A!
[...]
Longboats and a blinker, lifespan = 520 ticks:

Code: Select all

x = 25, y = 22, rule = B3/S23:T25,25
bo20bo$2bo18bobo$3o17bobo$20b2o4$13b2o$5bo6bobo$4bobo4bobo$3bobo6bo$3b
2o4$12bo$11bobo$10bobo10b2o$10b2o10bobo$21bobo$22bo$15b3o!

User avatar
Vort
Posts: 84
Joined: May 14th, 2024, 6:35 am

Re: Maximum lifespan on torus

Post by Vort » June 22nd, 2024, 7:21 am

I decided to check how switching from C# to C++ without significantly changing the algorithms improves the performance of random soup search program.
For comparison purposes I added output of patterns (soups) per second value to both programs.
Here is how this value looks like for different torus sizes on my computer:

Code: Select all

C#
10x10 - 17485
20x20 - 1278
40x40 - 100
80x80 - 10

C++
10x10 - 28273
20x20 - 2675
40x40 - 256
80x80 - 26

Code: Select all

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;

namespace TorusLife
{
    class Pattern : IEquatable<Pattern>
    {
        public const int Width = 20;
        public const int Height = 20;
        static byte[] lookup;
        byte[] cells;

        static Pattern()
        {
            lookup = new byte[16];
            lookup[3] = 1;
            lookup[10] = 1;
            lookup[11] = 1;
        }

        public Pattern()
        {
            cells = new byte[Width * Height];
        }

        public byte this[int x, int y]
        {
            get
            {
                return cells[x + y * Width];
            }
            set
            {
                cells[x + y * Width] = value;
            }
        }

        public Pattern Advance()
        {
            var result = new Pattern();
            for (int y = 0; y < Height; y++)
            {
                int ym1 = y == 0 ? Height - 1 : y - 1;
                int yp1 = y == Height - 1 ? 0 : y + 1;
                for (int x = 0; x < Width; x++)
                {
                    int xm1 = x == 0 ? Width - 1 : x - 1;
                    int xp1 = x == Width - 1 ? 0 : x + 1;
                    int neighbours =
                        this[xm1, ym1] +
                        this[x, ym1] +
                        this[xp1, ym1] +
                        this[xm1, y] +
                        this[xp1, y] +
                        this[xm1, yp1] +
                        this[x, yp1] +
                        this[xp1, yp1];
                    result[x, y] = lookup[neighbours & 7 | this[x, y] << 3];
                }
            }
            return result;
        }

        public void Randomize(Random rnd)
        {
            for (int i = 0; i < cells.Length; i++)
                cells[i] = rnd.Next(100) < 35 ? (byte)1 : (byte)0;
        }

        public void Measure(out int lifespan, out int period)
        {
            Pattern pattern = this;
            var patterns = new Dictionary<Pattern, int>();
            patterns.Add(this, 0);

            for (int i = 0; ; i++)
            {
                pattern = pattern.Advance();
                int patternIndex;
                if (patterns.TryGetValue(pattern, out patternIndex))
                {
                    period = patterns.Count - patternIndex;
                    lifespan = i - period + 1;
                    return;
                }
                patterns.Add(pattern, i + 1);
            }
        }

        public void WriteRLE(string fileName, string comment = null)
        {
            var sb = new StringBuilder();
            if (comment != null)
                sb.AppendLine($"#C {comment}");
            sb.AppendLine($"x = {Width}, y = {Height}, rule = B3/S23:T{Width},{Height}");
            var tags = new List<char>();
            for (int y = 0; y < Height; y++)
            {
                for (int x = 0; x < Width; x++)
                    tags.Add(this[x, y] == 1 ? 'o' : 'b');
                tags.Add(y == Height - 1 ? '!' : '$');
            }
            int lineLength = 0;
            for (int i = 0; i < tags.Count; i++)
            {
                int runCount = 1;
                while (i + 1 < tags.Count && tags[i] == tags[i + 1])
                {
                    i++;
                    runCount++;
                }
                string run = runCount == 1 ? $"{tags[i]}" : $"{runCount}{tags[i]}";
                if (lineLength + run.Length > 70)
                {
                    sb.AppendLine();
                    lineLength = 0;
                }
                sb.Append(run);
                lineLength += run.Length;
            }
            File.WriteAllText(fileName, sb.ToString());
        }

        public override int GetHashCode()
        {
            int hash = 0;
            int shift = 0;
            for (int i = 0; i < cells.Length; i++)
            {
                hash = hash ^ cells[i] << shift;
                shift = (shift + 1) & 31;
            }
            return hash;
        }

        public bool Equals(Pattern other)
        {
            for (int i = 0; i < cells.Length; i++)
                if (cells[i] != other.cells[i])
                    return false;
            return true;
        }
    }

    class Program
    {
        Program()
        {
            Random rnd = new Random();

            int period;
            int lifespan;
            int totalMax = 0;
            int countMax = 0;
            int bestLifespan = 0;
            int bestLifespanLocal = 0;
            long ts = Stopwatch.GetTimestamp();
            for (int i = 0, j = 0; ; i++, j++)
            {
                Pattern pattern = new Pattern();
                pattern.Randomize(rnd);
                pattern.Measure(out lifespan, out period);
                long dts = Stopwatch.GetTimestamp() - ts;
                if (dts > Stopwatch.Frequency * 4)
                {
                    totalMax += bestLifespanLocal;
                    countMax++;
                    int avgMax = totalMax / countMax;
                    long pps = Stopwatch.Frequency * j / dts;
                    Console.WriteLine($"Iteration: {i,7}, {pps,5} P/s, lifespan: {bestLifespan,4} | {bestLifespanLocal,4} | {avgMax,4}");
                    bestLifespanLocal = 0;
                    j = 0;
                    ts = Stopwatch.GetTimestamp();
                }
                if (lifespan > bestLifespan)
                {
                    bestLifespan = lifespan;
                    pattern.WriteRLE(
                        $"{Pattern.Width}x{Pattern.Height}_{lifespan}.rle",
                        $"lifespan: {lifespan}, period: {period}");
                }
                if (lifespan > bestLifespanLocal)
                    bestLifespanLocal = lifespan;
            }
        }

        static void Main(string[] args)
        {
            new Program();
        }
    }
}

Code: Select all

#include <memory>
#include <vector>
#include <map>
#include <string>
#include <sstream>
#include <fstream>
#include <iostream>
#include <iomanip>
#include <random>
#include <chrono>

class Pattern : public std::enable_shared_from_this<Pattern>
{
public:
	static const int Width = 20;
	static const int Height = 20;
	static const int Area = Width * Height;

private:
	static constexpr uint8_t lookup[16] = {
		0, 0, 0, 1, 0, 0, 0, 0,
		0, 0, 1, 1, 0, 0, 0, 0
	};

	uint8_t cells[Area];

public:
	uint8_t Get(int x, int y)
	{
		return cells[x + y * Width];
	}

	void Set(int x, int y, uint8_t value)
	{
		cells[x + y * Width] = value;
	}

	std::shared_ptr<Pattern> Advance()
	{
		auto result = std::make_shared<Pattern>();
		for (int y = 0; y < Height; y++)
		{
			int ym1 = y == 0 ? Height - 1 : y - 1;
			int yp1 = y == Height - 1 ? 0 : y + 1;
			for (int x = 0; x < Width; x++)
			{
				int xm1 = x == 0 ? Width - 1 : x - 1;
				int xp1 = x == Width - 1 ? 0 : x + 1;
				int neighbours =
					Get(xm1, ym1) +
					Get(x, ym1) +
					Get(xp1, ym1) +
					Get(xm1, y) +
					Get(xp1, y) +
					Get(xm1, yp1) +
					Get(x, yp1) +
					Get(xp1, yp1);
				result->Set(x, y, lookup[neighbours & 7 | Get(x, y) << 3]);
			}
		}
		return result;
	}

	void Randomize(std::mt19937& rnd)
	{
		for (int i = 0; i < Area; i++)
			cells[i] = rnd() % 100 < 35;
	}

	void Measure(int& lifespan, int& period)
	{
		auto compare = [](const std::shared_ptr<Pattern>& l,
			const std::shared_ptr<Pattern>& r) {
			return l->Compare(*r);
		};

		std::map<std::shared_ptr<Pattern>, int,
			decltype(compare)> patterns(compare);

		std::shared_ptr<Pattern> pattern(shared_from_this());
		patterns.insert({ pattern, 0 });

		for (int i = 0; ; i++)
		{
			pattern = pattern->Advance();
			auto p = patterns.insert({ pattern, i + 1 });
			if (!p.second)
			{
				period = int(patterns.size() - p.first->second);
				lifespan = i - period + 1;
				return;
			}
		}
	}

	void WriteRLE(std::string fileName, std::string comment = "")
	{
		std::stringstream ss;
		if (!comment.empty())
			ss << "#C " << comment << std::endl;
		ss << "x = " << Width << ", y = " << Height <<
			", rule = B3/S23:T" << Width << "," << Height << std::endl;
		std::vector<char> tags;
		for (int y = 0; y < Height; y++)
		{
			for (int x = 0; x < Width; x++)
				tags.push_back(Get(x, y) == 1 ? 'o' : 'b');
			tags.push_back(y == Height - 1 ? '!' : '$');
		}
		int lineLength = 0;
		for (int i = 0; i < tags.size(); i++)
		{
			int runCount = 1;
			while (i + 1 < tags.size() && tags[i] == tags[i + 1])
			{
				i++;
				runCount++;
			}
			std::stringstream runSs;
			if (runCount != 1)
				runSs << runCount;
			runSs << tags[i];
			std::string run = runSs.str();
			if (lineLength + run.size() > 70)
			{
				ss << std::endl;
				lineLength = 0;
			}
			ss << run;
			lineLength += int(run.size());
		}
		std::ofstream fs(fileName);
		fs << ss.str();
		fs.close();
	}

	bool Compare(const Pattern& other)
	{
		for (int i = 0; i < Area; i++)
			if (cells[i] < other.cells[i])
				return true;
			else if (cells[i] > other.cells[i])
				return false;
		return false;
	}
};

uint64_t GetMonotonicMicroseconds()
{
	return std::chrono::duration_cast<std::chrono::microseconds>(
		std::chrono::steady_clock::now().time_since_epoch()).count();
}

int main()
{
	uint64_t ts = GetMonotonicMicroseconds();
	std::mt19937 rnd((unsigned int)ts);

	int period;
	int lifespan;
	int totalMax = 0;
	int countMax = 0;
	int bestLifespan = 0;
	int bestLifespanLocal = 0;
	for (int i = 0, j = 0; ; i++, j++)
	{
		auto pattern = std::make_shared<Pattern>();
		pattern->Randomize(rnd);
		pattern->Measure(lifespan, period);
		uint64_t dts = GetMonotonicMicroseconds() - ts;
		if (dts > 4 * 1000000)
		{
			totalMax += bestLifespanLocal;
			countMax++;
			int avgMax = totalMax / countMax;
			uint64_t pps = 1000000LL * j / dts;
			std::cout << "Iteration: " << std::setw(7) << i << ", " <<
				std::setw(5) << pps << " P/s, lifespan: " << std::setw(4) << bestLifespan <<
				" | " << bestLifespanLocal << " | " << avgMax << std::endl;
			bestLifespanLocal = 0;
			j = 0;
			ts = GetMonotonicMicroseconds();
		}
		if (lifespan > bestLifespan)
		{
			bestLifespan = lifespan;
			std::stringstream fname;
			fname << Pattern::Width << "x" << Pattern::Height << "_" << lifespan << ".rle";
			std::stringstream comment;
			comment << "lifespan: " << lifespan << ", period: " << period;
			pattern->WriteRLE(fname.str(), comment.str());
		}
		if (lifespan > bestLifespanLocal)
			bestLifespanLocal = lifespan;
	}
	return 0;
}

Post Reply