The Wonderful World of Organic Geometry

Dual determinants for Penrose cubes

[This is a document created 8.7.2026 by Claude Fable AI in response to my request to derive formulas for calculating the dual determinant formulas for a Penrose cube with a rank-3 starting conic $S_0$. Typewriter font refers to elements of the software infrastructure I have been constructing with Claude’s help. Text in typewriter font refer to elements of the infrastructure for the Penrose cube software. -cg]

Dualizing the Penrose Determinantal Parametrization #

How the 4×4 parameter matrix of PenroseDeterminants.js transforms under projective duality (point-wise ↔ line-wise conics), in the all-rank-3 case. Companion to PENROSE_8_CONIC_THEOREM.md; read that first for the cube combinatorics and the primal parametrization.

1. Setting #

The existing pipeline is entirely point-wise: a conic is a symmetric 3×3 form Q with incidence PᵀQP = 0 on points. Its dual — the envelope of tangent lines — is the line-wise conic Q* with mᵀQ*m = 0 on line coordinates, where Q* is the adjugate (cofactor) matrix of Q. Since Q is symmetric, Q* is symmetric too (no transpose subtlety), and

Q · Q* = det(Q) · Id

so Q* is projectively equivalent to Q⁻¹ while remaining polynomial in Q’s entries — no division, consistent with the homogenization/LCD discipline of PenroseDeterminants, and well-defined even where Q⁻¹ is not.

The mixed-determinant machinery in PenroseDeterminants.js is type-agnostic: it never asks whether matrix[0][0] is a point-wise or a line-wise conic, or whether the matrix[0][j] 3-vectors are lines or points. Feeding it the dual data (S0*, pⱼ*, dⱼ*, aⱼₖ*) therefore computes the line-wise Penrose cube with zero code changes — provided the dual parameters are chosen correctly. This document records what “correctly” means.

2. The transformation law #

Notation: δ = det(Q), Q* = adj(Q), P = [p₁ p₂ p₃] (columns = the three chords of contact), D = the symmetric 3×3 lower block holding dᵢ (diagonal) and aⱼₖ (off-diagonal).

primal (point-wise) dual (line-wise)
Q (= S0) Q* = adj(Q)
pⱼ (chord of contact, a line) pⱼ* = adj(Q)·pⱼ (apex of contact, a point)
dⱼ dⱼ* = pⱼᵀ·adj(Q)·pⱼ − dⱼ·det(Q)
aⱼₖ aⱼₖ* = pⱼᵀ·adj(Q)·pₖ − aⱼₖ·det(Q)

In one stroke, the whole lower block transforms as a single object:

D* = Pᵀ · adj(Q) · P − det(Q) · D

Note the scalars dⱼ, aⱼₖ do not transform independently: they mix with the polar pairing pⱼᵀQ*pₖ of the chords (which vanishes iff pⱼ and pₖ are conjugate lines with respect to S0).

Careful with the apex formula: the matrix that maps lines to points is Q* = adj(Q), not Q itself (Q maps points to their polar lines). So the apex of contact is adj(Q)·pⱼ, i.e. ConicUtils.polarize(Qstar, pj).

Bordered-determinant restatement #

By the bordered-determinant identity

det [[Q, pⱼ], [pₖᵀ, m]] = m·det(Q) − pₖᵀ·adj(Q)·pⱼ

the transformation law says: each dual scalar is (minus) the numeric determinant of the same bordered matrix whose formal mixed determinant defines the corresponding primal object. E.g. dⱼ* = −det[[Q, pⱼ],[pⱼᵀ, dⱼ]] evaluated as a genuine 4×4 numeric matrix — the same {0,j}×{0,j} sub-matrix whose mixed determinant is the conic Tⱼ.

Division-free P¹ form #

The formulas are polynomial, so the homogeneous [x, y] representation stays division-free: with dⱼ = [x, y],

dⱼ* = [ y·(pⱼᵀ Q* pⱼ) − x·det(Q),  y ]

and analogously for aⱼₖ*.

3. Why it works (derivation sketch) #

Every vertex conic of the primal cube is a matrix-valued Schur complement of the bordered matrix N_Ω = [[Q, P_Ω],[P_Ωᵀ, D_Ω]]:

S_Ω = det(D_Ω)·Q − P_Ω·adj(D_Ω)·P_Ωᵀ        (= det(D_Ω) × Schur complement)

(Check |Ω| = 1: dⱼQ − pⱼpⱼᵀ. Check |Ω| = 2: reproduces the S{1,2} formula in PENROSE_8_CONIC_THEOREM.md §3.)

Applying the Woodbury/Sherman–Morrison identity to adj(S_Ω) = det(S_Ω)·S_Ω⁻¹ and simplifying — using adj(adj(D_Ω)) ∝ D_Ω — the adjugate collapses back into exactly the same Schur-complement shape over the starred data:

adj(S_Ω)  ∝  det(D*_Ω)·Q* − P*_Ω·adj(D*_Ω)·P*_Ωᵀ

which is precisely the {0}∪Ω sub-determinant of the dual parameter matrix M*. The computation goes through uniformly for |Ω| = 1, 2, 3.

Involution check #

Dualizing twice returns the primal up to gauge:

Q** = δ·Q,    pⱼ** = δ²·pⱼ,    dⱼ** = δ³·dⱼ,    aⱼₖ** = δ³·aⱼₖ

consistent with the parametrization’s gauge freedom (Q ↦ μQ, pⱼ ↦ λⱼpⱼ, dⱼ ↦ (λⱼ²/μ)dⱼ, aⱼₖ ↦ (λⱼλₖ/μ)aⱼₖ leaves all vertices projectively fixed; here μ = δ, λⱼ = δ²). A strong sign-consistency check.

4. Numerical verification #

Verified (2026-07-08) through the unchanged PenroseDeterminants machinery on a generic rank-3 configuration, building M* from the table above and comparing sub-determinants of M* against adjugates of the primal results:

  • All 8 vertices (calculateTi, calculateSi, calculateT0, and S0 itself): S*_Ω ∝ adj(S_Ω), relative errors ~1e-16 … 1e-14.
  • All 12 edge elements (calculateSjTkDblLn, calculateSiT0DblLn on M*): each dual output is the apex of contact — the pole of the corresponding primal chord w.r.t. a conic of that edge’s double-contact pencil. (The pole of the chord is the same for every member of the pencil: adj(A + t·ℓℓᵀ)·ℓ ∝ adj(A)·ℓ.) So the contact-element pipeline gets its dual counterparts for free.

The per-vertex proportionality ratios differ across vertices (each S*_Ω matches adj(S_Ω) only projectively, with an Ω-dependent factor ∝ det(D_Ω)·(…)). Harmless for rendering; relevant if conics are ever compared numerically across vertices.

5. Implementation cautions #

  1. The formulas are a package. Choosing pⱼ* = Q*pⱼ with coefficient exactly 1 forces the −det(Q) coefficient in dⱼ*, aⱼₖ*. If the app normalizes the apex points for display/dragging (as it does for the Pis), the stored dⱼ* must be rescaled by λⱼ² and aⱼₖ* by λⱼλₖ to stay consistent.
  2. Rank-3 assumption is load-bearing. The proportionality factors contain det(S_Ω)-type quantities, so the clean 1:1 map between primal and dual cubes degrades exactly when a vertex drops rank: adj of a rank-2 point-conic (line pair) is the rank-1 double point (its carrier), and adj of rank 1 vanishes identically. In degenerate cases the pair (Q, Q*) must be tracked as independent data (the complete-conic viewpoint, cf. PENROSE_8_CONIC_THEOREM.md §2) rather than derived one from the other. Those are the interesting cases; this document covers the foundation they will be built on.

6. Formats / conventions (code) #

  • 6-entry conic format [a, 2h, b, 2g, 2f, c]; conversions via ConicUtils.convertArrayToQ (flat 9) / convertQToArray.
  • Adjugate: Rn.adjugate(null, Qflat) (classical adjugate; symmetric input ⇒ symmetric output).
  • Polar pairing: pⱼᵀ Q* pₖ = Rn.innerProduct(pj, Rn.matrixTimesVector(null, Qstar, pk)).
  • Determinant: Rn.determinant(Qflat).