Diagonal pieces: domicule tilings of cylinders by their count (not in the OEIS)

A notebook: every code cell below runs when the site is built, and its output is printed under it. On GitHub only the code is shown; the rendered page has the results. It sorts the domicule tilings of cylinders by the number of diagonal pieces, as the rectangles notebook does on the board with no glued edge. The tilings themselves are counted in the cylinders notebook.

The problem

On a cylinder the left and right edges of the board are glued, so a diagonal piece may cross the seam between the last and the first column. The drawing shows the first tiling of the cylinder of circumference 8 and height 8 in the gallery's numbering for k = 0, 8, 16 and 32 diagonal pieces, with the straight pieces greyed out.

In [1]
const { drawTilings } = require('./site/notes.js');
const counts = [0, 8, 16, 32];
const svgs = counts.flatMap(k => drawTilings({ rowCount: 8, colCount: 8, pieces: 'domicule', surface: 'cylinder', diagonals: k, ranks: [0] }).svgs);

figure(svgs, 'Tilings of the cylinder of circumference 8 and height 8 with k = 0, 8, 16 and 32 diagonal pieces.', { highlight: true });
Out [1] 0.24 s
k = 0 k = 8 k = 16 k = 32
Tilings of the cylinder of circumference 8 and height 8 with k = 0, 8, 16 and 32 diagonal pieces.

Give every diagonal piece the weight x, as in the rectangles notebook. The weighted count is the polynomial P(x) = \sum_k T(k)\, x^k, where T(k) counts the tilings with k diagonal pieces. At x = 0 it is the domino count of the same cylinder (dominoes on cylinders) and at x = 1 its domicule count (domicules on cylinders). This is the dimer model of Sandvik and Moessner [1] with diagonal dimers of fugacity x, on a cylinder.

The Modular solver

domicule-cylinder-torus-polynomial-modular.js runs the cell by cell transfer of the tori notebook on the king's graph with a block of coefficients in every state, one prime per worker thread. domicule-cylinder-torus-polynomial-modular.rs and domicule_cylinder_torus_polynomial_modular.py count the same polynomial. The same solver counts the tori.

The BigInt solver

domicule-cylinder-torus-polynomial-bigint.js runs the row transfer of the cylinders notebook on the king's graph and counts a piece sent to the next row one column left or right as diagonal. Its Rust and Python implementations are domicule-cylinder-torus-polynomial-bigint.rs and domicule_cylinder_torus_polynomial_bigint.py.

Numbering

The gallery can also number only the tilings of a cylinder with k diagonal pieces. The scan of the cylinder numbering then carries a list of counts by the number of diagonal pieces in every entry, and unranking keeps the order of the full numbering. The gallery offers this up to 10 \times 10. The build checks the scan's count for k = 0 against the domino count of the same cylinder and checks that the counts add up to the number of tilings.

The counts

The cell below checks the gallery's scan against the stored counts of the solvers on every board both count. The table gives, for each n \times n cylinder in spec/cases/, how many values of k have a tiling, the k of the largest term and the mean share of diagonal pieces among the n^2/2 pieces. When the OEIS was searched by the terms of the 2 \times 2 and 4 \times 4 cylinders on 11 November 2026, it had no such row.

In [2]
const stored = require('./js/core/cases')
  .testCasesOf('domicule-cylinder-torus-polynomial-modular')
  .filter(({ rowCount, colCount, torus }) => !torus && rowCount === colCount && rowCount >= 2 && rowCount % 2 === 0)
  .map(({ rowCount, expectedResult }) => ({ side: rowCount, diagonals: expectedResult.split(',') }))
  .sort((a, b) => a.side - b.side);
const gallery = nb.context.periodicDiagonals.filter(({ surface, side }) => surface === 'cylinder' && side >= 2);
const both = gallery.filter(g => stored.some(s => s.side === g.side));
const differing = both.filter(g => stored.find(s => s.side === g.side).diagonals.join() !== g.diagonals.join());

