Git Product home page Git Product logo

qmsolve's Introduction

QMsolve: A module for solving and visualizing the Schrödinger equation

PyPi Python Versions PyPI Version Conda Version Chat

animation

QMsolve seeks to provide a solid and easy to use solver, capable of solving the Schrödinger equation for one and two particles, and creating descriptive and stunning visualizations of its solutions both in 1D, 2D, and 3D.

Installation

pip install qmsolve

3D plotting requires to have installed Mayavi. To install Mayavi directly along with QMsolve, you can type:

pip install qmsolve[with_mayavi]

Usage

The way this simulator works is by discretizing the Hamiltonian with an arbitrary potential, that you can specify as a function of the particle observables. This is achieved with the Hamiltonian constructor.

For example:

#define the interaction potential
def harmonic_oscillator(particle):
    k = 100. * eV / Å**2
    return 0.5 * k * particle.x**2

#define the Hamiltonian
H = Hamiltonian(particles = SingleParticle(), 
                potential = harmonic_oscillator, 
                spatial_ndim = 1, N = 512, extent = 20*Å)

Then, the method Hamiltonian.solve can be used to efficiently diagonalize the Hamiltonian and output the energies and the eigenstates of the system:

eigenstates = H.solve(max_states = 30)
print(eigenstates.energies) # The printed energies are in eV.

Finally, the eigenstates can be plotted with the use of the visualization class.

The visualization.superpositions method features the possibility of interactively visualizing a superposition of the computed eigenstates and studying the time dependence of the resulting wavefunction.

For efficiently solving the time dependent Schrödinger equation, TimeSimulation class must be used. It takes as an argument the Hamiltonian you have previously defined, and the method you desire to use.

For a quick start, take a look to the examples found in the examples subdirectory.

Eigenstate Solver Examples

To perform the example simulations, just run from the corresponding Python example scripts on the command prompt.

python 1D_harmonic_oscillator.py python 1D_harmonic_oscillator_superpositions.py
Link to the example Link to the example

This is the simplest example and one of the most well-studied Hamiltonians. The first script, 1D_harmonic_oscillator.py serves as an example of how to use the interface of the Eigenstate solver. This script returns the energies and a visualization of the eigenstates of the harmonic oscillator with an interactive slider. The second script, 1D_harmonic_oscillator_superpositions.py uses exactly the same Hamiltonian, but this time returns a simulation of a superposition of the computed eigenstates, whose coefficients can be interactively modified using the circular widgets presented below the animation.


python 1D_interactive_fermions_HO.py python 1D_non_interactive_fermions_HO.py
Link to the example Link to the example

These two examples show two fermions confined in 1D harmonic oscillator. The top-left plot represents the configuration space of the system.

Because we have two 1D particles, we need a two-dimensional space to represent it. Notice that because the particles are fermions, the wavefunction satisfies the antisymmetric condition: 𝜓(x1, x2) = - 𝜓(x2, x1)

An interesting characteristic that can be observed in these examples is how in the non interactive case the energy levels are equally spaced and degenerated, while in the interactive case the degeneracy is broken. As a starting point we suggest you to modify the confinement and the interaction potential to see what happens!

The time dependent version of this example can be found in 1D_interactive_fermions_HO_superpositions.py


animation
python 3D_four_gaussian_wells.py
Link to the example

This example serves to illustrate how to use the 3D solver. The results for other number of Gaussian wells are presented in this video. Gaussian wells are preferred as didactic examples because the absence of a singularities in its potential makes the computations easier. For realistic atomic examples, you can take a look at 3D_hydrogen_atom.py and 3D_dihydrogen_cation.py which use Coulomb potential.

Furthermore, in the hydrogen atom example you can turn on an electric field to visualize the Stark effect, or a magnetic field in 3D_hydrogen_atom_magnetic_field.py to visualize the Zeeman effect.


The default numerical method used for diagonalizing the Hamiltonian is ARPACK implementation of Implicitly Restarted Lanczos Method, which is called with scipy via eigsh method. For 3D examples, LOBPCG is preferred since its convergence is faster for high-dimensional matrices.

3D examples are also considerably faster when using a GPU. GPU acceleration requires having CuPy and CUDA installed in your computer.

To use GPU acceleration in your 3D simulations, add the argument method ='lobpcg-cupy' in the Hamiltonian solve method. For example:

eigenstates = H.solve( max_states = 50, method ='lobpcg-cupy')

Time Dependent Examples

These examples use the TimeSimulation class. This class takes as arguments the Hamiltonian you have previously defined, and the method you desire to use. Currently, there are two methods implemented: Split-Step Fourier and Cayley-Crank-Nicolson. To specify the method you want to use, use the arguments method ='split-step' and method ='crank-nicolson' respectively.

