Maximum lifespan on torus

For discussion of specific patterns or specific families of patterns in Conway's Game of Life, both newly-discovered and well-known.
User avatar
Vort
Posts: 203
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: 203
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: 203
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: 6697
Joined: February 8th, 2022, 3:15 pm
Location: learn to protect yourself against stray gliders and sparks and self-destruct mechanisms

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: 203
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: 288
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: 203
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 9688 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: 203
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 9494 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: 1571
Joined: January 15th, 2024, 9:37 am

Re: Maximum lifespan on torus

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

engineered patterns will eventually come out of randomness
here's the gosper glider gun

Code: Select all

#R life
24bo$22bobo$12b2o6b2o12b2o$11bo3bo4b2o12b2o$2o8bo5bo3b2o$2o8bo3bob2o4b
obo$10bo5bo7bo$11bo3bo$12b2o!

vilc
Posts: 288
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: 203
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: 288
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: 203
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: 6697
Joined: February 8th, 2022, 3:15 pm
Location: learn to protect yourself against stray gliders and sparks and self-destruct mechanisms

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: 203
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;
}

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

Re: Maximum lifespan on torus

Post by Vort » July 27th, 2024, 5:54 am

confocaloid wrote:
June 18th, 2024, 8:14 am
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!
Eaters allow to make longer delays because of smaller explosions (571 ticks):

Code: Select all

x = 25, y = 25, rule = LifeSuper:T25,25
Q$3Q17.2Q$3.Q16.Q$2.2Q14.Q.Q$18.2Q3$3.2Q$4.Q$4.Q.Q$5.2Q14.2Q$21.Q$22.
3Q$14.2Q8.Q$13.Q.Q$13.Q$2.2Q8.2Q7.Q$3.Q15.3Q$3Q15.Q$Q8.2Q7.2Q$9.Q.Q$
2.A.A6.Q11.pA$3.2A6.2Q10.pA$3.A19.pA!
---

I made updated chart and also packed source RLEs to zip archive:
torus_lifespan_v15.png
torus_lifespan_v15.png (6.23 KiB) Viewed 6616 times
torus_rle_v3.zip
(11.15 KiB) Downloaded 17 times
---

Updated version of C++ program for random soup searches:

Code: Select all

#include <memory>
#include <vector>
#include <unordered_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 = 30;
	static const int Height = 30;
	static const int WordWidth = (Width + 63) / 64;
	static const int WordCount = WordWidth * Height;

private:
	uint64_t cells[WordCount];

	static void HalfAdder(uint64_t a, uint64_t b, uint64_t& carry, uint64_t& sum)
	{
		sum = a ^ b;
		carry = a & b;
	}

	static void FullAdder(uint64_t a, uint64_t b, uint64_t c, uint64_t& carry, uint64_t& sum)
	{
		uint64_t temp = a ^ b;
		sum = temp ^ c;
		carry = a & b | temp & c;
	}

public:
	bool Get(int x, int y)
	{
		int div = x / 64;
		int shift = x % 64;
		return (cells[div + y * WordWidth] >> shift & 1ULL) == 1ULL;
	}

	void Set(int x, int y, bool value)
	{
		int div = x / 64;
		int shift = x % 64;
		cells[div + y * WordWidth] &= ~(1ULL << shift);
		cells[div + y * WordWidth] |= (value ? 1ULL : 0ULL) << shift;
	}

	std::shared_ptr<Pattern> Advance()
	{
		uint64_t n1[WordCount] = { 0 };
		uint64_t n2[WordCount] = { 0 };
		for (int y = 0; y < Height; y++)
		{
			for (int x = 0; x < WordWidth; x++)
			{
				uint64_t w = cells[x + y * WordWidth];
				n1[x + y * WordWidth] |= w >> 1;
				n2[x + y * WordWidth] |= w << 1;
				if (x != 0)
					n1[x - 1 + y * WordWidth] |= w << 63;
				else
				{
					int shift = (Width + 63) % 64;
					n1[WordWidth - 1 + y * WordWidth] |= (w & 1ULL) << shift;
				}
				if (x != WordWidth - 1)
					n2[x + 1 + y * WordWidth] |= w >> 63;
				else
				{
					int shift1 = 64 - Width % 64;
					uint64_t mask = 0xFFFFFFFFFFFFFFFFULL >> shift1;
					n2[x + y * WordWidth] &= mask;
					int shift2 = (Width - 1) % 64;
					n2[y * WordWidth] |= w >> shift2;
				}
			}
		}

		auto result = std::make_shared<Pattern>();
		for (int i = 0; i < WordCount; i++)
		{
			int im1 = i - WordWidth < 0 ? i - WordWidth + WordCount : i - WordWidth;
			int ip1 = i + WordWidth >= WordCount ? i + WordWidth - WordCount : i + WordWidth;
			uint64_t j, k, l, m, n, o;
			FullAdder(n1[im1], n1[i], n1[ip1], m, j);
			FullAdder(n2[im1], n2[i], n2[ip1], n, k);
			HalfAdder(cells[im1], cells[ip1], o, l);
			uint64_t x, y, z, w;
			FullAdder(j, k, l, y, w);
			FullAdder(m, n, o, x, z);
			result->cells[i] = (cells[i] | w) & (y ^ z) & ~x;
		}
		return result;
	}

	void Randomize(std::mt19937& rnd)
	{
		for (int y = 0; y < Height; y++)
			for (int x = 0; x < Width; x++)
				Set(x, y, rnd() % 100 < 35);
	}

	void Measure(int& lifespan, int& period)
	{
		auto hash = [](const std::shared_ptr<Pattern>& p) {
			return p->GetHashCode();
		};
		auto equal = [](const std::shared_ptr<Pattern>& l,
			const std::shared_ptr<Pattern>& r) {
			return l->Equals(*r);
		};
		std::unordered_map<std::shared_ptr<Pattern>, int,
			decltype(hash), decltype(equal)> patterns(512, hash, equal);

		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();
	}

	uint64_t GetHashCode()
	{
		uint64_t hash = cells[0];
		for (int i = 1; i < WordCount; i++)
			hash = hash ^ cells[i];
		return hash;
	}

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

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 > 2 * 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;
}
Performance (soups per second) when built with clang:

Code: Select all

10x10 - 78747
20x20 - 16040
40x40 - 3196
80x80 - 376
Last edited by Vort on August 8th, 2024, 10:11 pm, edited 1 time in total.

WhiteHawk
Posts: 1198
Joined: July 10th, 2024, 5:34 pm

Re: Maximum lifespan on torus

Post by WhiteHawk » July 30th, 2024, 5:05 pm

Vort wrote:
July 27th, 2024, 5:54 am

Eaters allow to make longer delays because of smaller explosions (571 ticks):

Code: Select all

x = 25, y = 25, rule = LifeSuper:T25,25
Q$3Q17.2Q$3.Q16.Q$2.2Q14.Q.Q$18.2Q3$3.2Q$4.Q$4.Q.Q$5.2Q14.2Q$21.Q$22.
3Q$14.2Q8.Q$13.Q.Q$13.Q$2.2Q8.2Q7.Q$3.Q15.3Q$3Q15.Q$Q8.2Q7.2Q$9.Q.Q$
2.A.A6.Q11.pA$3.2A6.2Q10.pA$3.A19.pA!
Messing around, and found a 2nd blinker makes a methusaleh lasting 753 on a 25x25

Code: Select all

x = 25, y = 25, rule = B3/S23Super:T25,25
Q$3Q17.2Q$3.Q16.Q$2.2Q14.Q.Q$18.2Q$A$A$A2.2Q$4.Q$4.Q.Q$5.2Q14.2Q$21.Q
$22.3Q$14.2Q8.Q$13.Q.Q$13.Q$2.2Q8.2Q7.Q$3.Q15.3Q$3Q15.Q$Q8.2Q7.2Q$9.Q
.Q$2.A.A6.Q11.pA$3.2A6.2Q10.pA$3.A19.pA!
EDIT: changing magenta blinker's phase gets it to 1452, almost double what it was, but still not good enough.

Code: Select all

x = 25, y = 25, rule = B3/S23Super:T25,25
Q$3Q17.2Q$3.Q16.Q$2.2Q14.Q.Q$18.2Q$A$A$A2.2Q$4.Q$4.Q.Q$5.2Q14.2Q$21.Q
$22.3Q$14.2Q8.Q$13.Q.Q$13.Q$2.2Q8.2Q7.Q$3.Q15.3Q$3Q15.Q$Q8.2Q7.2Q$9.Q
.Q$2.A.A6.Q$3.2A6.2Q9.3pA$3.A!
EDIT 2: 1508 gens

Code: Select all

x = 25, y = 25, rule = B3/S23Super:T25,25
Q12.A.A$3Q10.2A5.2Q$3.Q16.Q$2.2Q14.Q.Q$18.2Q$A$A$A2.2Q$4.Q$4.Q.Q$5.2Q
14.2Q$21.Q$22.3Q$14.2Q8.Q$13.Q.Q$13.Q$2.2Q8.2Q7.Q$3.Q15.3Q$3Q15.Q$Q8.
2Q7.2Q$9.Q.Q$2.A.A6.Q11.A$3.2A6.2Q10.A$3.A19.A$14.2A!
EDIT 3: 1509 generations!

