Quilt GPT
My wife, Marna, has always been into arts and crafts. Her mainstay is crochet - which I got her "hooked" on shortly after we were married. Hats, mittens, scarfs, and sweaters. Afgans/blankets. She has shelves of amigurumi plushes that she has made. The list goes on and on.
Marna is also very capable with a sewing machine. While she will be the mom to sew on club patches for everbody, she can also make full costumes. We once went on a Disney Cruise that sailed during Halloween. Passengers were encouraged ahead of time to bring costumes. Marna turned us all into authentic pirates.

And then there is quilting. Quilts are emormous projects. They require numerous squares to be made - this can be a project unto itself depending on the complexity of the squares. The squares get sewn together. Then there is the padding. Then the backing. Then the edges. Then the quilting itself. Because of the effort, quilting projects happen far less frequently than other types of projects.
If you get a Marna quilt, you know you are loved.
Recently, Marna set out to make a quilt for her mother. The dimensions of this quilt are 8 squares by 9 squares, with each square measuring about 10 inches square. There were seven types of squares - squares with different patterns. Once she had all the squares created, it came time to figure out how to lay them out in a pleasing manner. With 72 squares, across 7 different square types, in an 8 (even) by 9 (odd) rectangle, this became quite the challenge.
The Challenge
Marna arranged the squares out in a random 8x9 grid on the floor of our living room. Then she asked for input. First from my daughter, whose brain attempted to make a pattern out of the chaos. Patterns are pleasing, right? Then came my turn, and my initial instinct was to do the same thing - force a pattern out of the noise.

