Introduction to PyOR: A versatile NMR Simulator

Introduction

Magnetic resonance benefits from a variety of computational tools designed to simulate phenomena in both liquid and solid-state systems. Popular simulation packages include SpinDynamica, Spinach, SPINEVOLUTION, GAMMA, EasySpin, and SIMPSON. However, many of these tools rely on commercial platforms such as MATLAB or Mathematica, which may not be accessible to all users. Additionally, even when open-source, the complexity of the code can make it challenging to understand the underlying spin physics.

In this first post, I introduce PyOR (Python On Resonance) — a versatile NMR simulator designed to address a wide range of magnetic resonance problems. PyOR is particularly useful for learning, teaching, and simulating NMR, making it an excellent resource for both students and educators. One of its key strengths is helping spin physics enthusiasts visualize how textbook equations are translated into Python code. In this post, I’ll highlight some of PyOR’s main features and the “quantum object”.

In future posts, I will demonstrate how to define a spin system, create spin operators, define simulation parameters, construct Hamiltonians, evolve the system, plot and visualize data, and explore many other powerful functionalities offered by PyOR.

Overview

  • What is PyOR?
  • Download the PyOR source code
  • Features
  • Quantum objects in PyOR

What is PyOR?

In early 2024, I began developing PyOR (Python On Resonance), a compact and object-oriented Python package designed to simulate nonlinear nuclear magnetic resonance (NMR) phenomena. Initially focused on modeling effects such as radiation damping and maser (or “raser”), PyOR has since evolved into a versatile and extensible tool capable of simulating a wide range of magnetic resonance dynamics, encompassing both liquid and solid-state NMR.

Choosing Python as the development platform was an intentional decision: Python is freely available, easy to learn, and widely supported, making it an ideal environment for rapid scientific development. PyOR is particularly suited for beginners who have a basic understanding of matrices, spin operators, and Python programming. It provides an approachable way to code magnetic resonance pulse sequences and relaxation mechanics, serving both as a research tool and an educational platform.

With its modular structure and focus on transparency, PyOR also offers an opportunity for undergraduate and graduate students to explore the rich physics of magnetic resonance through direct simulation and hands-on coding. By blending intuitive design with scientific rigor, PyOR aims to lower the barrier to entry for magnetic resonance simulation and foster deeper engagement with the theory and practice of NMR.

Download the PyOR source code

PyOR is available at Github and Zenodo. Comprehensive documentation can be found at the PyOR Documentation Page.

Features

Spin System

A standout feature of PyOR is its ability to generate any type of spin system, from a single isolated spin to complex multispin networks (tested maximum of 6 spins), each with arbitrary spin quantum numbers. In PyOR, spin systems are defined using a dictionary that maps each spin label to a spin type. For example:

Spin_list = {"A" : "H1", "B" : "H2"}

defines a simple two-spin system where the labels A and B correspond to proton and deuterium with spin half and one, respectively.

Spin Operators

Once the user defines the spin dictionary, PyOR can construct all the spin operators not just for the whole system, but also for individual subsystems (i.e., for each spin separately). This allows for a lot of flexibility when building Hamiltonians, applying pulses, calculating observables, etc.

Here is an example of how to access the spin operators for both the full system and the subsystem of the previously defined spin-½ particle “A” in PyOR.

\text{QS.Az.matrix} = \begin{bmatrix} 0.5 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0.5 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0.5 & 0 & 0 & 0 \\ 0 & 0 & 0 & -0.5 & 0 & 0 \\ 0 & 0 & 0 & 0 & -0.5 & 0 \\ 0 & 0 & 0 & 0 & 0 & -0.5 \end{bmatrix}

\text{QS.Az\_sub.matrix} = \begin{bmatrix} 0.5 & 0 \\ 0 & -0.5 \end{bmatrix}

More about spin operators will be in my next post.

Master Equations

PyOR provides robust support for simulating relaxation dynamics using both the Redfield and Lindblad master equations, and this can be done in Hilbert or Liouville space, depending on the simulation context.

Currently, PyOR includes implementations for several common relaxation mechanisms, including:

  • Phenomenological relaxation
  • Random field fluctuations
  • Homonuclear dipolar interactions
  • Heteronuclear dipolar interactions