Code: Select all

x = 25, y = 25, rule = B3/S23Super:T25,25
Q12.A.A$3Q10.2A5.2Q$3.Q16.Q$2.2Q14.Q.Q$18.2Q2$2A22.A$3.2Q$4.Q$4.Q.Q$5.
2Q14.2Q$21.Q$22.3Q$14.2Q8.Q$13.Q.Q$13.Q$2.2Q8.2Q7.Q$2.2Q15.3Q$2Q16.Q$
9.2Q7.2Q4.Q$3.A5.Q.Q$4.A6.Q$2.3A6.2Q9.3A2$14.2A!
Last edited by WhiteHawk on August 7th, 2024, 2:41 pm, edited 3 times in total.
Currently working to improve Life's guns and work on updating SKOPs and Isotropic rules most similar to B3/S23 to Life standards. Will get software to begin searches eventually.

Pseudastur albicollis

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

Re: Maximum lifespan on torus

Post by Vort » July 31st, 2024, 12:37 am

WhiteHawk wrote:
July 30th, 2024, 5:05 pm
Messing around, and found a 2nd blinker makes a methusaleh lasting 753 on a 25x25
It is possible to use mixed approach, which combines engineered and pure random methods, right.

Without blinker at all, lifespan on 25x25 torus is 342, this is purest engineered result.
With random still lifes added to one of engineered solutions, lifespan goes up to 3264.
However, pure random is still the best for 25x25: it gives lifespan of 3750.

Other sizes have other proportions.
For example, for 30x30, mixed approach gives the best results.

User avatar
rattlesnake
Posts: 167
Joined: May 28th, 2022, 10:10 pm
Location: Following a 37P4H1V0

Re: Maximum lifespan on torus

Post by rattlesnake » August 8th, 2024, 9:23 pm

For 9x9:
Improved to 288 (probably can be improved)

Code: Select all

x = 9, y = 9, rule = B3/S23:T9,9
3bo4bo$o5bobo$8bo$o4b2obo$3bo3bo$3b2o2b2o$3ob2o$2bo$b2o4b2o!
I have discovered SKOP for 105, 115, 188, 476, 492 and gun_ and guntrue_ for 226, 339, 752.

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

Re: Maximum lifespan on torus

Post by Vort » August 8th, 2024, 10:13 pm

rattlesnake wrote:
August 8th, 2024, 9:23 pm
For 9x9:
Improved to 288 (probably can be improved)

Code: Select all

x = 9, y = 9, rule = B3/S23:T9,9
3bo4bo$o5bobo$8bo$o4b2obo$3bo3bo$3b2o2b2o$3ob2o$2bo$b2o4b2o!
Best random 9x9 have lifespan of 468 at this moment (random\9x9_468.rle file in torus_rle_v3.zip):

Code: Select all

x = 9, y = 9, rule = B3/S23:T9,9
2ob4obo$o2bobob2o$obo6b$bo2b3o2b$bo2b2o3b$9b$3o5bo$2bo2b2o2b$2bob2o3b!

User avatar
tommyaweosme
Posts: 1571
Joined: January 15th, 2024, 9:37 am

Re: Maximum lifespan on torus

Post by tommyaweosme » October 12th, 2024, 12:27 pm

350,100 lifespan ~9000

Code: Select all