if (differing.length) {
  throw new Error(`The gallery and the solvers differ on ${differing.map(({ side }) => `the ${side}x${side} cylinder`).join(', ')}.`);
}

print(`The gallery's scan agrees with the solvers on the ${both.length} cylinders both count.`);

const rows = stored.map(({ side, diagonals }) => {
  const counts = diagonals.map(BigInt);
  const total = counts.reduce((sum, c) => sum + c, 0n);
  const peak = counts.indexOf(counts.reduce((max, c) => (c > max ? c : max), 0n));
  const scale = 10n ** 15n;
  const mean = Number((counts.reduce((sum, c, k) => sum + BigInt(k) * c, 0n) * scale) / total) / Number(scale);

  return [`${side}×${side}`, counts.filter(c => c > 0n).length, peak, (mean / ((side * side) / 2)).toFixed(6)];
});

table(['board', 'k with a tiling', 'k of the largest term', 'share of diagonal pieces'], rows);
print(`The 4 × 4 cylinder, k = 0, 1, 2, ...: ${stored.find(({ side }) => side === 4).diagonals.join(', ')}`);
Out [2] 0.00 s
The gallery's scan agrees with the solvers on the 5 cylinders both count.
boardk with a tilingk of the largest termshare of diagonal pieces
2×2200.444444
4×4540.440166
6×61080.461452
8×817160.473688
10×1026240.481482
12×1237360.486751
14×1450480.490527
16×1665640.493361
The 4 × 4 cylinder, k = 0, 1, 2, ...: 121, 0, 840, 0, 1256, 0, 416, 0, 16

Running time

Average time of the count inside each solver, without the start of its process, from the newest benchmark run in benchmarks/results that measured these solvers.

In [3]
const { formatTime, periodicDiagonalBenchmark, periodicDiagonalRun } = nb.context;

if (!periodicDiagonalRun) {
  print('These solvers have no benchmark run yet.');
} else {
  table(
    ['board', 'modular, Rust', 'modular, Python', 'modular, JavaScript', 'BigInt, Rust', 'BigInt, Python', 'BigInt, JavaScript'],
    periodicDiagonalBenchmark
      .filter(({ surface }) => surface === 'cylinder')
      .map(r => [
        `${r.rowCount}×${r.colCount}`,
        ...[r.languages.rs, r.languages.py, r.modular, r.bigintLanguages.rs, r.bigintLanguages.py, r.bigint].map(formatTime),
      ]),
  );
  print(`Run: ${periodicDiagonalRun}`);
}
Out [3] 0.00 s
boardmodular, Rustmodular, Pythonmodular, JavaScriptBigInt, RustBigInt, PythonBigInt, JavaScript
2×20.09 ms4.21 ms9.99 ms0.00 ms0.03 ms0.19 ms
4×40.10 ms4.86 ms10.47 ms0.02 ms0.14 ms0.36 ms
6×60.20 ms11.38 ms13.17 ms0.28 ms2.25 ms2.43 ms
8×80.84 ms41.40 ms22.72 ms7.21 ms49.38 ms21.62 ms
10×108.28 ms139.09 ms90.75 ms178.63 ms1.14 s447.12 ms
12×1290.28 ms1.36 s480.41 ms4.12 s23.02 s10.90 s
14×141.63 s16.52 s5.02 s---
16×1613.63 s105.32 s36.69 s---
18×18132.57 s1209.18 s522.94 s---
Run: [object Object]

Verification

The two solvers agree with each other, with the domino count of all-lattices-cylinder-torus-bigint at k = 0 and with its king's graph count summed over k, and on the cylinders of up to 16 cells with every tiling listed one by one. The tests are in harness/, the known values in spec/cases/.

References

  1. A. W. Sandvik and R. Moessner. Correlations and confinement in nonplanar two-dimensional dimer models. Physical Review B 73(14):144504, 2006. doi:10.1103/PhysRevB.73.144504