Once the TimeSimulation is set up, you need to use TimeSimulation.run method to perform the simulation. It takes the following arguments:

  • initial_wavefunction: State of the system at t=0, specified as a function of the particle observables.
  • dt: size of simulation time step.
  • total_time: amount of time to evolve the wavefunction.
  • store_steps: number of time steps to save in the array denoted by TimeSimulation.Ψ to later visualize or analyze. total_time/store_steps must be larger or equal to dt.

By default in the examples, initial_wavefunction initializes a Gaussian wave-packet with a spatial standard deviation equal to σ and initial momentum p_x0 and p_y0.

animation
python 2D_double_slit.py
Link to the example

This is a famous example, which was used to demonstrate the wave-like behavior of matter. In this script, the user can vary the slits separation, width, and depth to study their effect on the diffracted wavefunction.


animation
python 2D_cyclotron_orbit_magneticfield.py
Link to the example

This script shows an example where Crank Nicolson method is required. It presents a charged particle under a strong and uniform magnetic field, being confined in a quantum mechanical cyclotron orbit. The period and the radius of the orbit are compared with the classical values. Unlike other confinement potentials like the harmonic oscillator, the initial wavepacket is greatly dispersed over time.


animation
python 2D_quantum_resonant_tunneling.py
Link to the example

Finally, we present a demonstration of quantum resonant tunneling. In this script, two wavepackets are incident on a double potential well, representing two independent electrons. Despite having less energy than the lower, the upper electron has a higher chance of passing through the barriers. This is because its mean energy has been tuned to excite the quasi-ground state of the double potential well.

For electrons with energy corresponding approximately to the resonant energy level of the double potential well, the transmittance is close to unity. Furthemore, we can get the energy transmittance spectrum by taking the Fourier transform of the simulated wavepackets at a specific output position, yielding the following plot:

N|Solid

Generally, the Split Step method is preferred over Crank Nicolson both because of the computational cost of the time step and its associated error. Split Step has a time step error of cubic order O(Δt^3) while Crank Nicolson time step error is quadratic O(Δt^2). Thus Crank Nicolson requires more time steps to achieve the Split Step accuracy. However, Split Step can only be used for potentials of the form V(x,y,z) and Crank Nicolson use is necessary when the potential is also dependent of the particles momentum, like in the cyclotron orbit example.

Both methods are also implemented to be GPU-accelerated with cupy. Specifically, the speed boost of the cupy split-step is tested to be one and two orders of magnitudes faster than the CPU implementation, depending of the GPU and the grid size used. To use GPU acceleration in your simulations, use the arguments method ='split-step-cupy' and method ='crank-nicolson-cupy' in the TimeSimulation constructor.

The interface uses Hartree atomic units for input. In the file constants.py there is a list of common conversion factors from other units, that can be imported and used to build your potential.

Main developers

Contributors

  • Hudson Smith (shift-invert trick for eigsh solver, lobpcg prototype, support for the project and multiple suggestions)
  • Andrew Knyazev (lobpcg algorithm and AMG preconditioner)

qmsolve's People

Contributors

dhudsmith avatar marl0ny avatar rafael-fuente avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

qmsolve's Issues

Trying To Specify a 3D Potential, Wierd Gaps

Hello,

Your library is amazing and so are your examples. I was working on something similar for my research then stumbled upon your project.

I am trying to specify a simple barrier for Quantum Tunneling. I copy/pasted the method for rendering eigen modes and used it to render the potential

I specify the potential with this code

def tunnelingBarrier(
            particle, potential = 1, 
            offsetX = .2, offsetY = .5, offsetZ = .5, 
            width = .2, height = 1, depth = 1
        ):
    extent = np.abs(particle.x.max()) + np.abs(particle.x.min())
    offsetX *= particle.x.max()
    width *= particle.x.max()
    return np.where(
            (particle.x < (offsetX + width)) & (particle.x > offsetX), 
            np.ones(particle.x.shape) * potential, 
            np.zeros(particle.x.shape), 
        )

Here is what I get:
Screenshot from 2022-12-10 20-15-30
Screenshot from 2022-12-10 20-15-25
It took quite a lot of struggle getting too this point, and thankfully it is sort of working
I can just see a an evanescent probability density on the other side of where the barrier might be.

Screenshot from 2022-12-10 20-31-43

However its quite wonky, first there is the matter of the gap, the value should be constant wherever the conditions are satisfied.

If I double the offset (without changing particle.x.max()) the width of the barrier increases as well, while it should not be effected. Its like there are 2 of them.

Screenshot from 2022-12-10 20-19-50

