I was reading some notes from QUT about using spectral graph theory to partition a graph using eigenvalues of unnormalised Laplacian. Their Matlab code looks like this:

% Form W = Gaussian distribution based on distances
W = zeros(100, 100);
D = zeros(100, 100);

sigma = 2;
N = 100;
for i = 1:N
    for j = 1:N
        if (j ~= i)
            % Calculate the distance between two points
            dist = norm([A(i, 1) - A(j, 1); A(i, 2) - A(j, 2)]);
            expp = exp(-dist^2 / (2 * sigma^2));
            adjacency(i, j) = 1;
            % Add the weights to the matrix
            W(i, j) = expp;
            % Add up the row sum as we go
            D(i, i) = D(i, i) + expp;
        end
    end
end

L = D - W;

Find the eigenpairs of L

[vec, val] = eig(L, 'vector');
% Ensure the eigenvalues and eigenvectors are sorted in ascending order
[val,ind] = sort(val);
vec = vec(:, ind);

% Plot with clusters identified by marker
% v2
figure;
hold on;
for i = 1:N
    plot(i, vec(i, 2), ['k' plot_syms{i}]);
end

Personally, this gives me nightmares from undergrad numerical methods classes. So here’s how to do it in Haskell. Full source (including cabal config) for this post is here.

module Notes where

To create the W matrix we use buildMatrix:

buildW :: Double -> Matrix Double -> Matrix Double
buildW sigma a = buildMatrix n n $ \(i,j) -> f sigma a i j
  where
    n = rows a

    f sigma a i j = if j /= i
                        then expp sigma a i j
                        else 0.0

    dist :: Matrix Double -> Int -> Int -> Double
    dist m i j = norm2 $ fromList [ m!i!0 - m!j!0, m!i!1 - m!j!1 ]

    expp :: Double -> Matrix Double -> Int -> Int -> Double
    expp sigma m i j = exp $ (-(dist m i j)**2)/(2*sigma**2)

The D matrix is a diagonal matrix with each element being the sum of a row of W, which is nicely expressible by composing a few functions:

buildD :: Matrix Double -> Matrix Double
buildD w = diag
         . fromList
         . map (foldVector (+) 0)
         . toRows
         $ w
  where
    n = rows w

The L matrix is real and symmetric so we use eigSH which provides the eigenvalues in descending order.

lapEigs :: Double -> Matrix Double -> (Vector Double, Matrix Double)
lapEigs sigma m = eigSH l
  where
    w = buildW sigma m
    d = buildD w
    l = d - w

To finish up, the Fiedler eigenvector corresponds to the second smallest eigenvalue:

fiedler :: Double -> Matrix Double -> (Double, Vector Double)
fiedler sigma m = (val ! (n-2), vector $ concat $ toLists $ vec ¿ [n-2])
  where
    (val, vec) = lapEigs sigma m
    n = rows m

To plot the eigenvalues and eigenvector we use the Chart library which uses the cairo backend.

doPlot "eigenvalues.png" "Eigenvalues" "eigenvalue" $ zip [0..] (reverse $ toList val)

doPlot "fiedler.png"
       "Second eigenvalue of unnormalised Laplacian"
       "fiedler eigenvector"
       (zip [0..] $ toList algConnecEigVec)