x = 339, y = 100, rule = B3/S23:T350,100
75b3o32bo101bo32b3o$111bo99bo$44bo64b3o32bo33bo32b3o64bo$45bo99bo31bo
99bo$43b3o32bo64b3o31b3o64bo32b3o$79bo163bo$77b3o32bo97bo32b3o$113bo95b
o$46bo64b3o32bo29bo32b3o64bo$47bo99bo27bo99bo$45b3o32bo64b3o27b3o64bo
32b3o$81bo159bo$79b3o32bo93bo32b3o$115bo91bo$48bo64b3o32bo25bo32b3o64b
o$49bo99bo23bo99bo$47b3o32bo64b3o23b3o64bo32b3o$83bo155bo$81b3o32bo89b
o32b3o$117bo87bo$50bo64b3o32bo21bo32b3o64bo$51bo99bo19bo99bo$49b3o32b
o64b3o19b3o64bo32b3o$85bo151bo$83b3o32bo85bo32b3o$119bo83bo$52bo64b3o
32bo17bo32b3o64bo$53bo99bo15bo99bo$51b3o32bo64b3o15b3o64bo32b3o$87bo147b
o$85b3o32bo81bo32b3o$121bo79bo$54bo64b3o32bo13bo32b3o64bo$55bo99bo11b
o99bo$53b3o32bo64b3o11b3o64bo32b3o$89bo143bo$87b3o32bo77bo32b3o$123bo
75bo$56bo64b3o32bo9bo32b3o64bo$57bo99bo7bo99bo$55b3o32bo64b3o7b3o64bo
32b3o$91bo139bo$89b3o32bo73bo32b3o$125bo71bo$58bo64b3o32bo5bo32b3o64b
o$59bo99bo3bo99bo$57b3o32bo64b3o3b3o64bo32b3o$93bo135bo$91b3o32bo69bo
32b3o$127bo67bo$60bo64b3o67b3o64bo$61bo199bo$18b3o11b3o24b3o32bo133bo
32b3o24b3o11b3o$17bo3bo9bo3bo59bo131bo59bo3bo9bo3bo$16b2o4bo7bo4b2o56b
3o32bo65bo32b3o56b2o4bo7bo4b2o$15bobob2ob2o5b2ob2obobo91bo63bo91bobob
2ob2o5b2ob2obobo$14b2obo4bob2ob2obo4bob2o23bo64b3o63b3o64bo23b2obo4bo
b2ob2obo4bob2o$13bo4bo3bo2bobo2bo3bo4bo23bo195bo23bo4bo3bo2bobo2bo3bo
4bo$25bobo33b3o32bo129bo32b3o33bobo$13b2o7b2obobob2o7b2o57bo127bo57b2o
7b2obobob2o7b2o$25bobo67b3o32bo61bo32b3o67bobo$19b3o9b3o97bo59bo97b3o
9b3o$19bo9bo4bo29bo64b3o59b3o64bo29bo4bo9bo$24b3o4bo33bo191bo33bo4b3o
$18b2o3bo2bo8bo27b3o32bo125bo32b3o27bo8bo2bo3b2o$26bo8bo63bo123bo63bo
8bo$22bo3bo3b2o65b3o32bo57bo32b3o65b2o3bo3bo$22bo3bo3b2o2bo98bo55bo98b
o2b2o3bo3bo$26bo4b3o32bo64b3o55b3o64bo32b3o4bo$23bobo41bo187bo41bobo$
65b3o32bo121bo32b3o$101bo119bo$34bo64b3o32bo53bo32b3o64bo$35bo99bo51b
o99bo$33b3o32bo64b3o51b3o64bo32b3o$69bo183bo$67b3o32bo117bo32b3o$103b
o115bo$36bo64b3o32bo49bo32b3o64bo$37bo99bo47bo99bo$35b3o32bo64b3o47b3o
64bo32b3o$71bo179bo$69b3o32bo113bo32b3o$105bo111bo$38bo64b3o32bo45bo32b
3o64bo$39bo99bo43bo99bo$37b3o32bo64b3o43b3o64bo32b3o$73bo175bo$71b3o32b
o109bo32b3o$107bo107bo$40bo64b3o32bo41bo32b3o64bo$41bo99bo39bo99bo$39b
3o32bo64b3o39b3o64bo32b3o$75bo171bo$73b3o32bo105bo32b3o$109bo103bo$42b
o64b3o32bo37bo32b3o64bo$43bo99bo35bo99bo$41b3o32bo64b3o35b3o64bo32b3o
$77bo167bo!
here's the gosper glider gun

Code: Select all

#R life
24bo$22bobo$12b2o6b2o12b2o$11bo3bo4b2o12b2o$2o8bo5bo3b2o$2o8bo3bob2o4b
obo$10bo5bo7bo$11bo3bo$12b2o!

User avatar
confocaloid
Posts: 6697
Joined: February 8th, 2022, 3:15 pm
Location: learn to protect yourself against stray gliders and sparks and self-destruct mechanisms

Re: Maximum lifespan on torus

Post by confocaloid » October 12th, 2024, 11:39 pm

A few other forum threads related to exploring what happens in toroidal universes:
viewtopic.php?f=7&t=3383 Small Tori in B3/S23
viewtopic.php?f=2&t=5248 Finite board census?
viewtopic.php?f=2&t=4795 Agar discussion thread
viewtopic.php?f=7&t=5858 Agars and other infinite patterns
127:1 B3/S234c User:Confocal/R (isotropic CA, incomplete)
Unlikely events happen.
My silence does not imply agreement, nor indifference. If I disagreed with something in the past, then please do not construe my silence as something that could change that.

Post Reply