diff --git a/kwant/linalg/lll.py b/kwant/linalg/lll.py
new file mode 100644
index 0000000000000000000000000000000000000000..5beffc96391f2aed29898b76864a2d520ec8e7a1
--- /dev/null
+++ b/kwant/linalg/lll.py
@@ -0,0 +1,192 @@
+# Copyright 2011-2013 kwant authors.
+#
+# This file is part of kwant.  It is subject to the license terms in the
+# LICENSE file found in the top-level directory of this distribution and at
+# http://kwant-project.org/license.  A list of kwant authors can be found in
+# the AUTHORS file at the top-level directory of this distribution and at
+# http://kwant-project.org/authors.
+
+from __future__ import division
+
+__all__ = ['lll', 'cvp', 'voronoi']
+
+import numpy as np
+from itertools import product
+
+def gs_coefficient(a, b):
+    """Gram-Schmidt coefficient."""
+    return np.dot(a, b) / np.linalg.norm(b)**2
+
+
+def gs(mat):
+    """Compute Gram-Schmidt decomposition on a matrix."""
+    mat = np.copy(mat)
+    for i in range(len(mat)):
+        for j in range(i):
+            mat[i] -= gs_coefficient(mat[i], mat[j]) * mat[j]
+    return mat
+
+
+def is_c_reduced(vecs, c):
+    """Check if a basis is c-reduced."""
+    vecs = gs(vecs)
+    r = np.apply_along_axis(lambda x: np.linalg.norm(x)**2, 1, vecs)
+    return np.all((r[: -1] / r[1:]) < c)
+
+
+def lll(basis, c=1.34):
+    """
+    Calculate a reduced lattice basis using LLL algorithm.
+
+    Reduce a basis of a lattice to an almost orthonormal form. For details see
+    e.g. http://en.wikipedia.org/wiki/LLL-algorithm.
+
+    Parameters
+    ----------
+    basis : 2d array-like
+        An array of lattice basis vectors to be reduced.
+    c : float
+        Reduction parameter for the algorithm. Must be larger than 1 1/3,
+        since otherwise a solution is not guaranteed to exist.
+
+    Returns
+    -------
+    reduced_basis : numpy array
+        The basis vectors of the LLL-reduced basis.
+    transformation : numpy integer array
+        Coefficient matrix for tranforming from the reduced basis to the
+        original one.
+    """
+    vecs = np.copy(basis)
+    if vecs.shape[0] > vecs.shape[1]:
+        raise ValueError('The number of basis vectors exceeds the '
+                         'space dimensionality.')
+    vecs_orig = np.copy(vecs)
+    vecsstar = np.copy(vecs)
+    m, n = vecs.shape
+    u = np.identity(m)
+    def ll_reduce(i):
+        for j in reversed(range(i)):
+            vecs[i] -= np.round(u[i, j]) * vecs[j]
+            u[i] -= np.round(u[i, j]) * u[j]
+
+    # Initialize values.
+    for i in range(m):
+        for j in range(i):
+            u[i, j] = gs_coefficient(vecs[i], vecsstar[j])
+            vecsstar[i] -= u[i, j] * vecsstar[j]
+        ll_reduce(i)
+
+    # Main part of LLL algorithm.
+    i = 0
+    while i < m-1:
+        if np.linalg.norm(vecsstar[i]) ** 2 < \
+           c * np.linalg.norm(vecsstar[i+1]) ** 2:
+            i += 1
+        else:
+            vecsstar[i+1] += u[i+1, i] * vecsstar[i]
+
+            u[i, i] = gs_coefficient(vecs[i], vecsstar[i+1])
+            u[i, i+1] = u[i+1, i] = 1
+            u[i+1, i+1] = 0
+            vecsstar[i] -= u[i, i] * vecsstar[i+1]
+            vecs[[i, i+1]] = vecs[[i+1, i]]
+            vecsstar[[i, i+1]] = vecsstar[[i+1, i]]
+            u[[i, i+1]] = u[[i+1, i]]
+            for j in range(i+2, m):
+                u[j, i] = gs_coefficient(vecs[j], vecsstar[i])
+                u[j, i+1] = gs_coefficient(vecs[j], vecsstar[i+1])
+            if abs(u[i+1, i]) > 0.5:
+                ll_reduce(i+1)
+            i = max(i-1, 0)
+    coefs = np.linalg.lstsq(vecs_orig.T, vecs.T)[0]
+    if not np.allclose(np.round(coefs), coefs, atol=1e-6):
+        raise RuntimeError('LLL algorithm instability.')
+    if not is_c_reduced(vecs, c):
+        raise RuntimeError('LLL algorithm instability.')
+    return vecs, np.array(np.round(coefs), int)
+
+
+def cvp(vec, basis, n=1):
+    """
+    Solve the n-closest vector problem for a vector, given a basis.
+
+    This algorithm performs poorly in general, so it should be supplied
+    with LLL-reduced bases.
+
+    Parameters
+    ----------
+    vec : 1d array-like
+        The lattice vectors closest to `vec` have to be found.
+    basis : 2d array-like
+        The list of basis vectors.
+    n : int
+        Number of lattice vectors closest to the point that need to be found.
+
+    Returns
+    -------
+    coords : numpy array
+        An array with the coefficients of the lattice vectors closest to the
+        requested point.
+
+    Notes
+    -----
+    This function can also be used to solve the `n` shortest lattice vector
+    problem if the `vec` is zero, and `n+1` points are requested
+    (and the first output is ignored).
+    """
+    # Calculate coordinates of the starting point in this basis.
+    basis = np.asarray(basis)
+    vec = np.asarray(vec)
+    center_coords = np.array(np.round(np.linalg.lstsq(basis.T, vec)[0]), int)
+    # Cutoff radius for n-th nearest neighbor.
+    rad = 1
+    nth_dist = np.inf
+    while True:
+        r = np.round(rad * np.linalg.cond(basis)) + 1
+        points = np.mgrid[tuple(slice(i - r, i + r) for i in center_coords)]
+        points = points.reshape(basis.shape[0], -1).T
+        if len(points) < n:
+            rad += 1
+            continue
+        point_coords = np.dot(points, basis)
+        point_coords -= vec.T
+        distances = np.sqrt(np.sum(point_coords**2, 1))
+        order = np.argsort(distances)
+        distances = distances[order]
+        if distances[n - 1] < nth_dist:
+            nth_dist = distances[n - 1]
+            rad += 1
+        else:
+            return np.array(points[order][:n], int)
+
+
+def voronoi(basis):
+    """
+    Return an array of lattice vectors forming its voronoi cell.
+
+    Parameters
+    ----------
+    basis : 2d array-like
+        Basis vectors for which the Voronoi neighbors have to be found.
+
+    Returns
+    -------
+    voronoi_neighbors : numpy array
+        All the lattice vectors that may potentially neighbor the origin.
+
+    Notes
+    -----
+    This algorithm does not calculate the minimal Voronoi cell of the lattice
+    and can be optimized. Its main aim is flood-fill, however, and better
+    safe than sorry.
+    """
+    displacements = list(product(*(len(basis) * [[0, .5]])))[1:]
+    vertices = np.array([cvp(np.dot(vec, basis), basis)[0] for vec in
+                         displacements])
+    vertices = np.array(np.round((vertices - displacements) * 2), int)
+    for i in range(len(vertices)):
+        if not np.any(vertices[i]):
+            vertices[i] += 2 * np.array(displacements[i])
+    vertices = np.concatenate([vertices, -vertices])
+    return vertices
diff --git a/kwant/linalg/tests/test_lll.py b/kwant/linalg/tests/test_lll.py
new file mode 100644
index 0000000000000000000000000000000000000000..2574dfd3a8e6cd95cd3704d86dba5385fb70a438
--- /dev/null
+++ b/kwant/linalg/tests/test_lll.py
@@ -0,0 +1,32 @@
+# Copyright 2011-2013 kwant authors.
+#
+# This file is part of kwant.  It is subject to the license terms in the
+# LICENSE file found in the top-level directory of this distribution and at
+# http://kwant-project.org/license.  A list of kwant authors can be found in
+# the AUTHORS file at the top-level directory of this distribution and at
+# http://kwant-project.org/authors.
+
+from __future__ import division
+import numpy as np
+from kwant.linalg import lll
+
+def test_lll():
+    np.random.seed(1)
+    for i in range(50):
+        x = np.random.randint(4) + 1
+        mat = np.random.randn(x, x + np.random.randint(2))
+        c = 1.34 + .5 * np.random.rand()
+        reduced_mat, coefs = lll.lll(mat)
+        assert lll.is_c_reduced(reduced_mat, c)
+        assert np.allclose(np.dot(mat.T, coefs), reduced_mat.T)
+
+
+def test_cvp():
+    np.random.seed(0)
+    for i in range(10):
+        mat = np.random.randn(4, 4)
+        mat = lll.lll(mat)[0]
+        for j in range(4):
+            point = 50 * np.random.randn(4)
+            assert np.array_equal(lll.cvp(point, mat, 10)[:3],
+                                  lll.cvp(point, mat, 3))