I did modify the existing way of rendering things, so maybe I did something wrong there and its just a graphical thing?

from mayavi import mlab
import numpy as np
from qmsolve.util.constants import *

def plot_potential(hamiltonian, contrast_vals= [0.1, 0.25], plot_type = "volume"):
    potential = hamiltonian.Vgrid
    mlab.figure(1, bgcolor=(0, 0, 0), size=(700, 700))

    abs_max= np.amax(np.abs(potential))
    potential = (potential)/(abs_max)

    L = hamiltonian.extent / 2 / Å
    N = hamiltonian.N

    vol = mlab.pipeline.volume(mlab.pipeline.scalar_field(potential))

    # Change the color transfer function
    from tvtk.util import ctf
    c = ctf.save_ctfs(vol._volume_property)
    c['rgb'] = [[-0.45, 0.3, 0.3, 1.0],
                [-0.4, 0.1, 0.1, 1.0],
                [-0.3, 0.0, 0.0, 1.0],
                [-0.2, 0.0, 0.0, 1.0],
                [-0.001, 0.0, 0.0, 1.0],
                [0.0, 0.0, 0.0, 0.0],
                [0.001, 1.0, 0.0, 0.],
                [0.2, 1.0, 0.0, 0.0],
                [0.3, 1.0, 0.0, 0.0],
                [0.4, 1.0, 0.1, 0.1],
                [0.45, 1.0, 0.3, 0.3]]

    c['alpha'] = [[-0.5, 1.0],
                  [-contrast_vals[1], 1.0],
                  [-contrast_vals[0], 0.0],
                  [0, 0.0],
                  [contrast_vals[0], 0.0],
                  [contrast_vals[1], 1.0],
                 [0.5, 1.0]]
    if plot_type == 'volume':
        ctf.load_ctfs(c, vol._volume_property)
        # Update the shadow LUT of the volume module.
        vol.update_ctf = True

        mlab.outline()
        mlab.axes(xlabel='x [Å]', ylabel='y [Å]', zlabel='z [Å]',nb_labels=6 , ranges = (-L,L,-L,L,-L,L) )
        #azimuth angle
        φ = 30
        mlab.view(azimuth= φ,  distance=N*3.5)
        mlab.show()

    if plot_type == 'abs-volume':
    
        abs_max= np.amax(np.abs(potential))
        psi = (potential)/(abs_max)

        L = hamiltonian.extent/2/Å
        N = hamiltonian.N

        vol = mlab.pipeline.volume(mlab.pipeline.scalar_field(np.abs(psi)), vmin= contrast_vals[0], vmax= contrast_vals[1])
        # Change the color transfer function

        mlab.outline()
        mlab.axes(xlabel='x [Å]', ylabel='y [Å]', zlabel='z [Å]',nb_labels=6 , ranges = (-L,L,-L,L,-L,L) )
        #azimuth angle
        φ = 30
        mlab.view(azimuth= φ,  distance=N*3.5)
        mlab.show()

    elif plot_type == 'contour':
        psi = potential
        L = hamiltonian.extent/2/Å
        N = hamiltonian.N
        isovalue = np.mean(contrast_vals)
        abs_max= np.amax(np.abs(potential))
        psi = (psi)/(abs_max)

        field = mlab.pipeline.scalar_field(np.abs(psi))

        arr = mlab.screenshot(antialiased = False)

        mlab.outline()
        mlab.axes(xlabel='x [Å]', ylabel='y [Å]', zlabel='z [Å]',nb_labels=6 , ranges = (-L,L,-L,L,-L,L) )
        colour_data = np.angle(psi.T.ravel())%(2*np.pi)
        field.image_data.point_data.add_array(colour_data)
        field.image_data.point_data.get_array(1).name = 'phase'
        field.update()
        field2 = mlab.pipeline.set_active_attribute(field, 
                                                    point_scalars='scalar')
        contour = mlab.pipeline.contour(field2)
        contour.filter.contours= [isovalue,]
        contour2 = mlab.pipeline.set_active_attribute(contour, 
                                                    point_scalars='phase')
        s = mlab.pipeline.surface(contour, colormap='hsv', vmin= 0.0 ,vmax= 2.*np.pi)

        s.scene.light_manager.light_mode = 'vtk'
        s.actor.property.interpolation = 'phong'


        #azimuth angle
        φ = 30
        mlab.view(azimuth= φ,  distance=N*3.5)

        mlab.show()

The field is normalized but if its constant over a region then it should have the same color/value throughout the region. I looked at the code for Hamiltonian, the generation of the potential looks pretty straightforward, I'm not sure what is causing it.

