PMM Tools Usage Guide
Welcome to pmm_tools! This interactive guide demonstrates how to build symbolic Hamiltonians for one-dimensional fermionic systems. We'll explore different ways to specify parameters and see exactly what symbolic expressions get generated.
Setup
Let's start by importing what we need and setting up better display for our symbolic expressions:
from sympy import symbols
import sympy
from IPython.display import display, Latex
import pmm_tools.models as models
# Make SymPy expressions display beautifully
from sympy import init_printing
init_printing()
Building Your First Hamiltonian: The Spinless Kitaev Chain
The interacting Kitaev chain is a perfect starting point—it includes hopping, superconducting pairing, Coulomb interactions, and chemical potential terms. Let's see how pmm_tools makes it easy to build these models symbolically.
Example 1: Start Simple with Defaults
When you don't specify parameters, each physical quantity becomes a single symbol that applies to the entire chain:
params, H, names = models.interacting_kiteav_chain(3)
print("Parameters generated:")
for key, value in params.items():
print(f" {key}: {value}")
print(f"\nFermionic operators: {names}")
print("\nThe resulting Hamiltonian:")
display(sympy.Eq(sympy.Symbol('H'), H))
findfont: Font family ['cmuserif'] not found. Falling back to DejaVu Sans.
findfont: Font family 'cmuserif' not found.
Parameters generated:
mu: mu
t: t
Delta: Delta
U: U
Fermionic operators: ['c_0', 'c_1', 'c_2']
The resulting Hamiltonian:
$\displaystyle H = \Delta {{c_0}^\dagger} {{c_1}^\dagger} + \Delta {{c_1}^\dagger} {{c_2}^\dagger} + \Delta {c_1} {c_0} + \Delta {c_2} {c_1} + U {{c_0}^\dagger} {c_0} {{c_1}^\dagger} {c_1} + U {{c_1}^\dagger} {c_1} {{c_2}^\dagger} {c_2} + \mu {{c_0}^\dagger} {c_0} + \mu {{c_1}^\dagger} {c_1} + \mu {{c_2}^\dagger} {c_2} + t {{c_0}^\dagger} {c_1} + t {{c_1}^\dagger} {c_0} + t {{c_1}^\dagger} {c_2} + t {{c_2}^\dagger} {c_1}$
Notice how each parameter (t, Delta, U, mu) is a single symbol that gets reused throughout the chain. This is perfect when you want uniform parameters.
Example 2: Mix Constants and Site-Dependent Parameters
Often you want some parameters to be the same everywhere (like hopping amplitude) but others to vary by site (like disorder in the chemical potential). Use declarative strings to control this:
params = {
"t": "constant", # Same hopping throughout
"Delta": "constant", # Uniform pairing
"U": "space-dependent", # Site-dependent interaction
"mu": "space-dependent", # Site-dependent chemical potential
}
params, H, names = models.interacting_kiteav_chain(3, params)
print("Parameter arrays:")
for key, value in params.items():
print(f" {key}: {value}")
print("\nHamiltonian with mixed parameter types:")
display(sympy.Eq(sympy.Symbol('H'), H))
findfont: Font family 'cmuserif' not found.
Parameter arrays:
t: t
Delta: Delta
U: [U_0, U_1]
mu: [mu_0, mu_1, mu_2]
Hamiltonian with mixed parameter types:
$\displaystyle H = \Delta {{c_0}^\dagger} {{c_1}^\dagger} + \Delta {{c_1}^\dagger} {{c_2}^\dagger} + \Delta {c_1} {c_0} + \Delta {c_2} {c_1} + U_{0} {{c_0}^\dagger} {c_0} {{c_1}^\dagger} {c_1} + U_{1} {{c_1}^\dagger} {c_1} {{c_2}^\dagger} {c_2} + \mu_{0} {{c_0}^\dagger} {c_0} + \mu_{1} {{c_1}^\dagger} {c_1} + \mu_{2} {{c_2}^\dagger} {c_2} + t {{c_0}^\dagger} {c_1} + t {{c_1}^\dagger} {c_0} + t {{c_1}^\dagger} {c_2} + t {{c_2}^\dagger} {c_1}$
See how U and mu now have indexed versions (U_0, U_1, U_2, etc.) while t and Delta remain single symbols? This gives you fine control over which terms can vary spatially.
Example 3: Custom Symbols and Numerical Values
You have complete flexibility—use custom SymPy symbols, set numerical values, or mix both with declarative strings:
params = {
"t": symbols("t_eff", real=True), # Custom symbol name
"Delta": symbols("Delta_eff", real=True), # Another custom symbol
"U": 0.1, # Numerical value
"mu": "space-dependent", # Still using declarative string
}
params, H, names = models.interacting_kiteav_chain(3, params)
print("Mixed parameter specification:")
for key, value in params.items():
print(f" {key}: {value}")
print("\nThe Hamiltonian with your custom symbols:")
display(sympy.Eq(sympy.Symbol('H'), H))
Mixed parameter specification:
t: t_eff
Delta: Delta_eff
U: 0.1
mu: [mu_0, mu_1, mu_2]
The Hamiltonian with your custom symbols:
findfont: Font family 'cmuserif' not found.
$\displaystyle H = \Delta_{eff} {{c_0}^\dagger} {{c_1}^\dagger} + \Delta_{eff} {{c_1}^\dagger} {{c_2}^\dagger} + \Delta_{eff} {c_1} {c_0} + \Delta_{eff} {c_2} {c_1} + \mu_{0} {{c_0}^\dagger} {c_0} + \mu_{1} {{c_1}^\dagger} {c_1} + \mu_{2} {{c_2}^\dagger} {c_2} + t_{eff} {{c_0}^\dagger} {c_1} + t_{eff} {{c_1}^\dagger} {c_0} + t_{eff} {{c_1}^\dagger} {c_2} + t_{eff} {{c_2}^\dagger} {c_1} + 0.1 {{c_0}^\dagger} {c_0} {{c_1}^\dagger} {c_1} + 0.1 {{c_1}^\dagger} {c_1} {{c_2}^\dagger} {c_2}$
Perfect! Notice how:
- t_eff and Delta_eff appear with your chosen names
- U is just the number 0.1 everywhere
- mu still gets expanded to site-dependent symbols (mu_0, mu_1, mu_2)
Example 4: Complete Manual Control
For maximum flexibility, specify exactly what you want at each site. This is great for modeling specific physical situations like quantum dots with tailored potentials:
t, Delta, U, mu = symbols("t Delta U mu", real=True)
# Create a chemical potential profile that varies across the chain
params = {
"t": t,
"Delta": Delta,
"U": U,
"mu": [mu - U/2, mu - U, mu - U/2] # Custom potential landscape
}
params, H, names = models.interacting_kiteav_chain(3, params)
print("Fully customized parameters:")
for key, value in params.items():
print(f" {key}: {value}")
print("\nHamiltonian with your custom potential profile:")
display(sympy.Eq(sympy.Symbol('H'), H))
Fully customized parameters:
t: t
Delta: Delta
U: U
mu: [-U/2 + mu, -U + mu, -U/2 + mu]
Hamiltonian with your custom potential profile:
findfont: Font family 'cmuserif' not found.
$\displaystyle H = \Delta {{c_0}^\dagger} {{c_1}^\dagger} + \Delta {{c_1}^\dagger} {{c_2}^\dagger} + \Delta {c_1} {c_0} + \Delta {c_2} {c_1} + U {{c_0}^\dagger} {c_0} {{c_1}^\dagger} {c_1} + U {{c_1}^\dagger} {c_1} {{c_2}^\dagger} {c_2} + t {{c_0}^\dagger} {c_1} + t {{c_1}^\dagger} {c_0} + t {{c_1}^\dagger} {c_2} + t {{c_2}^\dagger} {c_1} + \left(- U + \mu\right) {{c_1}^\dagger} {c_1} + \left(- \frac{U}{2} + \mu\right) {{c_0}^\dagger} {c_0} + \left(- \frac{U}{2} + \mu\right) {{c_2}^\dagger} {c_2}$
This creates a "valley" in the chemical potential—higher at the edges, lower in the middle. Perfect for trapping particles or creating interesting physics!
Under the hood: All these examples use
helper.prepare_parameters()to normalize your input into consistent arrays, thendefine_operators()creates the fermionic operators before assembling the Hamiltonian.
Stepping Up: Spinful Systems with the Dot-ABS Chain
Now let's explore spinful systems! The dot-ABS (Andreev Bound State) chain includes spin-orbit coupling, Zeeman splitting, and spin-dependent interactions. Each site now hosts two fermionic operators (one per spin direction).
params = {
"t_n": "constant", # Normal hopping (preserves spin)
"t_so": "constant", # Spin-orbit coupling (flips spin)
"Delta": "constant", # Superconducting pairing
"U": "constant", # Coulomb interaction
"mu": "space-dependent", # Chemical potential (can vary by site)
"E_z": "constant", # Zeeman energy (magnetic field)
}
params, H, names = models.dot_abs_chain(3, params)
print("Spinful system parameters:")
for key, value in params.items():
print(f" {key}: {value}")
print(f"\nFermionic operators (now with spin!): {names}")
print(" Notice: each site i has operators c_{i,up} and c_{i,down}")
print("\nThe full spinful Hamiltonian:")
display(sympy.Eq(sympy.Symbol('H'), H))
Spinful system parameters:
t_n: t_n
t_so: t_so
Delta: Delta
U: U
mu: [mu_0, mu_1, mu_2]
E_z: E_z
Fermionic operators (now with spin!): [c_{0\uparrow}, c_{0\downarrow}, c_{1\uparrow}, c_{1\downarrow}, c_{2\uparrow}, c_{2\downarrow}]
Notice: each site i has operators c_{i,up} and c_{i,down}
The full spinful Hamiltonian:
findfont: Font family 'cmuserif' not found.
$\displaystyle H = \Delta {{c_{0\uparrow}}^\dagger} {{c_{0\downarrow}}^\dagger} + \Delta {{c_{1\uparrow}}^\dagger} {{c_{1\downarrow}}^\dagger} + \Delta {{c_{2\uparrow}}^\dagger} {{c_{2\downarrow}}^\dagger} + U {{c_{0\uparrow}}^\dagger} {c_{0\uparrow}} {{c_{0\downarrow}}^\dagger} {c_{0\downarrow}} + U {{c_{1\uparrow}}^\dagger} {c_{1\uparrow}} {{c_{1\downarrow}}^\dagger} {c_{1\downarrow}} + U {{c_{2\uparrow}}^\dagger} {c_{2\uparrow}} {{c_{2\downarrow}}^\dagger} {c_{2\downarrow}} + t_{n} \left({{c_{0\downarrow}}^\dagger} {c_{1\downarrow}} + {{c_{1\downarrow}}^\dagger} {c_{0\downarrow}}\right) + t_{n} \left({{c_{0\uparrow}}^\dagger} {c_{1\uparrow}} + {{c_{1\uparrow}}^\dagger} {c_{0\uparrow}}\right) + t_{n} \left({{c_{1\downarrow}}^\dagger} {c_{2\downarrow}} + {{c_{2\downarrow}}^\dagger} {c_{1\downarrow}}\right) + t_{n} \left({{c_{1\uparrow}}^\dagger} {c_{2\uparrow}} + {{c_{2\uparrow}}^\dagger} {c_{1\uparrow}}\right) + t_{so} \left({{c_{0\downarrow}}^\dagger} {c_{1\uparrow}} + {{c_{1\uparrow}}^\dagger} {c_{0\downarrow}}\right) - t_{so} \left({{c_{0\uparrow}}^\dagger} {c_{1\downarrow}} + {{c_{1\downarrow}}^\dagger} {c_{0\uparrow}}\right) + t_{so} \left({{c_{1\downarrow}}^\dagger} {c_{2\uparrow}} + {{c_{2\uparrow}}^\dagger} {c_{1\downarrow}}\right) - t_{so} \left({{c_{1\uparrow}}^\dagger} {c_{2\downarrow}} + {{c_{2\downarrow}}^\dagger} {c_{1\uparrow}}\right) + \left(- E_{z} + \mu_{0}\right) {{c_{0\downarrow}}^\dagger} {c_{0\downarrow}} + \left(- E_{z} + \mu_{1}\right) {{c_{1\downarrow}}^\dagger} {c_{1\downarrow}} + \left(- E_{z} + \mu_{2}\right) {{c_{2\downarrow}}^\dagger} {c_{2\downarrow}} + \left(E_{z} + \mu_{0}\right) {{c_{0\uparrow}}^\dagger} {c_{0\uparrow}} + \left(E_{z} + \mu_{1}\right) {{c_{1\uparrow}}^\dagger} {c_{1\uparrow}} + \left(E_{z} + \mu_{2}\right) {{c_{2\uparrow}}^\dagger} {c_{2\uparrow}} + \overline{\Delta} {c_{0\downarrow}} {c_{0\uparrow}} + \overline{\Delta} {c_{1\downarrow}} {c_{1\uparrow}} + \overline{\Delta} {c_{2\downarrow}} {c_{2\uparrow}}$
What's new here?
-
Two operators per site:
c_{i,up}andc_{i,down}for spin-up and spin-down -
Spin-orbit terms:
t_socreates hopping that flips spin -
Zeeman splitting:
E_zshifts the energy of up/down spins differently