Proportion as a calculation problem

Scaling an office building means deciding on dimensions that people will find pleasant to look at, and architecture has long answered that question with mathematics. The golden ratio, 1.618, has two convenient properties: inverting it yields roughly the value minus one, and squaring it yields roughly the value plus one. Splitting a line using 1 : 1.618 reads as more beautiful than an arbitrary split such as 1 : 1.8976. PostgreSQL is enough to turn that into a workable plan.

Extending the ratio to two dimensions produces a rectangle:

1

2

3

4

5

6

7

test=# SELECT 1.618 AS constant,

     round(1 / 1.618, 4) AS inverted,

     round(pow(1.618, 2), 4) AS squared;

constant  | inverted | squared

----------+----------+---------

1.618     | 0.6180   | 2.6179

(1 row)

Applying that to the new building gives a basic layout of 16 x 26 meters, which satisfies the required mathematical proportions.

Adding a semi-circle, and how CTEs get optimized

A semi-circle appended to the rectangle needs an ideal diameter, which comes from the same formula:

1

2

3

4

5

test=# SELECT 16.07, 16.07 * 1.618;

?column?  | ?column?

----------+----------

16.07     | 26.00126

(1 row)

The query relies on a CTE, and PostgreSQL 13 can inline such expressions. Inspecting the plan shows the inlining happening:

1

2

3

4

5

6

7

test=# WITH semi_circle AS (SELECT 16.07 * 0.618 AS size)

     SELECT size, size / 2

     FROM semi_circle;

size     | ?column?

---------+--------------------

9.93126  | 4.9656300000000000

(1 row)

The rewritten form is:

1

2

3

4

5

6

7

test=# explain WITH semi_circle AS (SELECT 16.07 * 0.618 AS size)

     SELECT size, size / 2

     FROM semi_circle;

           QUERY PLAN

-------------------------------------------

Result (cost=0.00..0.01 rows=1 width=64)

(1 row)

After that, constant folding removes everything but a result node that displays the answer — the CTE no longer appears in the execution plan.

Deriving a series of usable numbers

To keep the front of the building from looking dull, the acceptable and unacceptable dimensions have to be identified. Recursion does this job:

1

SELECT 16.07 * 0.618, SELECT 16.07 * 0.618 / 2

Starting from 26 meters, the query computes the long and short sides and feeds that output into the next iteration, producing a family of valid numbers in which every component relates correctly to every other one. PostgreSQL supports this in ANSI-compatible form: the first SELECT inside the WITH supplies the starting values, the part after UNION ALL performs the recursion, and the WHERE clause terminates it.

Sometimes the iteration number matters as well:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

test=# WITH RECURSIVE x AS

(

     SELECT 26::numeric AS base,

          26*0.382::numeric AS short,

          26*0.618::numeric AS long

     UNION ALL

     SELECT round((base * 0.618)::numeric, 4),

          round((long * 0.382)::numeric, 4),

          round((long * 0.618)::numeric, 4)

     FROM x

     WHERE base > 0.5

)

SELECT *

FROM x

WHERE base > 0.5;

    base |  short | long

---------+--------+--------

     26  |  9.932 | 16.068

16.0680  | 6.1380 | 9.9300

9.9300  | 3.7933 | 6.1367

6.1367  | 2.3442 | 3.7925

3.7925  | 1.4487 | 2.3438

2.3438  | 0.8953 | 1.4485

1.4485  | 0.5533 | 0.8952

0.8952  | 0.3420 | 0.5532

0.5532  | 0.2113 | 0.3419

(9 rows)

Here row_number() simply returns a number identifying the line being examined.

Square roots and composite return types

Golden proportions are not the only acceptable source of dimensions. Looking at great buildings such as the Pantheon, architects also worked with square roots, cubic roots and similar values. PostgreSQL handles those through a function:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

test=# WITH RECURSIVE x AS

(

     SELECT 26::numeric AS base,

          26*0.382::numeric AS short,

          26*0.618::numeric AS long

     UNION ALL

     SELECT round((base * 0.618)::numeric, 4),

          round((long * 0.382)::numeric, 4),

          round((long * 0.618)::numeric, 4)

     FROM x

     WHERE base > 0.5

)

SELECT row_number() OVER (),

     *

FROM x

WHERE base > 0.5;

row_number | base    | short  | long

------------+---------+--------+--------

1          | 26      | 9.932  | 16.068

2          | 16.0680 | 6.1380 | 9.9300

3          | 9.9300  | 3.7933 | 6.1367

4          | 6.1367  | 2.3442 | 3.7925

5          | 3.7925  | 1.4487 | 2.3438

6          | 2.3438  | 0.8953 | 1.4485

7          | 1.4485  | 0.5533 | 0.8952

8          | 0.8952  | 0.3420 | 0.5532

9          | 0.5532  | 0.2113 | 0.3419

(9 rows)

22, 27m and 35m are examples of correct numbers that support the desired aesthetic.

Working from several dimensions is a different problem. To return more than one field, the function uses a composite data type:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

BEGIN;

CREATE TYPE good_number_type AS (

      base      numeric,

      golden_short      numeric,

      golden_long       numeric,

      sqrt2      numeric,

      sqrt3      numeric,

      sqrt4      numeric,

      sqrt5      numeric

);

CREATE OR REPLACE FUNCTION good_numbers(numeric)

RETURNS good_number_type AS

$

     SELECT ($1,

           0.382 * $1,

           0.618 * $1,

           sqrt(2) * $1,

           sqrt(3) * $1,

           2 * $1,

           sqrt(5) * $1

           )::good_number_type;

$ LANGUAGE 'sql';

COMMIT;

A composite value in the target list is awkward to read on its own, but its columns can be expanded:

1

2

3

4

5

6

7

8

9

10

11

test=# x

Expanded display is on.

test=# SELECT * FROM good_numbers(16);

-[ RECORD 1 ]+-----------------

base         | 16

golden_short | 6.112

golden_long  | 9.888

sqrt2        | 22.6274169979695

sqrt3        | 27.712812921102

sqrt4        | 32

sqrt5        | 35.7770876399966

Expanding the columns also makes it possible to compute results for an entire series of numbers by passing the id into the function:

1

2

3

4

test=# SELECT a FROM good_numbers(16) AS a;

-[ RECORD 1 ]

------------------------------------------------------------

a | (16,6.112,9.888,22.6274169979695,27.712812921102,32,35.7770876399966)