Code golf a random orthogonal matrix

9

An orthogonal matrix is a square matrix with real entries whose columns and rows are orthogonal unit vectors (i.e., orthonormal vectors).

This means that M^T M = I, where I is the identity matrix and ^T signifies matrix transposition.

Note that this is orthogonal not "special orthogonal" so the determinant of M can be 1 or -1.

The aim of this challenge is not machine precision so if M^T M = I to within 4 decimal places that will be fine.

The task is to write code that takes a positive integer n > 1 and outputs a random orthogonal n by n matrix. The matrix should be randomly and uniformly chosen from all n by n orthogonal matrices. In this context, "uniform" is defined in terms of the Haar measure, which essentially requires that the distribution does not change if multiplied by any freely chosen orthogonal matrix. This means the values of the matrix will be floating point values in the range -1 to 1.

The input and output can be any form you find convenient.

Please show an explicit example of your code running.

You may not use any existing library function which creates orthogonal matrices. This rule is a little subtle so I will explain more. This rule bans the use of any existing function which takes in some (or no) input and outputs a matrix of size at least n by n which is guaranteed to be orthogonal. As an extreme example, if you want the n by n identity matrix, you will have to create it yourself.

You can use any standard random number generator library for choosing random numbers of your choosing.

Your code should complete within at most a few seconds for n < 50.

user9206

Posted 2016-11-09T23:00:15.367

Reputation:

So using built-in identity matrix is forbidden? – JungHwan Min – 2016-11-10T00:33:50.200

@JHM You can't use it to create an n by n identity matrix at least. – None – 2016-11-10T05:55:22.457

What about diag? It creates an diagonal matrix which is indeed orthogonal but not always orthonormal. – Karl Napf – 2016-11-10T07:26:19.460

This seems to be an example of "do X without Y", which - so the consensus - should be avoided. – flawr – 2016-11-10T23:01:38.643

1Diagonal matrices aren't orthogonal matrices so diag should be ok. – Angs – 2016-11-11T05:43:39.593

Answers

7

Haskell, 169 150 148 141 132 131 bytes

import Numeric.LinearAlgebra
z=(unitary.flatten<$>).randn 1
r 1=asRow<$>z 1
r n=do;m<-r$n-1;(<>diagBlock[m,1]).haussholder 2<$>z n

Recursively extend an orthogonal matrix of size n-1 by adding 1 to lower right corner and apply a random Householder reflection. randn gives a matrix with random values from a gaussian distribution and z d gives a uniformly distributed unit vector in d dimensions.

haussholder tau v returns the matrix I - tau*v*vᵀ which isn't orthogonal when v isn't a unit vector.

Usage:

*Main> m <- r 5
*Main> disp 5 m
5x5
-0.24045  -0.17761   0.01603  -0.83299  -0.46531
-0.94274   0.12031   0.00566   0.29741  -0.09098
-0.02069   0.30417  -0.93612  -0.13759   0.10865
 0.02155  -0.83065  -0.35109   0.32365  -0.28556
-0.22919  -0.41411   0.01141  -0.30659   0.82575
*Main> (<1e-14) . maxElement . abs $ tr m <> m - ident 5
True

Angs

Posted 2016-11-09T23:00:15.367

Reputation: 4 825

Making the 1×1 matrix takes too much space for my taste, a special case just for getting zero from a gaussian random variable :/ (Without it, there's a infinitesimal chance to get a zero column) – Angs – 2016-11-10T11:40:00.083

I like your spirit of making it totally correct, but i think you can drop that requirement. In my code there is also a chance that 2 rows are linearly dependent and no one cares. – Karl Napf – 2016-11-10T15:22:51.890

@KarlNapf well, I figured out a way to lose two bytes from that part anyway, so problem partly solved :) – Angs – 2016-11-10T15:35:18.717

Ah okay, deleting my comments... – Karl Napf – 2016-11-11T11:11:34.280

Always happy when a Haskell answer wins! – None – 2016-11-16T14:23:54.557

4

Python 2+NumPy, 163 bytes

Thanks to xnor for pointing out to use normal distributed random values instead of uniform ones.

from numpy import*
n=input()
Q=random.randn(n,n)
for i in range(n):
 for j in range(i):u=Q[:,j];Q[:,i]-=u*dot(u,Q[:,i])/dot(u,u)
Q/=(Q**2).sum(axis=0)**0.5
print Q

Uses the Gram Schmidt Orthogonalization on a matrix with gaussian random values to have all directions.

The demonstration code is followed by

print dot(Q.transpose(),Q)

n=3:

[[-0.2555327   0.89398324  0.36809917]
 [-0.55727299  0.17492767 -0.81169398]
 [ 0.79003155  0.41254608 -0.45349298]]
[[  1.00000000e+00   0.00000000e+00   0.00000000e+00]
 [  0.00000000e+00   1.00000000e+00  -5.55111512e-17]
 [  0.00000000e+00  -5.55111512e-17   1.00000000e+00]]

n=5:

[[-0.63470728  0.41984536  0.41569193  0.25708079  0.42659843]
 [-0.36418389  0.06244462 -0.82734663 -0.24066123  0.3479231 ]
 [ 0.07863783  0.7048799   0.08914089 -0.64230492 -0.27651168]
 [ 0.67691426  0.33798442 -0.05984083  0.17555011  0.62702062]
 [-0.01095148 -0.45688226  0.36217501 -0.65773717  0.47681205]]
[[  1.00000000e+00   1.73472348e-16   5.37764278e-17   4.68375339e-17
   -2.23779328e-16]
 [  1.73472348e-16   1.00000000e+00   1.38777878e-16   3.33066907e-16
   -6.38378239e-16]
 [  5.37764278e-17   1.38777878e-16   1.00000000e+00   1.38777878e-16
    1.11022302e-16]
 [  4.68375339e-17   3.33066907e-16   1.38777878e-16   1.00000000e+00
    5.55111512e-16]
 [ -2.23779328e-16  -6.38378239e-16   1.11022302e-16   5.55111512e-16
    1.00000000e+00]]

It completes within a blink for n=50 and a few seconds for n=500.

Karl Napf

Posted 2016-11-09T23:00:15.367

Reputation: 4 131

I don't think this is uniform. Your starting distribution with a cube, which has more stuff towards the diagonals. Random gaussians would work because they generate a spherically symmetric distribution. – xnor – 2016-11-10T07:57:54.217

@xnor fixed. Luckily, this costed exactly 1 byte. – Karl Napf – 2016-11-10T08:07:48.690

@xnor Even more lucky, this saved the bytes for -0.5 – Karl Napf – 2016-11-10T08:10:29.123

Almost, you need the mean of the normal to be 0, but that's no longer than n. – xnor – 2016-11-10T18:08:34.433

-1

Mathematica, 69 bytes, probably non-competing

#&@@QRDecomposition@Array[RandomVariate@NormalDistribution[]&,{#,#}]&

QRDecomposition returns a pair of matrices, the first of which is guaranteed to be orthogonal (and the second of which is not orthogonal, but upper triangular). One could argue that this technically obeys the letter of the restriction in the post: it doesn't output an orthogonal matrix, but a pair of matrices....

Mathematica, 63 bytes, definitely non-competing

Orthogonalize@Array[RandomVariate@NormalDistribution[]&,{#,#}]&

Orthogonalize is unambiguously forbidden by the OP. Still, Mathematica is pretty cool eh?

Greg Martin

Posted 2016-11-09T23:00:15.367

Reputation: 13 940

You may not use any existing library function which creates orthogonal **matrices**. – Karl Napf – 2016-11-10T08:38:10.153