So I would like to ask:
A: How do I resolve this, and is it me or qmsolve?
B: Could we have some documentation on how to specify potentials?

Thank you :)

In 1D is time double faster than p_x0 in initial gaussians?

Hello, when running the sample 1d potential barrier with 0 pot value, if I give 50 AU v0 speed(same that p_x0 because mass is 1), it takes 0.55 AU to walk 50 AU, when it have to take 1 AU.
Can this be fixed replacing?:
np.exp(p_x0*particle.x*1j)
with
np.exp(p_x0*particle.x*0.5j)
Take note that I've modified your code for plotting AU instead of femtoseconds:
time_ax.set_text(u"t = {} au".format("%.3f" % ((animation_data['t'] * TAUFMS))))
and the same for space Amstrong plot

The solver doesn't work correctly when there are negative eigenvalues

Code to reproduce:

import numpy as np
from qmsolve import Hamiltonian, SingleParticle, init_visualization


#interaction potential
def single_gaussian_well(particle):
	𝜇 = 0.0
	σ = 0.5
	V = -600* np.exp((-(particle.x)**2 -(particle.y-𝜇)**2 ) / (2*σ**2))
	return V



H = Hamiltonian(particles = SingleParticle(), 
				potential = single_gaussian_well, 
				spatial_ndim = 2, N = 100, extent = 8)


eigenstates = H.solve(max_states = 60)

print(eigenstates.energies)
visualization = init_visualization(eigenstates)
#visualization.plot_eigenstate(6)
visualization.slider_plot()

The problem is that :
eigsh(H, k = max_states, which='LM', sigma=0)
doesn't work with negative eigenvalues.

Because
eigsh(H, k = max_states, which='SA')
works with positive eigenvalues, I suggest adding an option to choose the correct solver, or just scaling the potential to be always positive.

Bohmian trajectories for time dependent wave functions

I've been studying the 2D time-dependent two-slit diffraction example. I'm trying to understand the units of the time degree of freedom. If I modify the potential to be all zero, then I should just get the free particle Gaussian wave packet in 2D which is solvable analytically. When I do this, it looks like the phase velocity at the center of the Gaussian wave packet is quite a bit slower than the motion of the wave packet's density center, and I think they should be about the same. I'm trying to use qmsolve to simulate Bohmian trajectories and eventually stochastic mechanics trajectories, and so I want to make sure that the Gaussian case looks right as a sanity check. Peter Holland's book "The Quantum Theory of Motion", chapter 4, covers this in some detail. Has anybody thought to try and use qmsolve for calculating Bohmian trajectories yet? It seems like an obvious thing to do.

Request for Final year project

How are you? I have seen your videos on YouTube. Physics simulation videos are very beautiful and I am a final semester student of bachelor degree in physics and my final year project is also on physics simulation. It didn't work. Can you help me? Make me project at reasonable price.I share more details of the project with you if you want to make it.

Imaginary part of the wave function

In the time independent harmonic oscillator from example, the eigen states doesn't have any imaginary part contrary to what could be expected by the analytical resolution of the harmonic oscillator. (exp(imphi))

Do you have any idea why, and how I could fix it ?

Energy Visualization problem in 4 Gaussian Wells Example

I am having some issues learning how to work with this package. Some of the examples don't seem to work the same way on my computer.
Most work, but there are two that don't.

Specifically, 3D_two_gaussian_wells.py, line 21, references a variable "N0" which doesn't exist, leading to an error.

Also, I am having problems with the animation in 4 Gaussian Wells. I see the wavefunction visualization, but I don't see the energy levels? it's odd, because it's otherwise exactly the same as the example.

Non-printable characters

Some code contains non-printable characters that causes problems when trying to run simulations.
Manually solving this manually is tedious. Any chance of using only printable characters?

Animation error

The simulation seems to run ok. However a plot at t=0 is plotted but no animation.

The following warning is displayed:

UserWarning: Animation was deleted without rendering anything. This is most likely not intended. To prevent deletion, assign the Animation to a variable, e.g. anim, that exists until you have outputted the Animation using plt.show() or anim.save().
warnings.warn(

I have tried using plt.show() or anim.save(). but no luck.

Request: Add Eigsh-Cupy Mode

CuPy also has eigsh and it makes computation much faster, the code should be almost identical to what is alraedy there

        if method == 'eigsh':
            from scipy.sparse.linalg import eigsh

            # Note: uses shift-invert trick for stability finding low-lying states
            # Ref: https://docs.scipy.org/doc/scipy/reference/tutorial/arpack.html#shift-invert-mode

            eigenvalues, eigenvectors = eigsh(H, k=max_states, which='LM', sigma=min(0, self.E_min))

Simply change the import (and condition) and it should work.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.