PyOR offers a flexible framework, and users can implement custom relaxation mechanisms to suit their specific needs.

Solving the master equations

Users can propagate the initial density matrix using two distinct approaches:

  • Matrix exponentiation, which constructs and applies the propagator to evolve the system
  • Direct numerical integration of the differential equations of motion using the Scipy ODE (Ordinary Differential Equation) solver package (scipy.integrate.solve_ivp).

Source code readability

One of the key features that distinguishes PyOR from other magnetic resonance simulation tools is its strong focus on code readability and modifiability. PyOR is built to ensure that users can clearly trace how textbook equations are translated into code. With its clean, well-documented Python architecture, even users new to scientific programming can easily follow and understand the implementation.

This level of transparency empowers users not just to run simulations, but to actively extend and adapt the code to meet their specific research needs. Whether it’s introducing a new interaction, tweaking solver behavior, or experimenting with custom relaxation models, PyOR is designed to be a flexible foundation, not a closed black box.

This philosophy makes PyOR especially appealing to students, educators, and researchers who want full control over their simulations and a deeper understanding of the underlying physics.

Modular Package

PyOR is built as a modular Python package, organized into a collection of focused modules. Each module defines a specific class with a well-defined purpose, such as generating spin operators, constructing quantum states, handling time evolution, or implementing relaxation dynamics.

Here are the various modules in PyOR

  • PyOR_Basis.py
  • PyOR_CoherenceFilters.py
  • PyOR_Commutators.py
  • PyOR_CrystalOrientation.py
  • PyOR_DensityMatrix.py
  • PyOR_Evolution.py
  • PyOR_Fitting.py
  • PyOR_Gamma.py
  • PyOR_Hamiltonian.py
  • PyOR_HardPulse.py
  • PyOR_MaserDataAnalyzer.py
  • PyOR_NonlinearNMR.py
  • PyOR_Particle.py
  • PyOR_PhaseCycle.py
  • PyOR_PhysicalConstants.py
  • PyOR_Plotting.py
  • PyOR_ProbabilityDensityFunctions.py
  • PyOR_QuadrupoleMoment.py
  • PyOR_QuantumLibrary.py
  • PyOR_QuantumObject.py
  • PyOR_QuantumSystem.py
  • PyOR_Relaxation.py
  • PyOR_Rotation.py
  • PyOR_SignalProcessing.py
  • PyOR_SphericalTensors.py
  • PyOR_SpinQuantumNumber.py

At the core of PyOR’s design is its object-oriented approach. Key elements of magnetic resonance simulations, such as spin operators, density matrices, quantum states, and Hamiltonians, are treated as objects. We will see this in the following sections.

Quantum objects in PyOR

Python heavily embraces the object-oriented programming paradigm, where objects—instances of classes—encapsulate both data (attributes) and behavior (methods). This approach is central to many scientific computing libraries, including NumPy. In NumPy, arrays are instances of the ndarray class, which comes with a rich set of methods for efficient numerical operations. An example is shown below:

import numpy as np
data = [1, 2, 3]

# Object: array
array = np.asarray(data)

# Attribute of object 'array'
print(array.shape)
# Output: (3,) -> shape gives the dimensions of the array

# Methode of object 'array'
print(array.mean())
# Output: 2.0 -> mean() computes the average of the array elements

The object, array has attributes like .shape, and methods like .mean(). The function np.asarray() wraps raw data into a powerful object that carries both data and operations. This is a basic example of object-oriented programming in Python: objects with attributes and methods, derived from a class.

In the previous section, I mentioned that PyOR treats spin operators, quantum states, density matrices, Hamiltonians, and relaxation operators as objects of the Python class QunObj. This class is inspired by QuTiP. The full implementation of the QunObj class is available on GitHub:

Attributes and methods of Quantum objects

Each QunObj instance comes with the following key attributes:

  • .data – Returns the object as a NumPy array.
  • .shape – Returns the dimensions of the object.
  • .datatype -Returns the data type of the NumPy array.
  • .matrix -Returns a symbolic representation of the matrix.
  • .type – Return object type: ‘ket’, ‘bra’, or ‘operator’.