After moving squares around for a while, we could not arrive at a pattern that was pleasing to the eye. Every way we looked at it, our brains would latch on to the partial patterns that were there, and expect them to continue, only to be jolted by the pattern ending abruptly. Then Marna mentioed that she was originally going for a random pattern, and my brain immediately jumped to the probability machines that are LLMs.
The Prompt
Trying to come up with a random pattern where none of the squares of the same type were touching would have taken us ages. Chat GPT however, could iterate extensively and find a pattern for us.
I am making a quilt.
My quilt is 8 x 9 squares.
For the individual squares, I have the following count of each.
Make an 8x9 array in which none (or as few as possible) of the same squares touch.
===
Flowers: 12
Plaid: 12
Squares: 12
Spades: 12
Dark Green: 11
Mixed: 11
Light Green: 2
The prompt starts with basic context. Dimension are added in, and then a reference to the types of squares. After that the specific task/outcome is specified. Finally, the actual square labels and counts for each. The squares are labeled using words that we as humans understood referenced the patterns on each of the squares.
The first prompt for this task described one of the square types as "Two by Two" which Chat GPT seemed to read as a number of squares, not as a label. It then proceeded to drop other square types entirely to make the count fit the dimensions.
The steps Chat GPT went through to come to a successful conclusion are as follows:
- Exploring grid arrangement: Reviewing the count, dimensions, and labels.
- Pattern arrangement: Pattern based on a Latin rectangle - then deciding to use Python.
- Python: It then proceeded to write a Python script - and run it - to achieve the results.
- Checking work: Chat GPT then checked the work/output, and found it lacking.
- Update the code: Try again with some Python for edge cases of touching diagonals.
- Verify the output: Successful outcome.
- Present the results: In an inline table format for humans to follow.
In case you are interested, here is the 175 line Pyton program it used.
from collections import Counter
import random, math, itertools, time
rows, cols = 8, 9
counts = {
'Flowers':12,
'Plaid':12,
'Squares':12,
'Spades':12,
'Dark Green':11,
'Mixed':11,
'Light Green':2,
}
labels=list(counts)
cells=[(r,c) for r in range(rows) for c in range(cols)]
# initial fill random
arr=[]
for k,v in counts.items():
arr += [k]*v
random.shuffle(arr)
grid=[arr[i*cols:(i+1)*cols] for i in range(rows)]
orth_dirs=[(1,0),(-1,0),(0,1),(0,-1)]
diag_dirs=[(1,1),(1,-1),(-1,1),(-1,-1)]
def score(grid, diag=False):
s=0
for r in range(rows):
for c in range(cols):
for dr,dc in [(1,0),(0,1)] + ([(1,1),(1,-1)] if diag else []):
nr,nc=r+dr,c+dc
if 0<=nr<rows and 0<=nc<cols and grid[r][c]==grid[nr][nc]:
s+=1
return s
def optimize(diag=False, max_iter=300000):
arr=[]
for k,v in counts.items():
arr += [k]*v
random.shuffle(arr)
g=[arr[i*cols:(i+1)*cols] for i in range(rows)]
best=[row[:] for row in g]
bests=score(g,diag)
s=bests
T=2.0
for it in range(max_iter):
# pick two cells swap
r1,c1=random.randrange(rows),random.randrange(cols)
r2,c2=random.randrange(rows),random.randrange(cols)
if (r1,c1)==(r2,c2): continue
if g[r1][c1]==g[r2][c2]: continue
old=s
# local delta brute
affected={(r1,c1),(r2,c2)}
neigh=set()
for r,c in list(affected):
for dr,dc in orth_dirs + (diag_dirs if diag else []):
nr,nc=r+dr,c+dc
if 0<=nr<rows and 0<=nc<cols:
neigh.add((nr,nc))
region=affected|neigh
def regscore():
tot=0
dirs=[(1,0),(0,1)] + ([(1,1),(1,-1)] if diag else [])
for r,c in region:
for dr,dc in dirs:
nr,nc=r+dr,c+dc
if 0<=nr<rows and 0<=nc<cols and (nr,nc) in region:
if g[r][c]==g[nr][nc]:
tot+=1
# edges leaving region need count only from region endpoints
for r,c in affected:
for dr,dc in dirs:
nr,nc=r+dr,c+dc
if 0<=nr<rows and 0<=nc<cols and (nr,nc) not in region:
if g[r][c]==g[nr][nc]:
tot+=1
for dr,dc in [(-1,0),(0,-1)] + ([(-1,-1),(-1,1)] if diag else []):
nr,nc=r+dr,c+dc
if 0<=nr<rows and 0<=nc<cols and (nr,nc) not in region:
if g[r][c]==g[nr][nc]:
tot+=1
return tot
before=regscore()
g[r1][c1],g[r2][c2]=g[r2][c2],g[r1][c1]
after=regscore()
ns=s + (after-before)
if ns <= s or random.random() < math.exp((s-ns)/T):
s=ns
if s < bests:
bests=s
best=[row[:] for row in g]
# print("best",bests,it)
if bests==0:
return best,bests
else:
g[r1][c1],g[r2][c2]=g[r2][c2],g[r1][c1]
T*=0.99997
if T<0.001: T=0.001
return best,bests
for diag in [False, True]:
best,b=optimize(diag=diag, max_iter=100000)
print("diag",diag,"best",b)
if b==0:
for row in best: print(row)
print()
The resulting table:
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
|---|---|---|---|---|---|---|---|---|
| Spades | Flowers | Mixed | Squares | Mixed | Dark Green | Mixed | Spades | Dark Green |
| Plaid | Dark Green | Light Green | Flowers | Plaid | Spades | Squares | Plaid | Flowers |
| Squares | Spades | Squares | Dark Green | Squares | Flowers | Dark Green | Mixed | Spades |
| Flowers | Plaid | Mixed | Spades | Mixed | Spades | Plaid | Squares | Plaid |
| Mixed | Dark Green | Flowers | Plaid | Flowers | Dark Green | Flowers | Spades | Flowers |
| Squares | Spades | Squares | Dark Green | Spades | Light Green | Plaid | Mixed | Plaid |
| Flowers | Dark Green | Plaid | Mixed | Plaid | Dark Green | Squares | Dark Green | Squares |
| Squares | Mixed | Spades | Squares | Flowers | Mixed | Flowers | Spades | Plaid |
It took Chat GPT almost 45 seconds to arrive at a solution.

The Results
Once the physical squares were laid out, it was universally agreed that this was an ideal solution. The "why" it was an ideal solution remains a bit of a mystery. I do not know if this comes across in the image, but when looking at the physical quilt, your eye seems to first look for a pattern. The eye cannot find that pattern because there is none - that was the point. Interestingly, the brain accepts this as the whole of the quilt being the pattern and is happy to let you enjoy the chaos.
From here, Marna set to finalizing the squares and assembling the rows.
The result is not technically random - which was how we described it amongst our human selves. The pattern is really a structured approach to not having touching squares, which then seems to come across as pleasingly random. It would have taken us countless hours to arrive at the same "random" pattern. Leveraging a probability machine, er, LLM, made quick work of the task, and is a really fun example of how the digital and physical colliding in art can result in a very pleasing outcome.