diff --git a/docs/source/algorithm.md b/docs/source/algorithm.md
new file mode 100644
index 0000000000000000000000000000000000000000..3845eab26bc00c7d7c8949eaf84d34596c07503f
--- /dev/null
+++ b/docs/source/algorithm.md
@@ -0,0 +1,43 @@
+# Algorithm overview
+
+## Self-consistency loop
+
+In order to calculate the mean-field interaction in {eq}`mf_infinite`, we require the ground-state density matrix $\rho_{mn}(R)$.
+However, the density matrix in {eq}`density` is a functional of the mean-field interaction $\hat{V}_{\text{MF}}$ itself.
+Therefore, we need to solve for both self-consistently.
+
+We define a single iteration of a self-consistency loop:
+
+$$
+\text{SCF}(\hat{V}_{\text{init, MF}}) \to \hat{V}_{\text{new, MF}},
+$$
+
+such that it performs the following operations given an initial mean-field interaction $\hat{V}_{\text{init, MF}}$:
+
+1. Calculate the total Hamiltonian $\hat{H}(R) = \hat{H_0}(R) + \hat{V}_{\text{init, MF}}(R)$ in real-space.
+2. Fourier transform the total Hamiltonian to the momentum space $\hat{H}(R) \to \hat{H}(k)$.
+3. Calculate the ground-state density matrix $\rho_{mn}(k)$ in momentum space.
+    1. Diagonalize the Hamiltonian $\hat{H}(k)$ to obtain the eigenvalues and eigenvectors.
+    2. Calculate the fermi level $\mu$ given the desired filling of the unit cell.
+    3. Calculate the density matrix $\rho_{mn}(k)$ using the eigenvectors and the fermi level $\mu$ (currently we do not consider thermal effects so $\beta \to \infty$).
+4. Inverse Fourier transform the density matrix to real-space $\rho_{mn}(k) \to \rho_{mn}(R)$.
+5. Calculate the new mean-field interaction $\hat{V}_{\text{new, MF}}(R)$ via {eq}`mf_infinite`.
+
+## Self-consistency criterion
+
+To define the self-consistency condition, we first introduce an invertible function $f$ that uniquely maps $\hat{V}_{\text{MF}}$ to a real-valued vector which minimally parameterizes it:
+
+$$
+f : \hat{V}_{\text{MF}} \to f(\hat{V}_{\text{MF}}) \in \mathbb{R}^N.
+$$
+
+Currently, $f$ parameterizes the mean-field interaction by taking only the upper triangular elements of the matrix $V_{\text{MF}, nm}(R)$ (the lower triangular part is redundant due to the Hermiticity of the Hamiltonian) and splitting it into a real and imaginary parts to form a real-valued vector.
+
+With this function, we define the self-consistency criterion as a fixed-point problem:
+
+$$
+f(\text{SCF}(\hat{V}_{\text{MF}})) = f(\hat{V}_{\text{MF}}).
+$$
+
+To solve this fixed-point problem, we utilize a root-finding function `scipy.optimize.anderson` which uses the Anderson mixing method to find the fixed-point solution.
+However, our implementation also allows to use other custom fixed-point solvers by either providing it to `solver` or by re-defining the `solver` function.
diff --git a/docs/source/conf.py b/docs/source/conf.py
index 6ae82d9f4039aea8f48fbc083edc0605b367ab57..7b401e6ff9586b062feab2a3054e0e3ea1701d2d 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -71,6 +71,8 @@ intersphinx_mapping = {
 
 default_role = "autolink"
 
+latex_elements = {"extrapackages": r"\usepackage{braket}"}
+
 # Add any paths that contain templates here, relative to this directory.
 templates_path = ["_templates"]
 
diff --git a/docs/source/documentation/pymf.md b/docs/source/documentation/pymf.md
new file mode 100644
index 0000000000000000000000000000000000000000..7137c5a15d400e1c7371348cc4de6525e530636b
--- /dev/null
+++ b/docs/source/documentation/pymf.md
@@ -0,0 +1,76 @@
+# Package reference
+
+## Interactive problem definition
+
+To define the interactive problem, we use the following class:
+
+```{eval-rst}
+.. autoclass:: pymf.model.Model
+   :members: mfield
+```
+
+## Mean-field and density matrix
+
+```{eval-rst}
+.. automodule:: pymf.mf
+   :members: meanfield, construct_density_matrix, construct_density_matrix_kgrid, fermi_on_kgrid
+   :show-inheritance:
+```
+
+## Observables
+
+```{eval-rst}
+.. automodule:: pymf.observables
+   :members: expectation_value
+   :show-inheritance:
+```
+
+## Solvers
+
+```{eval-rst}
+.. automodule:: pymf.solvers
+   :members: solver, cost
+   :show-inheritance:
+```
+
+## Tight-binding dictionary
+
+### Manipulation
+
+```{eval-rst}
+.. automodule:: pymf.tb.tb
+   :members: add_tb, scale_tb
+   :show-inheritance:
+```
+
+### Brillouin zone transformations
+
+```{eval-rst}
+.. automodule:: pymf.tb.transforms
+   :members:
+   :show-inheritance:
+```
+
+### Parametrisation
+
+```{eval-rst}
+.. automodule:: pymf.params.rparams
+   :members:
+   :show-inheritance:
+```
+
+### Utility functions
+
+```{eval-rst}
+.. automodule:: pymf.tb.utils
+   :members:
+   :show-inheritance:
+```
+
+## `kwant` interface
+
+```{eval-rst}
+.. automodule:: pymf.kwant_helper.utils
+   :members:
+   :show-inheritance:
+```
diff --git a/docs/source/index.md b/docs/source/index.md
index 3df6663c32a93642efeb4d48c9c4e39058b0dcfc..9613885abede066d93b42f98cb5fd7978fe71505 100644
--- a/docs/source/index.md
+++ b/docs/source/index.md
@@ -18,7 +18,16 @@ kernelspec:
 :maxdepth: 1
 :caption: Tutorials
 
+```
+
+```{toctree}
+:hidden:
+:maxdepth: 1
+:caption: Documentation
+
 mf_notes.md
+algorithm.md
+documentation/pymf.md
 ```
 
 ## What is pymf?
diff --git a/docs/source/mf_notes.md b/docs/source/mf_notes.md
index 6ff0d32d5cb7799306def2699bc89efd339340a2..33d49dd7a17619bf65e5a61ecb36e2eb9d1337fd 100644
--- a/docs/source/mf_notes.md
+++ b/docs/source/mf_notes.md
@@ -1,304 +1,75 @@
-# Self-consistent mean field algorithm
+# Theory overview
 
-## Mean-field approximation
+## Interacting problems
 
-The full hamiltonian is:
+In physics, one often encounters problems where a system of multiple particles interact with each other.
+In this package, we consider a general electronic system with density-density interparticle interaction:
 
-$$
-\hat{H} = \hat{H_0} + \hat{V} = \sum_{ij} h_{ij} c^\dagger_{i} c_{j} + \frac{1}{2} \sum_{ijkl} v_{ijkl} c_i^\dagger c_j^\dagger c_l c_k
-$$
-
-We assume the dominant part of the ground state wavefunction comes from $\hat{H}_0$. Let's assume $b_i$ operators diagonalize the unperturbed hamiltonian:
-
-$$
-c_i^\dagger = \sum_{k} U_{ik} b_k^\dagger,
-$$
-
-such that the unperturbed groundstate wavefunction is:
-
-$$
-| 0 \rangle = \Pi_{E_i \leq \mu } b_i^\dagger |\textrm{vac}\rangle.
-$$
-
-Based on this definition, we define the normal ordering operator $:ABC...:$ such that it fulfills:
-
-$$
-:ABC...: | 0 \rangle = 0
-$$
-
-which practically means it orders $b_i$ operators based on whether its above or below the Fermi level $\mu$.
-
-Under this definition of normal ordering, we define the Wick's expansion of the interaction term:
-
-$$
-\begin{multline}
-c_i^\dagger c_j^\dagger c_l c_k = :c_i^\dagger c_j^\dagger c_l c_k: \\+  \overline{c_i^\dagger c_j^\dagger} :c_l c_k: + \overline{c_i^\dagger c_k} :c_j^\dagger c_l: - \overline{c_i^\dagger c_l} :c_j^\dagger c_k: + \overline{c_l c_k} :c_i^\dagger c_j^\dagger: - \overline{c_j^\dagger c_k} :c_i^\dagger c_l: + \overline{c_j^\dagger c_l} :c_i^\dagger c_k: \\
-\overline{c_i^\dagger c_j^\dagger} \overline{c_l c_k} - \overline{c_i^\dagger c_l} \overline{c_j^\dagger c_k} + \overline{c_i^\dagger c_k} \overline{c_j^\dagger c_l}
-\end{multline}
-$$
+:::{math}
+:label: hamiltonian
+\hat{H} = \hat{H_0} + \hat{V} = \sum_{ij} h_{ij} c^\dagger_{i} c_{j} + \frac{1}{2} \sum_{ij} v_{ij} c_i^\dagger c_j^\dagger c_j c_i
+:::
 
-where the overline defines Wick's contraction:
+where $c_i^\dagger$ and $c_i$ are creation and annihilation operators respectively for fermion in state $i$.
+The first term $\hat{H_0}$ is the non-interacting Hamiltonian which by itself is straightforward to solve in a single-particle basis by direct diagonalizations made easy through packages such as [kwant](https://kwant-project.org/).
+The second term $\hat{V}$ is density-density interaction term between two particles, for example Coulomb interaction.
+In order to solve the interacting problem exactly, one needs to diagonalize the full Hamiltonian $\hat{H}$ in the many-particle basis which grows exponentially with the number of particles.
+Such a task is often infeasible for large systems and one needs to resort to approximations.
 
-$$
-\overline{AB} = AB - :AB:.
-$$
+## Mean-field approximaton
 
-The expectation value of the interaction with respect to the $| 0 \rangle$ is:
+The first-order perturbative approximation to the interacting Hamiltonian is the Hartree-Fock approximation also known as the mean-field approximation.
+The mean-field approximates the quartic term $\hat{V}$ in {eq}`hamiltonian` as a sum of bilinear terms weighted by the expectation values the remaining operators:
+:::{math}
+:label: mf_approx
+\hat{V} \approx \hat{V}_{\text{MF}} \equiv \sum_{ij} v_{ij} \left[
+\braket{c_i^\dagger c_i} c_j^\dagger c_j - \braket{c_i^\dagger c_j} c_j^\dagger c_i \right]
+:::
+we neglect the superconducting pairing and constant offset terms.
+The expectation value terms  $\langle c_i^\dagger c_j \rangle$ are due to the ground-state density matrix and therefore act as an effective field acting on the system.
+The ground-state density matrix reads:
+:::{math}
+:label: density
+\rho_{ij} \equiv \braket{c_i^\dagger c_j } = \text{Tr}\left(e^{-\beta \left(\hat{H_0} + \hat{V}_{\text{MF}} - \mu \hat{N} \right)} c_i^\dagger c_j\right),
+:::
+where $\beta = 1/ (k_B T)$ is the inverse temperature, $\mu$ is the chemical potential, and $\hat{N} = \sum_i c_i^\dagger c_i$ is the number operator.
 
-$$
-\langle 0 | c_i^\dagger c_j^\dagger c_l c_k | 0 \rangle = \langle 0 | \overline{c_i^\dagger c_j^\dagger} \overline{c_l c_k} - \overline{c_i^\dagger c_l} \overline{c_j^\dagger c_k} + \overline{c_i^\dagger c_k} \overline{c_j^\dagger c_l}  | 0 \rangle
-$$
+## Finite tight-binding grid
 
-where we can forget about all the normal ordered states since those give zero acting on the unperturbed groundstate. To evaluate this further, we utilize the mean-field approximation:
+To simplify the mean-field Hamiltonian, we assume a finite, normalised orthogonal tight-binding grid defined by the single-particle basis states:
 
 $$
-A B \approx \langle A \rangle B + A \langle B \rangle - \langle A \rangle \langle B \rangle
+\ket{n} = c^\dagger_n\ket{\text{vac}}
 $$
 
-onto the contractions such that we get:
+where $\ket{\text{vac}}$ is the vacuum state.
+We project our mean-field interaction in {eq}`mf_approx` onto the tight-binding grid:
 
-$$
-\langle  c_i^\dagger c_j^\dagger c_l c_k \rangle \approx \langle c_i^\dagger c_j^\dagger \rangle \langle  c_l c_k \rangle + \langle c_i^\dagger c_k \rangle  \langle c_j^\dagger c_l \rangle - \langle c_i^\dagger c_l \rangle \langle c_j^\dagger c_k \rangle
-$$
+:::{math}
+:label: mf_finite
+V_{\text{MF}, nm} = \braket{n | \hat{V}_{\text{MF}} | m} =  \sum_{i} \rho_{ii} v_{in} \delta_{nm} - \rho_{mn} v_{mn},
+:::
+where $\delta_{nm}$ is the Kronecker delta function.
 
-note $\langle A B \rangle \approx \langle A \rangle  \langle B \rangle$ assuming mean-field.
+## Infinite tight-binding grid
 
-To consider excitations from the groundstate, we make use of the mean-field approximation defined above:
+In the limit of a translationally invariant system, the index $n$ that labels the basis states partitions into two independent variables: the unit cell internal degrees of freedom (spin, orbital, sublattice, etc.) and the position of the unit cell $R_n$:
 
 $$
-\begin{multline}
-c_i^\dagger c_j^\dagger c_l c_k \approx \\
-\langle c_i^\dagger c_j^\dagger \rangle c_l c_k + \langle c_i^\dagger c_k \rangle c_j^\dagger c_l - \langle c_i^\dagger c_l \rangle c_j^\dagger c_k + \langle c_l c_k \rangle c_i^\dagger c_j^\dagger - \langle c_j^\dagger c_k \rangle c_i^\dagger c_l + \langle c_j^\dagger c_l \rangle c_i^\dagger c_k + \\
-\langle c_i^\dagger c_j^\dagger \rangle \langle  c_l c_k \rangle + \langle c_i^\dagger c_k \rangle  \langle c_j^\dagger c_l \rangle - \langle c_i^\dagger c_l \rangle \langle c_j^\dagger c_k \rangle
-\end{multline}
+n \to n, R_n.
 $$
 
-Where we made use of the following operations:
+Because of the translationaly invariance, the physical properties of the system are independent of the absolute unit cell position $R_n$ and rather depend on the relative position between the two unit cells $R_{nm} = R_n - R_m$:
 
 $$
-:c_i^\dagger c_j^\dagger c_l c_k: \approx 0
-$$
-
-$$
-\overline{c_i^\dagger c_k} \overline{c_j^\dagger c_l} \approx \langle \overline{c_i^\dagger c_k} \rangle \overline{c_j^\dagger c_l} + \overline{c_i^\dagger c_k} \langle \overline{c_j^\dagger c_l} \rangle - \langle \overline{c_i^\dagger c_k} \rangle \langle \overline{c_j^\dagger c_i} \rangle =  \langle c_i^\dagger c_k \rangle \overline{c_j^\dagger c_l} + \overline{c_i^\dagger c_k} \langle c_j^\dagger c_l \rangle - \langle c_i^\dagger c_k \rangle \langle c_j^\dagger c_l \rangle
-$$
-
-$$
-\overline{c_i^\dagger c_k} :c_j^\dagger c_l: \approx \langle \overline{c_i^\dagger c_k} \rangle  :c_j^\dagger c_l: +  \overline{c_i^\dagger c_k} \langle :c_j^\dagger c_l: \rangle - \langle \overline{c_i^\dagger c_k} \rangle \langle :c_j^\dagger c_l: \rangle = \langle \overline{c_i^\dagger c_k} \rangle  :c_j^\dagger c_l:
-$$
-
-
-$$
-\langle \overline{c_i^\dagger c_k} \rangle  = \langle c_i^\dagger c_k - :c_i^\dagger c_k: \rangle  = \langle c_i^\dagger c_k \rangle
-$$
-
-
-Without any superconducting terms, the form simplifies to:
-
-$$
-\begin{multline}
-c_i^\dagger c_j^\dagger c_l c_k \approx
-\langle c_i^\dagger c_k \rangle c_j^\dagger c_l - \langle c_i^\dagger c_l \rangle c_j^\dagger c_k - \langle c_j^\dagger c_k \rangle c_i^\dagger c_l + \langle c_j^\dagger c_l \rangle c_i^\dagger c_k + \\
-\langle c_i^\dagger c_k \rangle  \langle c_j^\dagger c_l \rangle - \langle c_i^\dagger c_l \rangle \langle c_j^\dagger c_k \rangle
-\end{multline}
-$$
-
-## Finite size
-
-### Coulomb interaction
-
-We simplify the interaction term through the MF approximation to get:
-
-$$
-V = \frac{1}{2}\sum_{ijkl} v_{ijkl} c_i^{\dagger} c_j^{\dagger} c_l c_k
-\approx
-\frac12 \sum_{ijkl} v_{ijkl} \left[ \langle c_i^{\dagger} c_k \rangle c_j^{\dagger} c_l - \langle c_j^{\dagger} c_k \rangle c_i^{\dagger} c_l - \langle c_i^{\dagger} c_l \rangle c_j^{\dagger} c_k + \langle c_j^{\dagger} c_l \rangle c_i^{\dagger} c_k \right]
-$$
-(assuming no superconductivity)
-
-and an additional constant part:
-
-$$
-V_0 =  \frac{1}{2} \sum_{ijkl} v_{ijkl} \left(\langle c_j^{\dagger} c_l \rangle \langle c_i^{\dagger} c_k \rangle - \langle c_j^{\dagger} c_k \rangle \langle c_i^{\dagger} c_l \rangle \right).
-$$
-
-The interaction reads:
-
-$$
-v_{ijkl} = \iint w_{i}^*(r) w_{j}^*(r') V(r, r') w_{k}(r) w_l(r') dr dr' = \\
-\iint  V(|r - r'|) w_{i}^*(r)w_{k}(r) w_{j}^*(r')  w_l(r') dr dr'
-$$
-
-whereas $w_i$ is a wannier function on site i (and corresponding dof). Whenever one interchanges $i \to j, k \to l$, the Coulomb term is preserved $v_{ijkl} = v_{jilk}$
-
-To make things more understandable, we are also going to explicitly split up position and spin indices: $i \to i \times \sigma$. In this notation, the Coulomb integral reads:
-
-$$
-v_{ijkl}^{\sigma_i \sigma_j \sigma_k \sigma_l} =
-\iint V(|r - r'|) w_{i\times\sigma_i}^{*} (r)w_{k \times \sigma_k}(r) w_{j \times \sigma_j}^{*}(r')  w_{l\times \sigma_l}(r') dr dr' \delta_{\sigma_i \sigma_k} \delta_{\sigma_{j} \sigma_l}
-$$
-
-On a fine tight-binding model, we have:
-
-$$
-v_{ijkl}^{\sigma_i \sigma_j \sigma_k \sigma_l} = v_{ij} \delta_{ik} \delta_{jl} \delta_{\sigma_i \sigma_k} \delta_{\sigma_j \sigma_l}
-$$
-
-where $v_{ij} = V(r_i, r_j)$.
-
-We shall re-define $i$ index to absorb spin:
-
-$$
-\delta_{ik} \times \delta_{\sigma_{i} \sigma_{k}} \to \delta_{ik}
-$$
-
-in this notation the above reads:
-
-$$
-v_{ijkl} = v_{ij} \delta_{ik} \delta_{jl}
-$$
-
-The mean-field terms are:
-
+\rho_{mn} \to \rho_{mn}(R_{mn}).
 $$
-\langle c_i^{\dagger} c_j\rangle = \langle \Psi_F|c_i^{\dagger} c_j | \Psi_F \rangle
-$$
-
-whereas $|\Psi_F \rangle = \Pi_{i=0}^{N_F} b_i^\dagger |0\rangle$. To make sense of things, we need to transform between $c_i$ basis (position + internal dof basis) into the $b_i$ basis (eigenfunction of a given mean-field Hamiltonian):
-
-$$
-c_i^\dagger = \sum_{k} U_{ik} b_k^\dagger
-$$
-
-where
-
-$$
-U_{ik} = \langle{i|\psi_k} \rangle.
-$$
-
-That gives us:
-
-
-$$
-c_i^{\dagger} c_j = \sum_{k, l} U_{ik} U_{lj}^* b_k^\dagger b_{l}
-$$
-
-and its expectation value gives us the mean-field ... field $F_{ij}$:
-
-$$
-F_{ij} = \langle c_i^{\dagger} c_j\rangle =  \sum_{k, l} U_{ik} U_{lj}^* \langle \Psi_F| b_k^\dagger b_{l}| \Psi_F \rangle =  \sum_{k} U_{ik} U_{kj}^{*}
-$$
-
-whereas I assumed the indices for wavefunctions $k,l$ are ordered in terms of increasing eigenvalue. We pop that into the definition of the mean-field correction $V$:
-
-
-$$
-\begin{multline}
-V_{nm} = \frac12 \sum_{ijkl} v_{ijkl} \langle n| \left[ \langle c_i^{\dagger} c_k \rangle c_j^{\dagger} c_l - \langle c_j^{\dagger} c_k \rangle c_i^{\dagger} c_l - \langle c_i^{\dagger} c_l \rangle c_j^{\dagger} c_k + \langle c_j^{\dagger} c_l \rangle c_i^{\dagger} c_k \right] |m\rangle = \\
- \frac12 \sum_{ijkl} v_{ijkl} \left[ +\delta_{jn}\delta_{lm} F_{ik} -\delta_{in}\delta_{lm} F_{jk} -\delta_{jn}\delta_{km} F_{il} + \delta_{in}\delta_{km} F_{jl} \right] = \\
-\frac12 \left[ \sum_{ik} v_{inkm} F_{ik} - \sum_{jk} v_{njkm} F_{jk} - \sum_{il} v_{inml} F_{il} + \sum_{jl} v_{njml} F_{jl} \right] = \\
--\sum_{ij} F_{ij} \left(v_{inmj} - v_{injm} \right)
-\end{multline}
-$$
-
-where I used the $v_{ijkl} = v_{jilk}$ symmetry from Coulomb.
-
-For a tight-binding grid, the mean-field correction simplifies to:
-
-$$
-\begin{multline}
-V_{nm} = - \sum_{ij} F_{ij} \left(v_{inmj} - v_{injm} \right) = \\
--\sum_{ij}F_{ij} v_{in} \delta_{im} \delta_{nj} + \sum_{ij}F_{ij} v_{in} \delta_{ij} \delta_{nm} = \\
--F_{mn} v_{mn} + \sum_{i} F_{ii} v_{in} \delta_{nm}
-\end{multline}
-$$
-
-the first term is the exchange interaction whereas the second one is the direct interaction.
-
-Similarly, the constant offset term reads:
-
-$$
-\begin{multline}
-V_0 = \frac{1}{2} \sum_{ijkl} v_{ijkl} \left(F_{jl} F_{ik} - F_{jk} F_{il} \right) = \\
-\frac{1}{2} \sum_{ijkl} v_{ij} \delta_{ik} \delta_{jl} \left(F_{jl} F_{ik} - F_{jk} F_{il}\right) \\
-= \frac{1}{2} \sum_{ij} v_{ij} \left(F_{ii} F_{jj} - F_{ji} F_{ij}\right)
-\end{multline}
-$$
-
-where we identify the first term as the exchange (mixes indices) and the right one as the direct (diagonal in indices).
-
-## Translational Invariance
-
-The above assumed a finite tight-binding model - all $nm$-indices contain the position of all atoms (among other dof). In this section tho we want to consider an infinite system with translational invariance.
-
-To begin with we deconstruct a general matrix $O_{nm}$ into the cell degrees of freedom ($nm$) and the position of the the cell itself ($ij$):
-
-$$
-O_{nm} \to O^{ij}_{nm}
-$$
-
-and we will Fourier transform the upper indices into k-space:
-
-$$
-O_{mn}(k) = \sum_{ij} O_{nm}^{ij} e^{-i k (R_i-R_j)}
-$$
-
-where I assumed $O$ (and thus all operators I will consider here) is local and thus diagonal in k-space.
-
-Now lets rewrite our main result in the previous section using our new notation:
-
-$$
-V_{nm}^{ij} =-F_{mn}^{ij} v_{mn}^{ij} + \sum_{r,p} F_{pp}^{rr} v_{pn}^{rj} \delta_{nm} \delta^{ij}
-$$
-
-Lets first consider the second (direct) term. Lets express the corresponding $F$ term in k-space:
-
-$$
-F_{pp}^{rr} = \int e^{i k (R_r-R_r)} F_{pp}(k) dk = \int F_{pp}(k) dk
-$$
-
-Notice that in the final expression, there is no $rr$ dependence and thus this term is cell-periodic. Therefore, we shall redefine it as cell electron density $\rho$:
-$$
-F_{pp}^0 = F_{pp}(R = 0) = \int F_{pp}(k) dk
-$$
-
-Now since $\rho$ has no $r$ dependence, we can proceed with the sum:
-
-$$
-\sum_{r} v_{pn}^{rj} = \int v_{pn}(k) e^{ik R_j} \sum_{r} e^{-i k R_r} dk = \int v_{pn}(k) e^{ik R_j} \delta_{k, 0} dk = v_{pn}(0)
-$$
-
-We are finally ready to Fourier transform the main result. Invoking convolution theorem and the results above gives us:
-
-$$
-V_{nm}(k) = \sum_{p} F_{pp}^0 v_{pn}(0) \delta_{nm} -F_{mn}(k) \circledast v_{mn}(k) = V_n^D - F_{mn}(k) \circledast v_{mn}(k)
-$$
-
-which does make sense. The first term (direct) is a potential term coming from the mean-field and the second term (exchange) is purely responsible for the hopping.
-
-The constant offset is:
-$$
-V_0 = \frac{1}{2} \sum_{r,s} \rho_r v_{rs}(0) \rho_s- \\ \frac{1}{2} tr\left[\int_{BZ} \left(F \circledast v\right)(k) F(k) dk \right]
-$$
-
-## Short-Range interactions
-
-In the case of short-range interactions, it is much more convenient to go back to real space to both store objects and perform the operations. In real space the mean-field part of the Hamiltonian reads:
-
-$$
-V_{nm}(\mathbf{R}) = V_n^D \delta(\mathbf{R}) - F_{mn}(\mathbf{R}) v_{mn}(\mathbf{R})
-$$
-
-(the first term might need some prefactor from Fourier transformation)
 
-where $\mathbf{R}$ is a sequence of integers representing real-space unit cell indices.
+That allows us to re-write the mean-field interaction in {eq}`mf_finite` as:
 
-### Proposed Algorithm
-Given an initial Hamiltonian $H_0 (R)$ and the interaction term $v(R)$ in real-space, the mean-field algorithm is the following:
+:::{math}
+:label: mf_infinite
+V_{\text{MF}, nm} (R) =  \sum_{i} \rho_{ii} (0) v_{in} (0) \delta_{nm} \delta(R) - \rho_{mn}(R) v_{mn}(R),
+:::
 
-0. Start with a mean-field guess in real-space: $V(R)$.
-1. Fourier transform tight-binding model and the mean-field in real space to a given k-grid: $H_0(R) + V(R) \to H_0(k) + V(k)$
-2. Diagonalize and evaluate the density matrix: $H_0(k) + V(k) \to F(k)$
-3. Fourier transform the density matrix back to real-space: $F(k) \to F(R)$
-4. Evaluate the new mean-field Hamiltonian $V(R)$ according to the equation above.
-5. Evaluate self-consistency metric (could be based either on $V(R/k)$ or $F(R/k)$). Based on that, either stop or go back to 1 with a modified $V(R)$ starting guess.
+where now indices $i, n, m$ label the internal degrees of freedom of the unit cell and $R$ is the relative position between the two unit cells in terms of the lattice vectors.
diff --git a/pymf/__init__.py b/pymf/__init__.py
index a0f2b6e8598cdbc623b27aaeffdd5555fbcb8f96..bb870d26385e27f27b87fac4fbfd4b8f5b5b22fc 100644
--- a/pymf/__init__.py
+++ b/pymf/__init__.py
@@ -6,10 +6,30 @@ except ImportError:
     __version__ = "unknown"
     __version_tuple__ = (0, 0, "unknown", "unknown")
 
-from .mf import construct_density_matrix
+from .mf import (
+    construct_density_matrix,
+    meanfield,
+)
+from .solvers import solver
+from .model import Model
+from .observables import expectation_value
+from .tb.tb import add_tb, scale_tb
+from .tb.transforms import tb_to_kgrid, kgrid_to_tb
+from .tb.utils import generate_guess, calculate_fermi_energy
+
 
 __all__ = [
+    "solver",
+    "Model",
+    "expectation_value",
+    "add_tb",
+    "scale_tb",
+    "generate_guess",
+    "calculate_fermi_energy",
     "construct_density_matrix",
+    "meanfield",
+    "tb_to_kgrid",
+    "kgrid_to_tb",
     "__version__",
     "__version_tuple__",
 ]
diff --git a/pymf/kwant_helper/utils.py b/pymf/kwant_helper/utils.py
index debc0d99ac7ef73ef783418d17c1ebdaff186d78..650b636621b050aa592a29a9cf90e6d8f1fcf618 100644
--- a/pymf/kwant_helper/utils.py
+++ b/pymf/kwant_helper/utils.py
@@ -1,29 +1,36 @@
 import inspect
 from copy import copy
 from itertools import product
+from typing import Callable
 
-import kwant
 import numpy as np
 from scipy.sparse import coo_array
+import kwant
+import kwant.lattice
+import kwant.builder
 
+from pymf.tb.tb import _tb_type
 
-def builder_to_tb(builder, params={}, return_data=False):
-    """Construct a tight-binding model dictionary from a `kwant.Builder`.
+
+def builder_to_tb(
+    builder: kwant.builder.Builder, params: dict = {}, return_data: bool = False
+) -> _tb_type:
+    """Construct a tight-binding dictionary from a `kwant.builder.Builder` system.
 
     Parameters
     ----------
-    builder : `kwant.Builder`
-        Either builder for non-interacting system or interacting Hamiltonian.
-    params : dict
-        Dictionary of parameters to evaluate the Hamiltonian.
-    return_data : bool
+    builder :
+       system to convert to tight-binding dictionary.
+    params :
+        Dictionary of parameters to evaluate the builder on.
+    return_data :
         Returns dictionary with sites and number of orbitals per site.
 
     Returns
     -------
-    h_0 : dict
-        Tight-binding model of non-interacting systems.
-    data : dict
+    :
+        Tight-binding dictionary that corresponds to the builder.
+    :
         Data with sites and number of orbitals. Only if `return_data=True`.
     """
     builder = copy(builder)
@@ -124,28 +131,38 @@ def builder_to_tb(builder, params={}, return_data=False):
         return h_0
 
 
-def build_interacting_syst(builder, lattice, func_onsite, func_hop, max_neighbor=1):
-    """Construct an auxiliary `kwant` system to build Hamiltonian matrix.
+def build_interacting_syst(
+    builder: kwant.builder.Builder,
+    lattice: kwant.lattice.Polyatomic,
+    func_onsite: Callable,
+    func_hop: Callable,
+    max_neighbor: int = 1,
+) -> kwant.builder.Builder:
+    """
+    Construct an auxiliary `kwant` system that encodes the interactions.
 
     Parameters
     ----------
-    builder : `kwant.Builder`
-        Non-interacting `kwant` system.
-    func_onsite : function
-        Onsite function.
-    func_hop : function
-        Hopping function.
-    max_neighbor : int
-        Maximal nearest-neighbor order.
+    builder :
+        Non-interacting `kwant.builder.Builder` system.
+    lattice :
+        Lattice of the system.
+    func_onsite :
+        Onsite interactions function.
+    func_hop :
+        Hopping/inter unit cell interactions function.
+    max_neighbor :
+        The maximal number of neighbouring unit cells (along a lattice vector)
+        connected by interaction. Interaction goes to zero after this distance.
 
     Returns
     -------
-    int_builder : `kwant.Builder`
-        Dummy `kwant.Builder` to compute interaction matrix.
+    :
+        Auxiliary `kwant.builder.Builder` that encodes the interactions of the system.
     """
-    # lattice_info = list(builder.sites())[0][0]
-    # lattice = kwant.lattice.general(lattice_info.prim_vecs, norbs=lattice_info.norbs)
-    int_builder = kwant.Builder(kwant.TranslationalSymmetry(*builder.symmetry.periods))
+    int_builder = kwant.builder.Builder(
+        kwant.lattice.TranslationalSymmetry(*builder.symmetry.periods)
+    )
     int_builder[builder.sites()] = func_onsite
     for neighbors in range(max_neighbor):
         int_builder[lattice.neighbors(neighbors + 1)] = func_hop
diff --git a/pymf/mf.py b/pymf/mf.py
index d04d8f3da1e14adc20c3cb088547d5fa99ab279a..fd2c33893bf919643adb67b29b4a1ca607003f0d 100644
--- a/pymf/mf.py
+++ b/pymf/mf.py
@@ -1,80 +1,97 @@
 import numpy as np
-from scipy.fftpack import ifftn
+from typing import Tuple
 
-from pymf.tb.tb import add_tb
-from pymf.tb.transforms import ifftn_to_tb, tb_to_khamvector
+from pymf.tb.tb import add_tb, _tb_type
+from pymf.tb.transforms import tb_to_kgrid, kgrid_to_tb
 
 
-def construct_density_matrix_kgrid(kham, filling):
+def construct_density_matrix_kgrid(
+    kham: np.ndarray, filling: float
+) -> Tuple[np.ndarray, float]:
     """Calculate density matrix on a k-space grid.
 
     Parameters
     ----------
-    kham : npndarray
-         Hamiltonian in k-space of shape (len(dim), norbs, norbs)
-    filling : float
+    kham :
+        Hamiltonian from which to construct the density matrix.
+        The hamiltonian is sampled on a grid of k-points and has shape (nk, nk, ..., ndof, ndof),
+        where ndof is number of internal degrees of freedom.
+    filling :
         Number of particles in a unit cell.
+        Used to determine the Fermi level.
 
     Returns
     -------
-     np.ndarray, float
-         Density matrix in k-space and Fermi energy.
+    :
+        Density matrix on a k-space grid with shape (nk, nk, ..., ndof, ndof) and Fermi energy.
     """
     vals, vecs = np.linalg.eigh(kham)
-    fermi = fermi_on_grid(vals, filling)
+    fermi = fermi_on_kgrid(vals, filling)
     unocc_vals = vals > fermi
     occ_vecs = vecs
     np.moveaxis(occ_vecs, -1, -2)[unocc_vals, :] = 0
-    rho_krid = occ_vecs @ np.moveaxis(occ_vecs, -1, -2).conj()
-    return rho_krid, fermi
+    density_matrix_krid = occ_vecs @ np.moveaxis(occ_vecs, -1, -2).conj()
+    return density_matrix_krid, fermi
 
 
-def construct_density_matrix(h, filling, nk):
-    """Compute the density matrix in real-space tight-binding format.
+def construct_density_matrix(
+    h: _tb_type, filling: float, nk: int
+) -> Tuple[_tb_type, float]:
+    """Compute the real-space density matrix tight-binding dictionary.
 
     Parameters
     ----------
-    h : dict
-        Tight-binding model.
-    filling : float
-        Filling of the system.
-    nk : int
-        Number of k-points in the grid.
+    h :
+        Hamiltonian tight-binding dictionary from which to construct the density matrix.
+    filling :
+        Number of particles in a unit cell.
+        Used to determine the Fermi level.
+    nk :
+        Number of k-points in a grid to sample the Brillouin zone along each dimension.
+        If the system is 0-dimensional (finite), this parameter is ignored.
 
     Returns
     -------
-    (dict, float)
-        Density matrix in real-space tight-binding format and Fermi energy.
+    :
+        Density matrix tight-binding dictionary and Fermi energy.
     """
     ndim = len(list(h)[0])
     if ndim > 0:
-        kham = tb_to_khamvector(h, nk=nk)
-        rho_grid, fermi = construct_density_matrix_kgrid(kham, filling)
+        kham = tb_to_kgrid(h, nk=nk)
+        density_matrix_krid, fermi = construct_density_matrix_kgrid(kham, filling)
         return (
-            ifftn_to_tb(ifftn(rho_grid, axes=np.arange(ndim))),
+            kgrid_to_tb(density_matrix_krid),
             fermi,
         )
     else:
-        rho, fermi = construct_density_matrix_kgrid(h[()], filling)
-        return {(): rho}, fermi
+        density_matrix, fermi = construct_density_matrix_kgrid(h[()], filling)
+        return {(): density_matrix}, fermi
 
 
-def meanfield(density_matrix_tb, h_int):
-    """Compute the mean-field in k-space.
+def meanfield(density_matrix: _tb_type, h_int: _tb_type) -> _tb_type:
+    """Compute the mean-field correction from the density matrix.
 
     Parameters
     ----------
-    density_matrix : dict
-        Density matrix in real-space tight-binding format.
-    h_int : dict
-        Interaction tb model.
-
+    density_matrix :
+        Density matrix tight-binding dictionary.
+    h_int :
+        Interaction hermitian Hamiltonian tight-binding dictionary.
     Returns
     -------
-    dict
-        Mean-field tb model.
+    :
+        Mean-field correction tight-binding dictionary.
+
+    Notes
+    -----
+
+    The interaction h_int must be of density-density type.
+    For example in 1D system with ndof internal degrees of freedom,
+    h_int[(2,)] = U * np.ones((ndof, ndof)) is a Coulomb repulsion interaction
+    with strength U between unit cells separated by 2 lattice vectors, where
+    the interaction is the same between all internal degrees of freedom.
     """
-    n = len(list(density_matrix_tb)[0])
+    n = len(list(density_matrix)[0])
     local_key = tuple(np.zeros((n,), dtype=int))
 
     direct = {
@@ -82,7 +99,7 @@ def meanfield(density_matrix_tb, h_int):
             np.array(
                 [
                     np.diag(
-                        np.einsum("pp,pn->n", density_matrix_tb[local_key], h_int[vec])
+                        np.einsum("pp,pn->n", density_matrix[local_key], h_int[vec])
                     )
                     for vec in frozenset(h_int)
                 ]
@@ -92,26 +109,26 @@ def meanfield(density_matrix_tb, h_int):
     }
 
     exchange = {
-        vec: -1 * h_int.get(vec, 0) * density_matrix_tb[vec]  # / (2 * np.pi)#**2
-        for vec in frozenset(h_int)
+        vec: -1 * h_int.get(vec, 0) * density_matrix[vec] for vec in frozenset(h_int)
     }
     return add_tb(direct, exchange)
 
 
-def fermi_on_grid(vals, filling):
+def fermi_on_kgrid(vals: np.ndarray, filling: float) -> float:
     """Compute the Fermi energy on a grid of k-points.
 
     Parameters
     ----------
-    vals : ndarray
-        Eigenvalues of the hamiltonian in k-space of shape (len(dim), norbs, norbs)
-    filling : int
-         Number of particles in a unit cell.
-
+    vals :
+        Eigenvalues of a hamiltonian sampled on a k-point grid with shape (nk, nk, ..., ndof, ndof),
+        where ndof is number of internal degrees of freedom.
+    filling :
+        Number of particles in a unit cell.
+        Used to determine the Fermi level.
     Returns
     -------
-    fermi_energy : float
-         Fermi energy
+    :
+        Fermi energy
     """
     norbs = vals.shape[-1]
     vals_flat = np.sort(vals.flatten())
diff --git a/pymf/model.py b/pymf/model.py
index 6a61b2d4cda86bc770691c23b970c238f6ca3928..00a7fd4153b0eede6498caae7223b0895583cf2f 100644
--- a/pymf/model.py
+++ b/pymf/model.py
@@ -4,7 +4,7 @@ from pymf.mf import (
     construct_density_matrix,
     meanfield,
 )
-from pymf.tb.tb import add_tb
+from pymf.tb.tb import add_tb, _tb_type
 
 
 def _check_hermiticity(h):
@@ -12,24 +12,52 @@ def _check_hermiticity(h):
         op_vector = tuple(-1 * np.array(vector))
         op_vector = tuple(-1 * np.array(vector))
         if not np.allclose(h[vector], h[op_vector].conj().T):
-            raise ValueError("Hamiltonian is not Hermitian.")
+            raise ValueError("Tight-binding dictionary must be hermitian.")
 
 
 def _tb_type_check(tb):
     for count, key in enumerate(tb):
         if not isinstance(tb[key], np.ndarray):
-            raise ValueError("Inputted dictionary values are not np.ndarray's")
+            raise ValueError(
+                "Values of the tight-binding dictionary must be numpy arrays"
+            )
         shape = tb[key].shape
         if count == 0:
             size = shape[0]
         if not len(shape) == 2:
-            raise ValueError("Inputted dictionary values are not square matrices")
+            raise ValueError(
+                "Values of the tight-binding dictionary must be square matrices"
+            )
         if not size == shape[0]:
-            raise ValueError("Inputted dictionary elements shapes are not consistent")
+            raise ValueError(
+                "Values of the tight-binding dictionary must have consistent shape"
+            )
 
 
 class Model:
-    def __init__(self, h_0, h_int, filling):
+    """
+    Data class which defines the interacting tight-binding problem.
+
+    Parameters
+    ----------
+    h_0 :
+        Non-interacting hermitian Hamiltonian tight-binding dictionary.
+    h_int :
+        Interaction hermitian Hamiltonian tight-binding dictionary.
+    filling :
+        Number of particles in a unit cell.
+        Used to determine the Fermi level.
+
+    Notes
+    -----
+    The interaction h_int must be of density-density type.
+    For example in 1D system with ndof internal degrees of freedom,
+    h_int[(2,)] = U * np.ones((ndof, ndof)) is a Coulomb repulsion interaction
+    with strength U between unit cells separated by 2 lattice vectors, where
+    the interaction is the same between all internal degrees of freedom.
+    """
+
+    def __init__(self, h_0: _tb_type, h_int: _tb_type, filling: float) -> None:
         _tb_type_check(h_0)
         self.h_0 = h_0
         _tb_type_check(h_int)
@@ -42,31 +70,32 @@ class Model:
 
         _first_key = list(h_0)[0]
         self._ndim = len(_first_key)
-        self._size = h_0[_first_key].shape[0]
+        self._ndof = h_0[_first_key].shape[0]
         self._local_key = tuple(np.zeros((self._ndim,), dtype=int))
 
         _check_hermiticity(h_0)
         _check_hermiticity(h_int)
 
-    def mfield(self, mf_tb, nk=200):  # method or standalone?
-        """Compute single mean field iteration.
+    def mfield(self, mf: _tb_type, nk: int = 20) -> _tb_type:
+        """Computes a new mean-field correction from a given one.
 
         Parameters
         ----------
-        mf_tb : dict
-            Mean-field tight-binding model.
-        nk : int
-            Number of k-points in the grid.
+        mf :
+            Initial mean-field correction tight-binding dictionary.
+        nk :
+            Number of k-points in a grid to sample the Brillouin zone along each dimension.
+            If the system is 0-dimensional (finite), this parameter is ignored.
 
         Returns
         -------
-        dict
-            New mean-field tight-binding model.
+        :
+            new mean-field correction tight-binding dictionary.
         """
         rho, fermi_energy = construct_density_matrix(
-            add_tb(self.h_0, mf_tb), self.filling, nk
+            add_tb(self.h_0, mf), self.filling, nk
         )
         return add_tb(
             meanfield(rho, self.h_int),
-            {self._local_key: -fermi_energy * np.eye(self._size)},
+            {self._local_key: -fermi_energy * np.eye(self._ndof)},
         )
diff --git a/pymf/observables.py b/pymf/observables.py
index bf770349e11798bbe4d86dae5f3f076cf86043f1..bd47485a9299786551bd22455600c31bb89a6d11 100644
--- a/pymf/observables.py
+++ b/pymf/observables.py
@@ -1,19 +1,21 @@
 import numpy as np
 
+from pymf.tb.tb import _tb_type
 
-def expectation_value(density_matrix, observable):
+
+def expectation_value(density_matrix: _tb_type, observable: _tb_type) -> complex:
     """Compute the expectation value of an observable with respect to a density matrix.
 
     Parameters
     ----------
-    density_matrix : dict
-        Density matrix in tight-binding format.
-    observable : dict
-        Observable in tight-binding format.
+    density_matrix :
+        Density matrix tight-binding dictionary.
+    observable :
+        Observable tight-binding dictionary.
 
     Returns
     -------
-    complex
+    :
         Expectation value.
     """
     return np.sum(
diff --git a/pymf/params/param_transforms.py b/pymf/params/param_transforms.py
index 98b68e2485597b9479ab30755e76b37312e15968..2095e345634d7588dc6eed6a90689fc7f7e6d756 100644
--- a/pymf/params/param_transforms.py
+++ b/pymf/params/param_transforms.py
@@ -1,18 +1,20 @@
 import numpy as np
 
+from pymf.tb.tb import _tb_type
 
-def tb_to_flat(tb):
-    """Convert a hermitian tight-binding dictionary to flat complex matrix.
+
+def tb_to_flat(tb: _tb_type) -> np.ndarray:
+    """Parametrise a hermitian tight-binding dictionary by a flat complex vector.
 
     Parameters
     ----------
-    tb : dict with nd-array elements
+    tb :
         Hermitian tigh-binding dictionary
 
     Returns
     -------
-    flat : complex 1d numpy array
-        Flattened tight-binding dictionary
+    :
+        1D complex array that parametrises the tight-binding dictionary.
     """
     if len(list(tb)[0]) == 0:
         matrix = np.array(list(tb.values()))
@@ -23,34 +25,39 @@ def tb_to_flat(tb):
     return sorted_vals[:N].flatten()
 
 
-def flat_to_tb(flat, shape, tb_keys):
+def flat_to_tb(
+    tb_param_complex: np.ndarray,
+    ndof: int,
+    tb_keys: list[tuple[None] | tuple[int, ...]],
+) -> _tb_type:
     """Reverse operation to `tb_to_flat`.
 
     It takes a flat complex 1d array and return the tight-binding dictionary.
 
     Parameters
     ----------
-    flat : dict with nd-array elements
-        Hermitian tigh-binding dictionary
-    shape : tuple
-        shape of the tb elements
-    tb_keys : iterable
-        original tb key elements
+    tb_param_complex :
+        1d complex array that parametrises the tb model.
+    ndof :
+        Number internal degrees of freedom within the unit cell.
+    tb_keys :
+        List of keys of the tight-binding dictionary.
 
     Returns
     -------
-    tb : dict
+    tb :
         tight-binding dictionary
     """
+    shape = (len(tb_keys), ndof, ndof)
     if len(tb_keys[0]) == 0:
         matrix = np.zeros((shape[-1], shape[-2]), dtype=complex)
-        matrix[np.triu_indices(shape[-1])] = flat
+        matrix[np.triu_indices(shape[-1])] = tb_param_complex
         matrix += matrix.conj().T
         matrix[np.diag_indices(shape[-1])] /= 2
         return {(): matrix}
     matrix = np.zeros(shape, dtype=complex)
     N = len(tb_keys) // 2 + 1
-    matrix[:N] = flat.reshape(N, *shape[1:])
+    matrix[:N] = tb_param_complex.reshape(N, *shape[1:])
     matrix[N:] = np.moveaxis(matrix[-(N + 1) :: -1], -1, -2).conj()
 
     tb_keys = np.array(list(tb_keys))
@@ -59,17 +66,22 @@ def flat_to_tb(flat, shape, tb_keys):
     return tb
 
 
-def complex_to_real(z):
-    """Split real and imaginary parts of a complex array.
+def complex_to_real(z: np.ndarray) -> np.ndarray:
+    """Split and concatenate real and imaginary parts of a complex array.
 
     Parameters
     ----------
-    z : array
+    z :
         Complex array.
+
+    Returns
+    -------
+    :
+        Real array that concatenates the real and imaginary parts of the input array.
     """
     return np.concatenate((np.real(z), np.imag(z)))
 
 
-def real_to_complex(z):
+def real_to_complex(z: np.ndarray) -> np.ndarray:
     """Undo `complex_to_real`."""
     return z[: len(z) // 2] + 1j * z[len(z) // 2 :]
diff --git a/pymf/params/rparams.py b/pymf/params/rparams.py
index bb78ed086ef08366f3d6c91f90ea0f91d838793b..dcb0479ec413f675732d12497b7a0c9a2331d221 100644
--- a/pymf/params/rparams.py
+++ b/pymf/params/rparams.py
@@ -1,44 +1,48 @@
+import numpy as np
+
 from pymf.params.param_transforms import (
     complex_to_real,
     flat_to_tb,
     real_to_complex,
     tb_to_flat,
 )
+from pymf.tb.tb import _tb_type
 
 
-def tb_to_rparams(tb):
-    """Convert a mean-field tight-binding model to a set of real parameters.
+def tb_to_rparams(tb: _tb_type) -> np.ndarray:
+    """Parametrise a hermitian tight-binding dictionary by a real vector.
 
     Parameters
     ----------
-    tb : dict
-        Mean-field tight-binding model.
+    tb :
+        tight-binding dictionary.
 
     Returns
     -------
-    dict
-        Real parameters.
+    :
+        1D real vector that parametrises the tight-binding dictionary.
     """
-    return complex_to_real(tb_to_flat(tb))  # placeholder for now
+    return complex_to_real(tb_to_flat(tb))
 
 
-def rparams_to_tb(r_params, key_list, size):
-    """Extract mean-field tight-binding model from a set of real parameters.
+def rparams_to_tb(
+    tb_params: np.ndarray, tb_keys: list[tuple[None] | tuple[int, ...]], ndof: int
+) -> _tb_type:
+    """Extract a hermitian tight-binding dictionary from a real vector parametrisation.
 
     Parameters
     ----------
-    r_params : dict
-        Real parameters.
-    key_list : list
-        List of the keys of the mean-field tight-binding model, meaning all the
-        hoppings.
-    size : tuple
-        Shape of the mean-field tight-binding model.
+    tb_params :
+        1D real array that parametrises the tight-binding dictionary.
+    tb_keys :
+        List of keys of the tight-binding dictionary.
+    ndof :
+        Number internal degrees of freedom within the unit cell.
 
     Returns
     -------
-    dict
-        Mean-field tight-binding model.
+    :
+        Tight-biding dictionary.
     """
-    flat_matrix = real_to_complex(r_params)
-    return flat_to_tb(flat_matrix, (len(key_list), size, size), key_list)
+    flat_matrix = real_to_complex(tb_params)
+    return flat_to_tb(flat_matrix, ndof, tb_keys)
diff --git a/pymf/solvers.py b/pymf/solvers.py
index 9099f39749878dfac9374f19291593d6262e9814..520941232e403673ae99b998c21ed896f4c7efa2 100644
--- a/pymf/solvers.py
+++ b/pymf/solvers.py
@@ -1,65 +1,76 @@
 from functools import partial
-
 import numpy as np
 import scipy
+from typing import Optional, Callable
 
 from pymf.params.rparams import rparams_to_tb, tb_to_rparams
-from pymf.tb.tb import add_tb
+from pymf.tb.tb import add_tb, _tb_type
+from pymf.model import Model
 from pymf.tb.utils import calculate_fermi_energy
 
 
-def cost(mf_param, Model, nk=100):
-    """Define the cost function for fixed point iteration.
+def cost(mf_param: np.ndarray, model: Model, nk: int = 20) -> np.ndarray:
+    """Defines the cost function for root solver.
 
-    The cost function is the difference between the input mean-field real space
-    parametrisation and a new mean-field.
+    The cost function is the difference between the computed and inputted mean-field.
 
     Parameters
     ----------
-    mf_param : numpy.array
-        The mean-field real space parametrisation.
-    Model : Model
-        The model object.
-    nk : int, optional
-        The number of k-points to use in the grid. The default is 100.
+    mf_param :
+        1D real array that parametrises the mean-field correction.
+    Model :
+        Interacting tight-binding problem definition.
+    nk :
+        Number of k-points in a grid to sample the Brillouin zone along each dimension.
+        If the system is 0-dimensional (finite), this parameter is ignored.
+
+    Returns
+    -------
+    :
+        1D real array that is the difference between the computed and inputted mean-field
+        parametrisations
     """
-    shape = Model._size
-    mf_tb = rparams_to_tb(mf_param, list(Model.h_int), shape)
-    mf_tb_new = Model.mfield(mf_tb, nk=nk)
-    mf_params_new = tb_to_rparams(mf_tb_new)
+    shape = model._ndof
+    mf = rparams_to_tb(mf_param, list(model.h_int), shape)
+    mf_new = model.mfield(mf, nk=nk)
+    mf_params_new = tb_to_rparams(mf_new)
     return mf_params_new - mf_param
 
 
 def solver(
-    Model, mf_guess, nk=100, optimizer=scipy.optimize.anderson, optimizer_kwargs={}
-):
-    """Solve the mean-field self-consistent equation.
+    model: Model,
+    mf_guess: np.ndarray,
+    nk: int = 20,
+    optimizer: Optional[Callable] = scipy.optimize.anderson,
+    optimizer_kwargs: Optional[dict[str, str]] = {"M": 0},
+) -> _tb_type:
+    """Solve for the mean-field correction through self-consistent root finding.
 
     Parameters
     ----------
-    Model : Model
-        The model object.
-    mf_guess : numpy.array
-        The initial guess for the mean-field tight-binding model.
-    nk : int, optional
-        The number of k-points to use in the grid. The default is 100. In the
-        0-dimensional case, this parameter is ignored.
-    optimizer : scipy.optimize, optional
-        The optimizer to use to solve for fixed-points. The default is
-        scipy.optimize.anderson.
-    optimizer_kwargs : dict, optional
-        The keyword arguments to pass to the optimizer. The default is {}.
+    model :
+        Interacting tight-binding problem definition.
+    mf_guess :
+        The initial guess for the mean-field correction in the tight-binding dictionary format.
+    nk :
+        Number of k-points in a grid to sample the Brillouin zone along each dimension.
+        If the system is 0-dimensional (finite), this parameter is ignored.
+    optimizer :
+        The solver used to solve the fixed point iteration.
+        Default uses `scipy.optimize.anderson`.
+    optimizer_kwargs :
+        The keyword arguments to pass to the optimizer.
 
     Returns
     -------
-    result : numpy.array
-        The mean-field tight-binding model.
+    :
+        Mean-field correction solution in the tight-binding dictionary format.
     """
-    shape = Model._size
+    shape = model._ndof
     mf_params = tb_to_rparams(mf_guess)
-    f = partial(cost, Model=Model, nk=nk)
+    f = partial(cost, model=model, nk=nk)
     result = rparams_to_tb(
-        optimizer(f, mf_params, **optimizer_kwargs), list(Model.h_int), shape
+        optimizer(f, mf_params, **optimizer_kwargs), list(model.h_int), shape
     )
-    fermi = calculate_fermi_energy(add_tb(Model.h_0, result), Model.filling, nk=nk)
-    return add_tb(result, {Model._local_key: -fermi * np.eye(Model._size)})
+    fermi = calculate_fermi_energy(add_tb(model.h_0, result), model.filling, nk=nk)
+    return add_tb(result, {model._local_key: -fermi * np.eye(model._ndof)})
diff --git a/pymf/tb/tb.py b/pymf/tb/tb.py
index a059323b98b434dd41b3a8b413442fa21c95b88d..445a99b483292f885368842b52a240c9e86f1ab3 100644
--- a/pymf/tb/tb.py
+++ b/pymf/tb/tb.py
@@ -1,43 +1,45 @@
 import numpy as np
 
+_tb_type = dict[tuple[()] | tuple[int, ...], np.ndarray]
 
-def add_tb(tb1, tb2):
-    """Add up two tight-binding models together.
+
+def add_tb(tb1: _tb_type, tb2: _tb_type) -> _tb_type:
+    """Add up two tight-binding dictionaries together.
 
     Parameters
     ----------
-    tb1 : dict
-        Tight-binding model.
-    tb2 : dict
-        Tight-binding model.
+    tb1 :
+        Tight-binding dictionary.
+    tb2 :
+        Tight-binding dictionary.
 
     Returns
     -------
-    dict
-        Sum of the two tight-binding models.
+    :
+        Sum of the two tight-binding dictionaries.
     """
     return {k: tb1.get(k, 0) + tb2.get(k, 0) for k in frozenset(tb1) | frozenset(tb2)}
 
 
-def scale_tb(tb, scale):
-    """Scale a tight-binding model.
+def scale_tb(tb: _tb_type, scale: float) -> _tb_type:
+    """Scale a tight-binding dictionary by a constant.
 
     Parameters
     ----------
-    tb : dict
-        Tight-binding model.
-    scale : float
-        The scaling factor.
+    tb :
+        Tight-binding dictionary.
+    scale :
+        Constant to scale the tight-binding dictionary by.
 
     Returns
     -------
-    dict
-        Scaled tight-binding model.
+    :
+        Scaled tight-binding dictionary.
     """
     return {k: tb.get(k, 0) * scale for k in frozenset(tb)}
 
 
-def compare_dicts(dict1, dict2, atol=1e-10):
+def compare_dicts(dict1: dict, dict2: dict, atol: float = 1e-10) -> None:
     """Compare two dictionaries."""
     for key in frozenset(dict1) | frozenset(dict2):
         assert np.allclose(dict1[key], dict2[key], atol=atol)
diff --git a/pymf/tb/transforms.py b/pymf/tb/transforms.py
index 4befee2240393754d58623f0095745e3de6d7efb..6d7a8715bdc01b64f18710ac89066989d1820f2c 100644
--- a/pymf/tb/transforms.py
+++ b/pymf/tb/transforms.py
@@ -1,29 +1,30 @@
 import itertools
 import numpy as np
+from scipy.fftpack import ifftn
 
+from pymf.tb.tb import _tb_type
 
-def tb_to_khamvector(tb, nk, ks=None):
-    """Real-space tight-binding model to hamiltonian on k-space grid.
+
+def tb_to_kgrid(tb: _tb_type, nk: int) -> np.ndarray:
+    """Evaluate a tight-binding dictionary on a k-space grid.
 
     Parameters
     ----------
-    tb : dict
-        A dictionary with real-space vectors as keys and complex np.arrays as values.
-    nk : int
-        Number of k-points along each direction.
-    ks : 1D-array
-        Set of k-points. Repeated for all directions.
+    tb :
+        Tight-binding dictionary to evaluate on a k-space grid.
+    nk :
+        Number of k-points in a grid to sample the Brillouin zone along each dimension.
+        If the system is 0-dimensional (finite), this parameter is ignored.
 
     Returns
     -------
-    ndarray
-        Hamiltonian evaluated on a k-point grid.
-
+    :
+        Tight-binding dictionary evaluated on a k-space grid.
+        Has shape (nk, nk, ..., ndof, ndof), where ndof is number of internal degrees of freedom.
     """
     ndim = len(list(tb)[0])
-    if ks is None:
-        ks = np.linspace(-np.pi, np.pi, nk, endpoint=False)
-        ks = np.concatenate((ks[nk // 2 :], ks[: nk // 2]), axis=0)  # shift for ifft
+    ks = np.linspace(-np.pi, np.pi, nk, endpoint=False)
+    ks = np.concatenate((ks[nk // 2 :], ks[: nk // 2]), axis=0)  # shift for ifft
     kgrid = np.meshgrid(*([ks] * ndim), indexing="ij")
 
     num_keys = len(list(tb.keys()))
@@ -37,66 +38,41 @@ def tb_to_khamvector(tb, nk, ks=None):
     return np.sum(tb_array * k_dependency, axis=0)
 
 
-def ifftn_to_tb(ifft_array):
-    """Convert an array from ifftn to a tight-binding model format.
+def kgrid_to_tb(kgrid_array: np.ndarray) -> _tb_type:
+    """
+    Convert a k-space grid array to a tight-binding dictionary.
 
     Parameters
     ----------
-    ifft_array : ndarray
-        An array obtained from ifftn.
-
+    kgrid_array :
+        K-space grid array to convert to a tight-binding dictionary.
+        The array should be of shape (nk, nk, ..., ndof, ndof),
+        where ndof is number of internal degrees of freedom.
     Returns
     -------
-    dict
-        A dictionary with real-space vectors as keys and complex np.arrays as values.
+    :
+        Tight-binding dictionary.
     """
-    size = ifft_array.shape[:-2]
-
-    keys = [np.arange(-size[0] // 2 + 1, size[0] // 2) for i in range(len(size))]
-    keys = itertools.product(*keys)
-    return {tuple(k): ifft_array[tuple(k)] for k in keys}
+    ndim = len(kgrid_array.shape) - 2
+    return ifftn_to_tb(ifftn(kgrid_array, axes=np.arange(ndim)))
 
 
-def kham_to_tb(kham, hopping_vecs, ks=None):
-    """Extract hopping matrices from Bloch Hamiltonian.
+def ifftn_to_tb(ifft_array: np.ndarray) -> _tb_type:
+    """
+    Convert the result of `scipy.fft.ifftn` to a tight-binding dictionary.
 
     Parameters
     ----------
-    kham : nd-array
-        Bloch Hamiltonian matrix kham[k_x, ..., k_n, i, j]
-    hopping_vecs : list
-        List of hopping vectors, will be the keys to the tb.
-    ks : 1D-array
-        Set of k-points. Repeated for all directions. If the system is finite,
-        ks=None`.
-
+    ifft_array :
+        Result of `scipy.fft.ifftn` to convert to a tight-binding dictionary.
+        The input to `scipy.fft.ifftn` should be from `tb_to_khamvector`.
     Returns
     -------
-    scf_model : dict
-        Tight-binding model of Hartree-Fock solution.
+    :
+        Tight-binding dictionary.
     """
-    if ks is not None:
-        ndim = len(kham.shape) - 2
-        dk = np.diff(ks)[0]
-        nk = len(ks)
-        k_pts = np.tile(ks, ndim).reshape(ndim, nk)
-        k_grid = np.array(np.meshgrid(*k_pts))
-        k_grid = k_grid.reshape(k_grid.shape[0], np.prod(k_grid.shape[1:]))
-        kham = kham.reshape(np.prod(kham.shape[:ndim]), *kham.shape[-2:])
-
-        hopps = (
-            np.einsum(
-                "ij,jkl->ikl",
-                np.exp(1j * np.einsum("ij,jk->ik", hopping_vecs, k_grid)),
-                kham,
-            )
-            * (dk / (2 * np.pi)) ** ndim
-        )
-
-        h_0 = {}
-        for i, vector in enumerate(hopping_vecs):
-            h_0[tuple(vector)] = hopps[i]
+    size = ifft_array.shape[:-2]
 
-        return h_0
-    else:
-        return {(): kham}
+    keys = [np.arange(-size[0] // 2 + 1, size[0] // 2) for i in range(len(size))]
+    keys = itertools.product(*keys)
+    return {tuple(k): ifft_array[tuple(k)] for k in keys}
diff --git a/pymf/tb/utils.py b/pymf/tb/utils.py
index cc20753f704bf160e6b0eec752ae1ae50adebc46..123b559f3cd80683cb1f28143aa86f21df085303 100644
--- a/pymf/tb/utils.py
+++ b/pymf/tb/utils.py
@@ -1,30 +1,31 @@
 from itertools import product
-
 import numpy as np
 
-from pymf.mf import fermi_on_grid
-from pymf.tb.transforms import tb_to_khamvector
+from pymf.tb.tb import _tb_type
+from pymf.mf import fermi_on_kgrid
+from pymf.tb.transforms import tb_to_kgrid
 
 
-def generate_guess(vectors, ndof, scale=1):
-    """Generate guess for a tight-binding model.
+def generate_guess(
+    tb_keys: list[tuple[None] | tuple[int, ...]], ndof: int, scale: float = 1
+) -> _tb_type:
+    """Generate hermitian guess tight-binding dictionary.
 
     Parameters
     ----------
-    vectors : list
-        List of hopping vectors.
-    ndof : int
-        Number internal degrees of freedom (orbitals),
-    scale : float
-        The scale of the guess. Maximum absolute value of each element of the guess.
-
+    tb_keys :
+       List of hopping vectors (tight-binding dictionary keys) the guess contains.
+    ndof :
+        Number internal degrees of freedom within the unit cell.
+    scale :
+        Scale of the random guess.
     Returns
     -------
-    guess : tb dictionary
-        Guess in the form of a tight-binding model.
+    :
+        Hermitian guess tight-binding dictionary.
     """
     guess = {}
-    for vector in vectors:
+    for vector in tb_keys:
         if vector not in guess.keys():
             amplitude = scale * np.random.rand(ndof, ndof)
             phase = 2 * np.pi * np.random.rand(ndof, ndof)
@@ -40,25 +41,44 @@ def generate_guess(vectors, ndof, scale=1):
     return guess
 
 
-def generate_vectors(cutoff, dim):
-    """Generate hopping vectors up to a cutoff.
+def generate_tb_keys(cutoff: int, dim: int) -> list[tuple[None] | tuple[int, ...]]:
+    """Generate tight-binding dictionary keys up to a cutoff.
 
     Parameters
     ----------
-    cutoff : int
-        Maximum distance along each direction.
-    dim : int
-        Dimension of the vectors.
+    cutoff :
+        Maximum distance along each dimension to generate tight-bindign dictionary keys for.
+    dim :
+        Dimension of the tight-binding dictionary.
 
     Returns
     -------
-    List of hopping vectors.
+    :
+        List of generated tight-binding dictionary keys up to a cutoff.
     """
     return [*product(*([[*range(-cutoff, cutoff + 1)]] * dim))]
 
 
-def calculate_fermi_energy(tb, filling, nk=100):
-    """Calculate the Fermi energy for a given filling."""
-    kham = tb_to_khamvector(tb, nk, ks=None)
+def calculate_fermi_energy(tb: _tb_type, filling: float, nk: int = 100):
+    """
+    Calculate the Fermi energy of a given tight-binding dictionary.
+
+    Parameters
+    ----------
+    tb :
+        Tight-binding dictionary.
+    filling :
+        Number of particles in a unit cell.
+        Used to determine the Fermi level.
+    nk :
+        Number of k-points in a grid to sample the Brillouin zone along each dimension.
+        If the system is 0-dimensional (finite), this parameter is ignored.
+
+    Returns
+    -------
+    :
+        Fermi energy.
+    """
+    kham = tb_to_kgrid(tb, nk)
     vals = np.linalg.eigvalsh(kham)
-    return fermi_on_grid(vals, filling)
+    return fermi_on_kgrid(vals, filling)
diff --git a/pymf/tests/test_graphene.py b/pymf/tests/test_graphene.py
index 8657f7d36f16c9bea7c4cea804c61f832b8f27d8..90e1c1aecd2fdc22dcba7824da0bca036b5d82ba 100644
--- a/pymf/tests/test_graphene.py
+++ b/pymf/tests/test_graphene.py
@@ -3,11 +3,13 @@ import numpy as np
 import pytest
 
 from pymf.kwant_helper import kwant_examples, utils
-from pymf.model import Model
-from pymf.solvers import solver
-from pymf.tb.tb import add_tb
-from pymf.tb.transforms import tb_to_khamvector
-from pymf.tb.utils import generate_guess
+from pymf import (
+    Model,
+    solver,
+    tb_to_kgrid,
+    generate_guess,
+    add_tb,
+)
 
 
 def compute_gap(tb, fermi_energy=0, nk=100):
@@ -27,7 +29,7 @@ def compute_gap(tb, fermi_energy=0, nk=100):
      gap : float
      Indirect gap.
     """
-    kham = tb_to_khamvector(tb, nk, ks=None)
+    kham = tb_to_kgrid(tb, nk)
     vals = np.linalg.eigvalsh(kham)
 
     emax = np.max(vals[vals <= fermi_energy])
diff --git a/pymf/tests/test_hat.py b/pymf/tests/test_hat.py
index 154daf071b9b4a22914602d87106abe5b9a329c1..6f8fedc27db004ff02d6ad22ae70d76716c8914a 100644
--- a/pymf/tests/test_hat.py
+++ b/pymf/tests/test_hat.py
@@ -1,17 +1,21 @@
 # %%
 import numpy as np
-from pymf.solvers import solver
-from pymf.tb import utils
-from pymf.model import Model
-from pymf.tb.tb import add_tb, scale_tb
-from pymf import mf
-from pymf import observables
 import pytest
 
+from pymf import (
+    Model,
+    solver,
+    generate_guess,
+    scale_tb,
+    add_tb,
+    expectation_value,
+    construct_density_matrix,
+)
+
 
 # %%
 def total_energy(ham_tb, rho_tb):
-    return np.real(observables.expectation_value(rho_tb, ham_tb))
+    return np.real(expectation_value(rho_tb, ham_tb))
 
 
 # %%
@@ -35,10 +39,10 @@ for ndim in np.arange(4):
 
 # %%
 @np.vectorize
-def mf_rescaled(alpha, mf0, h0):
-    hamiltonian = add_tb(h0, scale_tb(mf0, alpha))
-    rho, _ = mf.construct_density_matrix(hamiltonian, filling=filling, nk=nk)
-    hamiltonian = add_tb(h0, scale_tb(mf0, np.sign(alpha)))
+def mf_rescaled(alpha, mf0):
+    hamiltonian = add_tb(h_0, scale_tb(mf0, alpha))
+    rho, _ = construct_density_matrix(hamiltonian, filling=filling, nk=nk)
+    hamiltonian = add_tb(h_0, scale_tb(mf0, np.sign(alpha)))
     return total_energy(hamiltonian, rho)
 
 
@@ -46,7 +50,7 @@ def mf_rescaled(alpha, mf0, h0):
 def test_mexican_hat(seed):
     np.random.seed(seed)
     for h0, h_int in zip(h0s, h_ints):
-        guess = utils.generate_guess(frozenset(h_int), ndof)
+        guess = generate_guess(frozenset(h_int), ndof)
         _model = Model(h0, h_int, filling=filling)
         mf_sol_groundstate = solver(
             _model, mf_guess=guess, nk=nk, optimizer_kwargs={"M": 0}
diff --git a/pymf/tests/test_hubbard.py b/pymf/tests/test_hubbard.py
index f06103409b066f16f97174563058a8791c922245..9007d995909c750cb007d7673913b7651201d786 100644
--- a/pymf/tests/test_hubbard.py
+++ b/pymf/tests/test_hubbard.py
@@ -2,11 +2,13 @@
 import numpy as np
 import pytest
 
-from pymf.model import Model
-from pymf.solvers import solver
-from pymf.tb import utils
-from pymf.tb.tb import add_tb
 from pymf.tests.test_graphene import compute_gap
+from pymf import (
+    Model,
+    solver,
+    generate_guess,
+    add_tb,
+)
 
 repeat_number = 10
 
@@ -33,7 +35,7 @@ def gap_relation_hubbard(Us, nk, nk_dense, tol=1e-3):
         h_int = {
             (0,): U * np.kron(np.ones((2, 2)), np.eye(2)),
         }
-        guess = utils.generate_guess(frozenset(h_int), len(list(h_0.values())[0]))
+        guess = generate_guess(frozenset(h_int), len(list(h_0.values())[0]))
         full_model = Model(h_0, h_int, filling=2)
         mf_sol = solver(full_model, guess, nk=nk)
         _gap = compute_gap(add_tb(h_0, mf_sol), fermi_energy=0, nk=nk_dense)
diff --git a/pymf/tests/test_params.py b/pymf/tests/test_params.py
index 1b636b310188fd5db9f6bdcbed8cc25ce9b6b5c4..640b2b68b3ecff30dc776fc6f8ba05b752d82878 100644
--- a/pymf/tests/test_params.py
+++ b/pymf/tests/test_params.py
@@ -3,7 +3,7 @@ import pytest
 import numpy as np
 from pymf.params.rparams import rparams_to_tb, tb_to_rparams
 from pymf.tb.tb import compare_dicts
-from pymf.tb.utils import generate_guess
+from pymf import generate_guess
 
 repeat_number = 10
 
diff --git a/pymf/tests/test_tb.py b/pymf/tests/test_tb.py
index 1b0a667b6b7d2698838ff60f60cfd4647d2d0b8e..fecb06d6dca555ae3bb7912f574b139517053f45 100644
--- a/pymf/tests/test_tb.py
+++ b/pymf/tests/test_tb.py
@@ -6,7 +6,7 @@ import pytest
 from scipy.fftpack import ifftn
 
 from pymf.tb.tb import compare_dicts
-from pymf.tb.transforms import ifftn_to_tb, tb_to_khamvector
+from pymf.tb.transforms import ifftn_to_tb, tb_to_kgrid
 
 repeat_number = 10
 
@@ -24,6 +24,6 @@ def test_fourier(seed):
     keys = [np.arange(-max_order + 1, max_order) for i in range(ndim)]
     keys = it.product(*keys)
     h_0 = {key: (np.random.rand(matrix_size, matrix_size) - 0.5) * 2 for key in keys}
-    kham = tb_to_khamvector(h_0, nk=nk)
+    kham = tb_to_kgrid(h_0, nk=nk)
     tb_new = ifftn_to_tb(ifftn(kham, axes=np.arange(ndim)))
     compare_dicts(h_0, tb_new)
diff --git a/pymf/tests/test_zero_hint.py b/pymf/tests/test_zero_hint.py
index db5b872ba4706df9fddb6e01e45776a744fa962b..70deb57497fd3ca6a92c40ab3c41b1a550e925f1 100644
--- a/pymf/tests/test_zero_hint.py
+++ b/pymf/tests/test_zero_hint.py
@@ -2,10 +2,9 @@
 import numpy as np
 import pytest
 
-from pymf.model import Model
-from pymf.solvers import solver
 from pymf.tb import utils
-from pymf.tb.tb import add_tb, compare_dicts
+from pymf.tb.tb import compare_dicts
+from pymf import Model, solver, generate_guess, add_tb, calculate_fermi_energy
 
 # %%
 repeat_number = 10
@@ -21,16 +20,16 @@ def test_zero_hint(seed):
     dim = np.random.randint(0, 3)
     ndof = np.random.randint(2, 10)
     filling = np.random.randint(1, ndof)
-    random_hopping_vecs = utils.generate_vectors(cutoff, dim)
+    random_hopping_vecs = utils.generate_tb_keys(cutoff, dim)
 
     zero_key = tuple([0] * dim)
-    h_0_random = utils.generate_guess(random_hopping_vecs, ndof, scale=1)
-    h_int_only_phases = utils.generate_guess(random_hopping_vecs, ndof, scale=0)
-    guess = utils.generate_guess(random_hopping_vecs, ndof, scale=1)
+    h_0_random = generate_guess(random_hopping_vecs, ndof, scale=1)
+    h_int_only_phases = generate_guess(random_hopping_vecs, ndof, scale=0)
+    guess = generate_guess(random_hopping_vecs, ndof, scale=1)
     model = Model(h_0_random, h_int_only_phases, filling=filling)
 
     mf_sol = solver(model, guess, nk=40, optimizer_kwargs={"M": 0, "f_tol": 1e-10})
-    h_fermi = utils.calculate_fermi_energy(mf_sol, filling=filling, nk=20)
+    h_fermi = calculate_fermi_energy(mf_sol, filling=filling, nk=20)
     mf_sol[zero_key] -= h_fermi * np.eye(mf_sol[zero_key].shape[0])
 
     compare_dicts(add_tb(mf_sol, h_0_random), h_0_random, atol=1e-10)
diff --git a/pyproject.toml b/pyproject.toml
index aba62d50bf13f427a4789e7e7c22f58803f9ae1b..f910fb94f9202d8e255d315537702302cd53e303 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -47,5 +47,5 @@ include = [
 ]
 
 [tool.codespell]
-skip = "*.ipynb,"
-ignore-words-list = "multline,"
+skip = "*.ipynb"
+ignore-words-list = "multline, ket, bra, braket"