And the following are a few methods of the class:

  • .Adjoint() – Return the Hermitian conjugate of the quantum object
  • .Conjugate() – Return the complex conjugate of the quantum object
  • .Transpose() – Return the transpose of the quantum object
  • .Trace() – Compute the trace of the object

User defined Quantum objects

Users can also create their own quantum objects using the QunObj class. Below are a few examples to illustrate how easily this can be done:

# Define the source path
SourcePath = 'The path to/Source_Doc'

# Add source path
import sys
sys.path.append(SourcePath)

# Import PyOR package
from PyOR_QuantumObject import QunObj

# Vector (ket)
ket1 = QunObj([[1], [0]],PrintDefault=True) 
ket2 = QunObj([[0], [1]],PrintDefault=True) 

# Matrix (Spin operator operator)
Sx = QunObj([[0.0, 0.5],[0.5,0.0]],PrintDefault=True) 
Sy = QunObj([[0.0, -0.5j],[0.5j,  0.0]],PrintDefault=True) 
Sz = QunObj([[0.5, 0.0],[0.0, -0.5]],PrintDefault=True) 

Id = QunObj([[1., 0.0],[0.0, 1]],PrintDefault=True) 

The above example created six objects ket1, ket2, Sx, Sy, Sz and Id; two quantum states (“kets”), three spin operator (“operators”), and identity matrix (“operators”) respectively. Now lets see some attributes and methods of these quantum objects.

Attribute: .matrix

To obtain the symbolic representation of objects, use the .matrix attribute as shown below:

ket1.matrix 
ket2.matrix 
Sx.matrix 
Sy.matrix 
Sz.matrix 
Id.matrix 

ket1.matrix gives the symbolic form of the object ket1:

\begin{bmatrix}1 \\ 0 \end{bmatrix}

ket2.matrix gives the symbolic form of the object ket2:

\begin{bmatrix}0 \\ 1 \end{bmatrix}

Similarly, the Sx.matrix returns,

\begin{bmatrix} 0.0 & 0.5 \\ 0.5 & 0.0 \end{bmatrix}

Attribute: .type

ket1.type 
ket2.type
Sx.type
Sy.type 
Sz.type 
Id.type

The ket1.type and Sx.type return, 'ket‘ and 'operator‘ respectively.

Method: .Rotate()

The method, Rotate can rotate any objects. It implements either R(\theta, J) \Psi or R(\theta, J) \rho R(\theta, J)^{\dagger} for a quantum state or operator, respectively. Where \Psi is a quantum state, \rho is a density matrix, J is a spin operator and R(\theta, J) = e^{-i \theta J}

To rotate ket1 by 180 degrees about the x axis (J = Sx)

ket3 = ket1.Rotate(180,Sx) 
ket3.matrix

returns,

\begin{bmatrix} 0 \\ -1.0i \end{bmatrix}

Method: add two objects

To add two objects, simply type:

ket4 = 2 * ket1 + 5 *  ket2
ket4.matrix

returns,

\begin{bmatrix} 2.0 \\ 5.0 \end{bmatrix}

Method: multiply two objects

For multiplying any two objects:

ket5 = Sx * ket1
ket5.matrix

returns,

\begin{bmatrix} 0 \\ 0.5 \end{bmatrix}

Method: .Adjoint()

This method gives the adjoint (conjugate transpose) of any quantum object.

bra1 = ket1.Adjoint()
bra1.matrixcode

return,

\begin{bmatrix} 1.0 & 0 \end{bmatrix}

To explore more examples of the QunObj class, click.

Conclusion

In this post, we explored some features of PyOR and introduced its quantum object system. The QunObj class provides a convenient way to manipulate these objects. In the next post, I’ll show how to create spin operators from a defined spin system. See you then!

Leave a Reply
    Artist Credit:
    Yu SciVis & Art LLC (Dr. Chung-Jui Yu)
    Website designed and developed by:
    NetzOptimize Inc.
    © COPYRIGHT . QUANTUM-RESONANCE.ORG

    Quantum Insights