wavepacket 0.2.0__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- wavepacket-0.4.0/CHANGELOG.rst +109 -0
- {wavepacket-0.2.0 → wavepacket-0.4.0}/LICENSE +1 -1
- wavepacket-0.4.0/PKG-INFO +123 -0
- wavepacket-0.4.0/README.rst +88 -0
- wavepacket-0.4.0/doc/advanced/ode_solvers.md +264 -0
- {wavepacket-0.2.0/doc/demos → wavepacket-0.4.0/doc/advanced}/pendular_states.md +32 -29
- wavepacket-0.4.0/doc/advanced/plane_wave_grid.md +298 -0
- wavepacket-0.4.0/doc/advanced/polynomial_solvers.md +233 -0
- wavepacket-0.4.0/doc/advanced/thermal_states.md +457 -0
- {wavepacket-0.2.0 → wavepacket-0.4.0}/doc/architecture.rst +29 -20
- {wavepacket-0.2.0 → wavepacket-0.4.0}/doc/conf.py +27 -10
- wavepacket-0.4.0/doc/index.rst +48 -0
- wavepacket-0.4.0/doc/license.rst +17 -0
- wavepacket-0.4.0/doc/license.txt +121 -0
- {wavepacket-0.2.0 → wavepacket-0.4.0}/doc/representations.rst +5 -4
- wavepacket-0.4.0/doc/tutorials/chebychev_solvers.md +211 -0
- wavepacket-0.4.0/doc/tutorials/eigenstates.md +85 -0
- {wavepacket-0.2.0 → wavepacket-0.4.0}/doc/tutorials/plotting.md +65 -21
- wavepacket-0.4.0/doc/tutorials/relaxation.md +225 -0
- wavepacket-0.4.0/doc/tutorials/schroedinger_cat.md +171 -0
- wavepacket-0.4.0/pyproject.toml +111 -0
- wavepacket-0.4.0/src/wavepacket/__init__.py +62 -0
- wavepacket-0.4.0/src/wavepacket/builder/__init__.py +22 -0
- {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/builder/density.py +5 -6
- {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/builder/wave_function.py +42 -32
- {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/exceptions.py +5 -0
- wavepacket-0.4.0/src/wavepacket/expression/__init__.py +15 -0
- wavepacket-0.4.0/src/wavepacket/expression/expressionbase.py +105 -0
- wavepacket-0.4.0/src/wavepacket/expression/liouvillian.py +85 -0
- wavepacket-0.4.0/src/wavepacket/expression/schroedingerequation.py +42 -0
- {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/grid/__init__.py +1 -3
- {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/grid/_utils.py +5 -1
- {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/grid/dofbase.py +30 -40
- {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/grid/grid.py +30 -54
- {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/grid/planewavedof.py +15 -8
- {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/grid/spherical_harmonics_dof.py +27 -9
- {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/grid/state.py +42 -14
- wavepacket-0.4.0/src/wavepacket/logging.py +39 -0
- wavepacket-0.4.0/src/wavepacket/operator/__init__.py +27 -0
- wavepacket-0.4.0/src/wavepacket/operator/_clipping.py +37 -0
- {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/operator/fbroperators.py +77 -17
- {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/operator/misc_operators.py +26 -19
- {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/operator/operatorbase.py +53 -44
- {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/operator/potentials.py +29 -5
- {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/operator/time_dependent_operators.py +12 -6
- wavepacket-0.4.0/src/wavepacket/operator_utils.py +105 -0
- wavepacket-0.4.0/src/wavepacket/plot/__init__.py +17 -0
- wavepacket-0.4.0/src/wavepacket/plot/_utilities.py +8 -0
- {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/plot/plot_1d.py +70 -48
- wavepacket-0.4.0/src/wavepacket/plot/plot_2d.py +276 -0
- wavepacket-0.4.0/src/wavepacket/py.typed +0 -0
- {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/solver/__init__.py +3 -1
- wavepacket-0.4.0/src/wavepacket/solver/chebychev.py +192 -0
- {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/solver/odesolver.py +17 -13
- {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/solver/solverbase.py +20 -25
- wavepacket-0.4.0/src/wavepacket/special/__init__.py +16 -0
- {wavepacket-0.2.0/src/wavepacket → wavepacket-0.4.0/src/wavepacket/special}/generators.py +29 -14
- wavepacket-0.2.0/src/wavepacket/functions.py → wavepacket-0.4.0/src/wavepacket/special/pulse_shapes.py +9 -7
- {wavepacket-0.2.0/src/wavepacket/grid → wavepacket-0.4.0/src/wavepacket}/state_utilities.py +103 -24
- {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/testing/__init__.py +1 -2
- {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/testing/dummydof.py +9 -3
- {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/testing/dummyoperator.py +4 -5
- {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/testing/random.py +1 -1
- wavepacket-0.4.0/src/wavepacket/typing/__init__.py +7 -0
- {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/typing/data_types.py +8 -2
- {wavepacket-0.2.0 → wavepacket-0.4.0}/test/builder/test_density.py +6 -2
- {wavepacket-0.2.0 → wavepacket-0.4.0}/test/builder/test_product_wave_function.py +6 -3
- wavepacket-0.4.0/test/builder/test_wave_function.py +47 -0
- {wavepacket-0.2.0 → wavepacket-0.4.0}/test/conftest.py +1 -2
- wavepacket-0.4.0/test/expression/test_expression_sum.py +31 -0
- wavepacket-0.4.0/test/expression/test_liouvillian.py +74 -0
- {wavepacket-0.2.0 → wavepacket-0.4.0}/test/expression/test_schroedingerequation.py +1 -2
- {wavepacket-0.2.0 → wavepacket-0.4.0}/test/grid/test_dofbase.py +3 -2
- {wavepacket-0.2.0 → wavepacket-0.4.0}/test/grid/test_grid.py +12 -6
- {wavepacket-0.2.0 → wavepacket-0.4.0}/test/grid/test_sphericalharmonicsdof_basics.py +1 -1
- {wavepacket-0.2.0 → wavepacket-0.4.0}/test/grid/test_sphericalharmonicsdof_transformations.py +6 -2
- wavepacket-0.4.0/test/operator/test_clipping.py +52 -0
- wavepacket-0.2.0/test/operator/test_fbroperator.py → wavepacket-0.4.0/test/operator/test_fbroperators.py +47 -5
- {wavepacket-0.2.0 → wavepacket-0.4.0}/test/operator/test_misc_operators.py +9 -10
- {wavepacket-0.2.0 → wavepacket-0.4.0}/test/operator/test_operator_arithmetic.py +49 -9
- {wavepacket-0.2.0 → wavepacket-0.4.0}/test/operator/test_operatorbase.py +2 -4
- {wavepacket-0.2.0 → wavepacket-0.4.0}/test/operator/test_planewavefbroperator.py +35 -4
- {wavepacket-0.2.0 → wavepacket-0.4.0}/test/operator/test_potential1d.py +13 -0
- wavepacket-0.4.0/test/solver/test_chebychev.py +124 -0
- {wavepacket-0.2.0 → wavepacket-0.4.0}/test/solver/test_odesolver.py +6 -0
- {wavepacket-0.2.0 → wavepacket-0.4.0}/test/solver/test_solverbase.py +1 -1
- {wavepacket-0.2.0/test → wavepacket-0.4.0/test/special}/test_generators.py +28 -17
- wavepacket-0.2.0/test/test_functions.py → wavepacket-0.4.0/test/special/test_pulse_shapes.py +13 -14
- {wavepacket-0.2.0 → wavepacket-0.4.0}/test/test_logging.py +1 -1
- wavepacket-0.4.0/test/test_operator_utilities.py +117 -0
- wavepacket-0.4.0/test/test_state_utilities.py +262 -0
- wavepacket-0.2.0/.github/workflows/ci_testing.yml +0 -30
- wavepacket-0.2.0/.gitignore +0 -67
- wavepacket-0.2.0/.readthedocs.yaml +0 -18
- wavepacket-0.2.0/CHANGELOG.rst +0 -41
- wavepacket-0.2.0/PKG-INFO +0 -102
- wavepacket-0.2.0/README.rst +0 -83
- wavepacket-0.2.0/doc/getting-started.rst +0 -55
- wavepacket-0.2.0/doc/index.rst +0 -64
- wavepacket-0.2.0/doc/tutorials/schroedinger_cat.md +0 -96
- wavepacket-0.2.0/pyproject.toml +0 -64
- wavepacket-0.2.0/src/wavepacket/__init__.py +0 -27
- wavepacket-0.2.0/src/wavepacket/builder/__init__.py +0 -9
- wavepacket-0.2.0/src/wavepacket/expression/__init__.py +0 -10
- wavepacket-0.2.0/src/wavepacket/expression/expressionbase.py +0 -44
- wavepacket-0.2.0/src/wavepacket/expression/liouvillian.py +0 -64
- wavepacket-0.2.0/src/wavepacket/expression/schroedingerequation.py +0 -65
- wavepacket-0.2.0/src/wavepacket/logging.py +0 -34
- wavepacket-0.2.0/src/wavepacket/operator/__init__.py +0 -14
- wavepacket-0.2.0/src/wavepacket/operator/operator_utils.py +0 -30
- wavepacket-0.2.0/src/wavepacket/plot/__init__.py +0 -3
- wavepacket-0.2.0/src/wavepacket/typing/__init__.py +0 -7
- wavepacket-0.2.0/test/builder/test_wave_function.py +0 -60
- wavepacket-0.2.0/test/expression/test_commutatorliouvillian.py +0 -49
- wavepacket-0.2.0/test/grid/test_state_utilities.py +0 -177
- wavepacket-0.2.0/test/operator/test_operatorutils.py +0 -40
- {wavepacket-0.2.0 → wavepacket-0.4.0}/doc/Makefile +0 -0
- {wavepacket-0.2.0 → wavepacket-0.4.0}/doc/make.bat +0 -0
- {wavepacket-0.2.0 → wavepacket-0.4.0}/doc/requirements.txt +0 -0
- {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/testing/assertions.py +0 -0
- {wavepacket-0.2.0 → wavepacket-0.4.0}/test/grid/test_planewavedof_basics.py +0 -0
- {wavepacket-0.2.0 → wavepacket-0.4.0}/test/grid/test_planewavedof_transformations.py +0 -0
- {wavepacket-0.2.0 → wavepacket-0.4.0}/test/grid/test_state.py +0 -0
- {wavepacket-0.2.0 → wavepacket-0.4.0}/test/operator/test_time_dependent_operators.py +0 -0
- {wavepacket-0.2.0 → wavepacket-0.4.0}/test/test_base_exports.py +0 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
=========
|
|
2
|
+
ChangeLog
|
|
3
|
+
=========
|
|
4
|
+
|
|
5
|
+
0.4
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Breaking changes:
|
|
9
|
+
|
|
10
|
+
- (#44) Moved functionality around: all utility functions are in the main namespace,
|
|
11
|
+
all callables in a new "special" namespace.
|
|
12
|
+
- (#55) Swapped arguments for plotting functions to be consistent with how
|
|
13
|
+
solver.propagate() returns them
|
|
14
|
+
|
|
15
|
+
Major changes:
|
|
16
|
+
|
|
17
|
+
- (#37) Added contour plots for two-dimensional systems.
|
|
18
|
+
- (#35) Added documentation on the convergence of equally-spaced grids
|
|
19
|
+
and absorbing boundary conditions
|
|
20
|
+
- (#48) Added documentation on the convergence of ODE solvers
|
|
21
|
+
|
|
22
|
+
Minor changes:
|
|
23
|
+
|
|
24
|
+
- (#52) added unit_wave_function() to generate a wave function consisting of ones
|
|
25
|
+
- (#43) copied some user input arrays to avoid accidental side effects
|
|
26
|
+
- (#37) When calculating the density, you can optionally calculate the reduced
|
|
27
|
+
density along some degree of freedom.
|
|
28
|
+
- (#37) When plotting data, the potentials are evaluated at the time of plotting.
|
|
29
|
+
This may be interesting for time-dependent potentials.
|
|
30
|
+
- (#55) Gaussian accepts floats, not only arrays, and can be used for pulse shapes
|
|
31
|
+
- (#55) Comprehensible error message when adding an incorrect object to an operator
|
|
32
|
+
- (#16) Added code coverage and black+isort formatting
|
|
33
|
+
|
|
34
|
+
Bug fixes:
|
|
35
|
+
|
|
36
|
+
- (#35) 1D plots calculated abs(energy) instead of real(energy),
|
|
37
|
+
and did not treat unnormalized wave functions correctly.
|
|
38
|
+
- (#48) The calculation of the population lacked an abs(), so the populations could
|
|
39
|
+
be negative.
|
|
40
|
+
- (#16) SoftRectangularFunction had no soft turnoff
|
|
41
|
+
- Random wave functions were set up in the DVR, not in the default representation.
|
|
42
|
+
In general, this should cause an incorrect norm, but could change results in more
|
|
43
|
+
subtle ways.
|
|
44
|
+
|
|
45
|
+
0.3
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
- (#32) added ChebychevSolvers
|
|
49
|
+
- added a ChebychevSolver for real-time propagation
|
|
50
|
+
- added RelaxationSolver for imaginary-time propagation
|
|
51
|
+
- added function to normalize a state
|
|
52
|
+
- Added various documentation on Chebychev solver use, relaxation,
|
|
53
|
+
and polynomial solver theory
|
|
54
|
+
|
|
55
|
+
- (#24) added ExpressionSum and OneSidedLiouvillian for more complex expressions
|
|
56
|
+
|
|
57
|
+
- (#31) added diagonalize() function for operator eigenstates and -energies
|
|
58
|
+
|
|
59
|
+
- (#11) operator truncation and systematic handling of operator time-dependence
|
|
60
|
+
|
|
61
|
+
- (#12) Added some documentation on the construction of thermal states
|
|
62
|
+
|
|
63
|
+
- (#12) Changed the implementation of builder.random_wave_function();
|
|
64
|
+
it now constructs random numbers exp(i * phi) because these are more useful.
|
|
65
|
+
|
|
66
|
+
- (#13) Tutorials and demos also can be downloaded as Jupyter notebooks
|
|
67
|
+
and mostly render fine as notebooks.
|
|
68
|
+
|
|
69
|
+
- significantly improved type information, simplified attributes,
|
|
70
|
+
implemented some annotation best practices
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
0.2
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
- (#18) added spherical harmonics expansion:
|
|
77
|
+
degree of freedom, operator, generator
|
|
78
|
+
|
|
79
|
+
- (#19) added time-dependent functions and laser fields as operators,
|
|
80
|
+
including helper functions for sin**2 and rectangular pulses with soft turn-on
|
|
81
|
+
|
|
82
|
+
- (#20) added functionality related to projections
|
|
83
|
+
- projection operator for projecting onto a state or subspace
|
|
84
|
+
- population() function to easily calculate populations of target states
|
|
85
|
+
- Gram-Schmidt orthogonalization and normalization
|
|
86
|
+
|
|
87
|
+
- (#21) added more initial states
|
|
88
|
+
random wave functions, zero densities and wave functions, unit density
|
|
89
|
+
- (#21) added function grid.fbr_density() to calculate density in FBR
|
|
90
|
+
|
|
91
|
+
- (#22) 1D plotting helpers
|
|
92
|
+
- added plotting classes StackedPlot1D and SimplePlot1D
|
|
93
|
+
- added a tutorial on plotting
|
|
94
|
+
|
|
95
|
+
- (#23) Added a notebook with the PendularStates demos converted from the older versions
|
|
96
|
+
|
|
97
|
+
- (#15) added CI build using tox for different Python versions
|
|
98
|
+
Wavepacket now supports every Python >= 3.11
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
0.1
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
Initial release.
|
|
105
|
+
|
|
106
|
+
Scope is the simulation of a harmonic oscillator with an ordinary ODE solver,
|
|
107
|
+
plus a bit of introductory documentation and a demo.
|
|
108
|
+
|
|
109
|
+
That is, infrastructure, not so much content.
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: wavepacket
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: A package for solving Schrödinger and Liouville von Neumann equations.
|
|
5
|
+
Keywords: wave packet,quantum simulation,quantum system,DVR,Schroedinger equation,Liouville von Neumann equation,open systems,wave function,density operator,Liouvillian
|
|
6
|
+
Author: Ulf Lorenz
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Description-Content-Type: text/x-rst
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
|
13
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
14
|
+
Classifier: Operating System :: POSIX
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Chemistry
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: numpy >= 2.1.3
|
|
23
|
+
Requires-Dist: scipy >= 1.14.1
|
|
24
|
+
Requires-Dist: matplotlib >= 3.10
|
|
25
|
+
Requires-Dist: coverage >= 7 ; extra == "coverage"
|
|
26
|
+
Requires-Dist: black == 26.* ; extra == "formatting"
|
|
27
|
+
Requires-Dist: pytest >= 8.3 ; extra == "test"
|
|
28
|
+
Project-URL: Homepage, https://wavepacket.readthedocs.io
|
|
29
|
+
Project-URL: Issues, https://github.com/ulflor/wavepacket/issues
|
|
30
|
+
Project-URL: Source, https://github.com/ulflor/wavepacket
|
|
31
|
+
Provides-Extra: coverage
|
|
32
|
+
Provides-Extra: formatting
|
|
33
|
+
Provides-Extra: test
|
|
34
|
+
|
|
35
|
+
.. Be aware that this document also doubles as index.html on the documentation page.
|
|
36
|
+
|
|
37
|
+
Description
|
|
38
|
+
-----------
|
|
39
|
+
|
|
40
|
+
Wavepacket is a Python package to simulate small quantum systems.
|
|
41
|
+
Technically, it allows you to numerically solve Schrödinger and
|
|
42
|
+
Liouville-von-Neumann equations for distinguishable particles.
|
|
43
|
+
|
|
44
|
+
The full documentation can be found under https://wavepacket.readthedocs.io.
|
|
45
|
+
See also the advanced use cases for examples of what can be done.
|
|
46
|
+
|
|
47
|
+
Wavepacket focuses on a particular niche:
|
|
48
|
+
|
|
49
|
+
- Direct solution of Schrödinger / Liouville von Neumann equations.
|
|
50
|
+
No electronic structure, no MCTDH, no semiclassics, at least for now.
|
|
51
|
+
- Grid-based representation of operators (DVR approximation).
|
|
52
|
+
- Accessibility is an overriding goal. Wavepacket should be directly usable in teaching.
|
|
53
|
+
We try to provide good tutorials / docs, but also goodies like simple plotting.
|
|
54
|
+
- Exotic use cases should be as frictionless as possible.
|
|
55
|
+
For example, switching between wave functions and density operators is supported well.
|
|
56
|
+
Or random thermal wave functions are not much more complex to use than Gaussian states.
|
|
57
|
+
- Performance is a subordinate goal.
|
|
58
|
+
For specific systems, you can easily improve the performance by integer factors.
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
Links
|
|
62
|
+
-----
|
|
63
|
+
|
|
64
|
+
Documentation:
|
|
65
|
+
https://wavepacket.readthedocs.io
|
|
66
|
+
Source:
|
|
67
|
+
https://github.com/ulflor/wavepacket
|
|
68
|
+
Issue tracker:
|
|
69
|
+
https://github.com/ulflor/wavepacket/issues
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
Support
|
|
73
|
+
-------
|
|
74
|
+
|
|
75
|
+
There are three different avenues for feedback and support:
|
|
76
|
+
|
|
77
|
+
- The prefered option is through the mailing list wavepacket@python.org.
|
|
78
|
+
You may want to subscribe at https://mail.python.org/mailman3/lists/wavepacket.python.org/
|
|
79
|
+
to ensure you receive all answers.
|
|
80
|
+
- You can use the Forum on github: https://github.com/ulflor/wavepacket/discussions
|
|
81
|
+
You need a github account to post messages.
|
|
82
|
+
- You can open a new issue for bug reports or feature requests directly at
|
|
83
|
+
https://github.com/ulflor/wavepacket/issues.
|
|
84
|
+
This requires a github account.
|
|
85
|
+
You can of course also report bugs or request features on the mailing list or in the forum.
|
|
86
|
+
Depending on the complexity of the feature, this will lead to an immediate,
|
|
87
|
+
rapid, or prioritized implementation.
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
Contribution
|
|
91
|
+
------------
|
|
92
|
+
|
|
93
|
+
I currently lack a formal procedure for new contributors, but you are
|
|
94
|
+
very welcome to contribute to the project. If you do not know what to do,
|
|
95
|
+
use one of the support channels from the previous section ; there is enough
|
|
96
|
+
work for multiple developers, also for non-coding skills (there is never enough
|
|
97
|
+
documentation).
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
History
|
|
101
|
+
-------
|
|
102
|
+
|
|
103
|
+
The original long-time version of Wavepacket was written in Matlab and is still
|
|
104
|
+
actively maintained under https://sourceforge.net/p/wavepacket/matlab. It is stable,
|
|
105
|
+
battle-tested and works.
|
|
106
|
+
|
|
107
|
+
However, Matlab is expensive, and not all users had (legal)
|
|
108
|
+
access to it. Also, the project's architecture did not support some
|
|
109
|
+
advanced use cases well. Finally,
|
|
110
|
+
C++11 had just come out and looked cool, so I started a
|
|
111
|
+
reimplementation in C++ around 2013, adding Python bindings as an
|
|
112
|
+
afterthought. The C++ project will be superseded by this Python package, but
|
|
113
|
+
can be found under https://sourceforge.net/p/wavepacket/cpp.
|
|
114
|
+
|
|
115
|
+
This worked really well, except that deploying C++ code is difficult.
|
|
116
|
+
In particular, there was no cheap route towards building a "good"
|
|
117
|
+
Python package, especially under Windows, which restricted accessibility.
|
|
118
|
+
|
|
119
|
+
The solution is this Python-only version based on numpy.
|
|
120
|
+
The Python version is slower by a factor of two to three compared
|
|
121
|
+
to C++-backed code. This is, however, often cancelled by a
|
|
122
|
+
parallelization of the tensor operations.
|
|
123
|
+
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
.. Be aware that this document also doubles as index.html on the documentation page.
|
|
2
|
+
|
|
3
|
+
Description
|
|
4
|
+
-----------
|
|
5
|
+
|
|
6
|
+
Wavepacket is a Python package to simulate small quantum systems.
|
|
7
|
+
Technically, it allows you to numerically solve Schrödinger and
|
|
8
|
+
Liouville-von-Neumann equations for distinguishable particles.
|
|
9
|
+
|
|
10
|
+
The full documentation can be found under https://wavepacket.readthedocs.io.
|
|
11
|
+
See also the advanced use cases for examples of what can be done.
|
|
12
|
+
|
|
13
|
+
Wavepacket focuses on a particular niche:
|
|
14
|
+
|
|
15
|
+
- Direct solution of Schrödinger / Liouville von Neumann equations.
|
|
16
|
+
No electronic structure, no MCTDH, no semiclassics, at least for now.
|
|
17
|
+
- Grid-based representation of operators (DVR approximation).
|
|
18
|
+
- Accessibility is an overriding goal. Wavepacket should be directly usable in teaching.
|
|
19
|
+
We try to provide good tutorials / docs, but also goodies like simple plotting.
|
|
20
|
+
- Exotic use cases should be as frictionless as possible.
|
|
21
|
+
For example, switching between wave functions and density operators is supported well.
|
|
22
|
+
Or random thermal wave functions are not much more complex to use than Gaussian states.
|
|
23
|
+
- Performance is a subordinate goal.
|
|
24
|
+
For specific systems, you can easily improve the performance by integer factors.
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
Links
|
|
28
|
+
-----
|
|
29
|
+
|
|
30
|
+
Documentation:
|
|
31
|
+
https://wavepacket.readthedocs.io
|
|
32
|
+
Source:
|
|
33
|
+
https://github.com/ulflor/wavepacket
|
|
34
|
+
Issue tracker:
|
|
35
|
+
https://github.com/ulflor/wavepacket/issues
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
Support
|
|
39
|
+
-------
|
|
40
|
+
|
|
41
|
+
There are three different avenues for feedback and support:
|
|
42
|
+
|
|
43
|
+
- The prefered option is through the mailing list wavepacket@python.org.
|
|
44
|
+
You may want to subscribe at https://mail.python.org/mailman3/lists/wavepacket.python.org/
|
|
45
|
+
to ensure you receive all answers.
|
|
46
|
+
- You can use the Forum on github: https://github.com/ulflor/wavepacket/discussions
|
|
47
|
+
You need a github account to post messages.
|
|
48
|
+
- You can open a new issue for bug reports or feature requests directly at
|
|
49
|
+
https://github.com/ulflor/wavepacket/issues.
|
|
50
|
+
This requires a github account.
|
|
51
|
+
You can of course also report bugs or request features on the mailing list or in the forum.
|
|
52
|
+
Depending on the complexity of the feature, this will lead to an immediate,
|
|
53
|
+
rapid, or prioritized implementation.
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
Contribution
|
|
57
|
+
------------
|
|
58
|
+
|
|
59
|
+
I currently lack a formal procedure for new contributors, but you are
|
|
60
|
+
very welcome to contribute to the project. If you do not know what to do,
|
|
61
|
+
use one of the support channels from the previous section ; there is enough
|
|
62
|
+
work for multiple developers, also for non-coding skills (there is never enough
|
|
63
|
+
documentation).
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
History
|
|
67
|
+
-------
|
|
68
|
+
|
|
69
|
+
The original long-time version of Wavepacket was written in Matlab and is still
|
|
70
|
+
actively maintained under https://sourceforge.net/p/wavepacket/matlab. It is stable,
|
|
71
|
+
battle-tested and works.
|
|
72
|
+
|
|
73
|
+
However, Matlab is expensive, and not all users had (legal)
|
|
74
|
+
access to it. Also, the project's architecture did not support some
|
|
75
|
+
advanced use cases well. Finally,
|
|
76
|
+
C++11 had just come out and looked cool, so I started a
|
|
77
|
+
reimplementation in C++ around 2013, adding Python bindings as an
|
|
78
|
+
afterthought. The C++ project will be superseded by this Python package, but
|
|
79
|
+
can be found under https://sourceforge.net/p/wavepacket/cpp.
|
|
80
|
+
|
|
81
|
+
This worked really well, except that deploying C++ code is difficult.
|
|
82
|
+
In particular, there was no cheap route towards building a "good"
|
|
83
|
+
Python package, especially under Windows, which restricted accessibility.
|
|
84
|
+
|
|
85
|
+
The solution is this Python-only version based on numpy.
|
|
86
|
+
The Python version is slower by a factor of two to three compared
|
|
87
|
+
to C++-backed code. This is, however, often cancelled by a
|
|
88
|
+
parallelization of the tensor operations.
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
---
|
|
2
|
+
file_format: mystnb
|
|
3
|
+
kernelspec:
|
|
4
|
+
name: python3
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Background theory: ODE solvers
|
|
8
|
+
|
|
9
|
+
This web page can be downloaded as notebook: {nb-download}`ode_solvers.ipynb` (Jupyter)
|
|
10
|
+
or {download}`ode_solvers.md` (Markdown)
|
|
11
|
+
|
|
12
|
+
Here, we want to have a look at conventional ODE solvers.
|
|
13
|
+
In particular, how do you check calculations, and how do you speed up a calculation?
|
|
14
|
+
|
|
15
|
+
For the impatient, the main results of the following treatise are:
|
|
16
|
+
|
|
17
|
+
* Poor convergence can show up as a divergent norm.
|
|
18
|
+
In practice, however, ODE solvers use adaptive step sizes;
|
|
19
|
+
for reasonable parameters, they just take forever.
|
|
20
|
+
* The required time step, hence the performance,
|
|
21
|
+
is dictated by the largest (absolute) eigenvalue, not by the typical energies.
|
|
22
|
+
* Truncate aggressively and shift energies to improve performance.
|
|
23
|
+
|
|
24
|
+
## Some basics
|
|
25
|
+
|
|
26
|
+
### ODE solvers theory
|
|
27
|
+
|
|
28
|
+
We start from the Schrödinger equation
|
|
29
|
+
|
|
30
|
+
$$
|
|
31
|
+
\imath \dot \psi(t) = \hat H(t) \psi(t)
|
|
32
|
+
$$
|
|
33
|
+
|
|
34
|
+
and expand the wave function in a complete basis with time-dependent coefficients,
|
|
35
|
+
$|\psi(t)\rangle = \sum_n c_n(t) |\phi_n\rangle$.
|
|
36
|
+
Inserting and multiplying with $\langle \phi_m |$ gives the formula for the coefficients,
|
|
37
|
+
|
|
38
|
+
$$
|
|
39
|
+
\imath \dot c_m(t) = \sum_n \langle \phi_m| \, \hat H \, |\phi_n \rangle \ c_n(t)
|
|
40
|
+
$$
|
|
41
|
+
|
|
42
|
+
This is a system of ordinary differential equations,
|
|
43
|
+
and any explicit solver can be used for the time evolution, most notably Runge-Kutta algorithms.
|
|
44
|
+
A similar derivation can be done for the Liouville von-Neumann equation for density operators.
|
|
45
|
+
|
|
46
|
+
ODE solvers have the big advantage that they are robust work horses.
|
|
47
|
+
You can throw any problem at them: time-dependent Hamiltonians, complex-valued Hamiltonians, open systems, ...
|
|
48
|
+
they will produce a solution.
|
|
49
|
+
Nowadays, they always come with adaptive time steps,
|
|
50
|
+
so you only need to set an error bound, and the algorithm chooses an appropriate time step for you.
|
|
51
|
+
|
|
52
|
+
The big drawback is performance; ODE solvers do not exploit any knowledge of the system,
|
|
53
|
+
and this makes optimized solvers like polynomial solvers more efficient for specific classes of problems.
|
|
54
|
+
Typically, ODE solvers are not unitary, so the norm is not a conserved quantity and diverges on poor convergence.
|
|
55
|
+
However, because of the adaptive steps, this does not usually happen,
|
|
56
|
+
instead the algorithm chooses tiny step sizes with corresponding long run times.
|
|
57
|
+
|
|
58
|
+
### Model system
|
|
59
|
+
|
|
60
|
+
As example system, we choose a one-dimensional harmonic oscillator
|
|
61
|
+
with a shifted Gaussian as initial state.
|
|
62
|
+
|
|
63
|
+
```{code-cell}
|
|
64
|
+
import wavepacket as wp
|
|
65
|
+
|
|
66
|
+
grid = wp.grid.Grid(wp.grid.PlaneWaveDof(-10, 10, 128))
|
|
67
|
+
hamiltonian = (wp.operator.CartesianKineticEnergy(grid, 0, 1.0)
|
|
68
|
+
+ wp.operator.Potential1D(grid, 0, lambda x: 0.5 * x ** 2))
|
|
69
|
+
equation = wp.expression.SchroedingerEquation(hamiltonian)
|
|
70
|
+
|
|
71
|
+
psi0 = wp.builder.product_wave_function(grid, wp.special.Gaussian(x=-3, rms=2))
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Let us have a look at the projection onto the Hamiltonian eigenstates:
|
|
75
|
+
|
|
76
|
+
```{code-cell}
|
|
77
|
+
import matplotlib.pyplot as plt
|
|
78
|
+
import numpy as np
|
|
79
|
+
|
|
80
|
+
eigenstates = [state for _, state in wp.diagonalize(hamiltonian)]
|
|
81
|
+
projection = [wp.population(psi0, state) for state in eigenstates]
|
|
82
|
+
|
|
83
|
+
plt.semilogy(np.arange(128), projection, '+');
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
In theory, the population should decay smoothly.
|
|
87
|
+
In practice, strange things start to happen around the 50th eigenstate.
|
|
88
|
+
If we play around with the grid parameters,
|
|
89
|
+
it can be shown that this is an artifact of the finite grid extent and the finite number of grid points.
|
|
90
|
+
We skip the demonstration here to reduce the run time of the notebook.
|
|
91
|
+
This finite grid error is a common issue and discussed in depth in {doc}`plane_wave_grid`.
|
|
92
|
+
|
|
93
|
+
## Convergence behavior
|
|
94
|
+
|
|
95
|
+
Intuitively, we would assume that the time step is determined by the specific dynamics,
|
|
96
|
+
i.e., the initial wave function for our time-independent system.
|
|
97
|
+
For example, a low-energy state evolves slower in time than a high-energy state,
|
|
98
|
+
hence it should allow larger time steps.
|
|
99
|
+
Unfortunately, this intuition is incorrect.
|
|
100
|
+
|
|
101
|
+
To understand what happens let us force a calculation to not converge.
|
|
102
|
+
This is a contrived example; scipy's ODE solvers always use adaptive stepping,
|
|
103
|
+
so we need to choose absurd parameters to render this mechanism ineffective.
|
|
104
|
+
To understand where exactly things go awry,
|
|
105
|
+
we can plot the populations of the individual eigenstates
|
|
106
|
+
|
|
107
|
+
```{code-cell}
|
|
108
|
+
solver = wp.solver.OdeSolver(equation, 0.1, atol=1e10, first_step=0.01)
|
|
109
|
+
x = np.arange(128)
|
|
110
|
+
|
|
111
|
+
for t, psi in solver.propagate(psi0, 0, 2):
|
|
112
|
+
print(f"t = {t} Trace = {wp.trace(psi)}")
|
|
113
|
+
|
|
114
|
+
populations = [wp.population(psi, state) for state in eigenstates]
|
|
115
|
+
plt.semilogy(x, populations, '+')
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
We succeed: The norm diverges rapidly.
|
|
119
|
+
From the plot of the populations,
|
|
120
|
+
we find that the divergence is caused only by the high-energy contributions.
|
|
121
|
+
These are completely irrelevant for the actual dynamics,
|
|
122
|
+
but require small timesteps for accurate time evolution.
|
|
123
|
+
|
|
124
|
+
Improving the efficiency thus require an optimization of the operator spectrum.
|
|
125
|
+
There are two options: We can shift the spectrum, or we can truncate it.
|
|
126
|
+
|
|
127
|
+
### Shifting the spectrum
|
|
128
|
+
|
|
129
|
+
The solver spends most effort on the correct propagation of the populated
|
|
130
|
+
states with the highest energies.
|
|
131
|
+
This burden can be reduced by shifting the operator spectrum,
|
|
132
|
+
thereby making the largest (absolute) energies smaller.
|
|
133
|
+
Simple scaling laws suggest that the required time step scales with the inverse of the largest energy,
|
|
134
|
+
so shifting the spectrum to be balanced around zero would ideally halve the computation time.
|
|
135
|
+
|
|
136
|
+
To see what happens, let us repeat the previous analysis with the spectrum shifted by 50 atomic units
|
|
137
|
+
(approximately the energy of the 50th eigenstate)
|
|
138
|
+
|
|
139
|
+
```{code-cell}
|
|
140
|
+
shifted_equation = wp.expression.SchroedingerEquation(hamiltonian - 50)
|
|
141
|
+
|
|
142
|
+
shifted_solver = wp.solver.OdeSolver(shifted_equation, 0.1, atol=1e10, first_step=0.01)
|
|
143
|
+
x = np.arange(128)
|
|
144
|
+
|
|
145
|
+
for t, psi in shifted_solver.propagate(psi0, 0, 2):
|
|
146
|
+
print(f"t = {t} Trace = {wp.trace(psi)}")
|
|
147
|
+
|
|
148
|
+
populations = [wp.population(psi, state) for state in eigenstates]
|
|
149
|
+
plt.semilogy(x, populations, '+')
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
The good news is that the shift helps visibly with the divergent trace.
|
|
153
|
+
Alas, now the populations of the low-energy eigenstates start to diverge.
|
|
154
|
+
This causes problems with adaptive step sizes,
|
|
155
|
+
because the low-energy eigenstates have larger populations,
|
|
156
|
+
therefore they contribute more to the step error.
|
|
157
|
+
|
|
158
|
+
As a consequence, shifting by half of the spectral range is probably not optimal.
|
|
159
|
+
To figure out the optimal value, we use a "Counting expression" that measures
|
|
160
|
+
how often our Hamiltonian is applied, which is the expensive part of the algorithm.
|
|
161
|
+
|
|
162
|
+
```{code-cell}
|
|
163
|
+
class CountingExpression(wp.expression.ExpressionBase):
|
|
164
|
+
def __init__(self, wrapped_expression):
|
|
165
|
+
super().__init__(False)
|
|
166
|
+
|
|
167
|
+
self._wrapped_expression = wrapped_expression
|
|
168
|
+
self.count = 0
|
|
169
|
+
|
|
170
|
+
def apply(self, state, t):
|
|
171
|
+
self.count += 1
|
|
172
|
+
return self._wrapped_expression.apply(state, t)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Then we can try out the computational effort for different energy shifts:
|
|
176
|
+
|
|
177
|
+
```{code-cell}
|
|
178
|
+
for shift in range(0, 51, 10):
|
|
179
|
+
shifted_equation = wp.expression.SchroedingerEquation(hamiltonian - shift)
|
|
180
|
+
counter = CountingExpression(shifted_equation)
|
|
181
|
+
solver = wp.solver.OdeSolver(counter, 0.5)
|
|
182
|
+
|
|
183
|
+
solver.step(psi0, 0)
|
|
184
|
+
print(f"Shift = {shift} a.u. count = {counter.count}")
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
We find an optimal shift of around 40 a.u., which reduces the computational effort by about 25%,
|
|
188
|
+
only half the maximum possible reduction.
|
|
189
|
+
The drawback of this shifting is the required guesswork to find a suitable energy shift;
|
|
190
|
+
after all computing time is often cheaper than the scientist's attention.
|
|
191
|
+
And choosing the time step suboptimally yields even smaller gains.
|
|
192
|
+
|
|
193
|
+
### Truncating the spectrum
|
|
194
|
+
|
|
195
|
+
The second manipulation is a direct truncation of the operator spectrum.
|
|
196
|
+
While this may appear clumsy, there are good reasons to truncate aggressively:
|
|
197
|
+
|
|
198
|
+
1. As the previous analyses have shown, high-energy eigenstates are poorly represented by the grid anyway.
|
|
199
|
+
In the example here, this affects everything above about the 50th eigenstate.
|
|
200
|
+
Aggressive truncation therefore only invalidates results that were already incorrect in the first place.
|
|
201
|
+
|
|
202
|
+
In theory, we could remove these artifacts by extending the grid,
|
|
203
|
+
but then we pay twice: The larger grid makes all computations slower,
|
|
204
|
+
and to faithfully propagate the large-energy components, we need smaller time steps.
|
|
205
|
+
And at the end of the day, the gain in accuracy is tiny.
|
|
206
|
+
because the populations of the high-energy eigenstates are small, after all.
|
|
207
|
+
2. Whenever we simulate existing physical systems, we use models.
|
|
208
|
+
These models are _always_ low-energy approximations of the real system.
|
|
209
|
+
For example, we neglect anharmonic contributions,
|
|
210
|
+
or we ignore the coupling to highly excited electronic states,
|
|
211
|
+
maybe we even ignore excited states entirely within the Born-Oppenheimer approximation.
|
|
212
|
+
In the particular example here, we can safely assume that the 50th excited state
|
|
213
|
+
will be anything but a harmonic eigenstate.
|
|
214
|
+
|
|
215
|
+
Again, this means that by truncating the Hamiltonian,
|
|
216
|
+
we merely trade one incorrect approximation for another, so no harm is done.
|
|
217
|
+
|
|
218
|
+
Truncating the spectrum of the Hamiltonian requires an expensive diagonalization,
|
|
219
|
+
so we truncate the individual components instead.
|
|
220
|
+
|
|
221
|
+
Similar to the shifting of the spectrum, we can study the effect of truncation on the efficiency.
|
|
222
|
+
|
|
223
|
+
```{code-cell}
|
|
224
|
+
for cutoff in range(50, 201, 50):
|
|
225
|
+
truncated_hamiltonian = (wp.operator.CartesianKineticEnergy(grid, 0, 1.0, cutoff=cutoff)
|
|
226
|
+
+ wp.operator.Potential1D(grid, 0, lambda x: 0.5 * x ** 2, cutoff=cutoff))
|
|
227
|
+
truncated_equation = wp.expression.SchroedingerEquation(truncated_hamiltonian)
|
|
228
|
+
counter = CountingExpression(truncated_equation)
|
|
229
|
+
solver = wp.solver.OdeSolver(counter, 0.5)
|
|
230
|
+
|
|
231
|
+
solver.step(psi0, 0)
|
|
232
|
+
print(f"Cutoff = {cutoff} a.u. count = {counter.count}")
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
The computational effort drops considerably when reducing the cutoff,
|
|
236
|
+
which supports the thesis that significant effort is spent on propagating high-energy contributions.
|
|
237
|
+
After truncating the kinetic and potential energy to 50 a.u. each,
|
|
238
|
+
we are left with less than half of the original computational effort.
|
|
239
|
+
If we show more restraint and truncate only at 100 a.u., we still reduce the computational effort by one third.
|
|
240
|
+
These numbers suggest an advantage of truncation:
|
|
241
|
+
Even suboptimal or cautious guesses reduce the computational effort significantly.
|
|
242
|
+
|
|
243
|
+
How bad is the approximation for the dynamics?
|
|
244
|
+
To answer this question, we can look at the projection of the truncated result on the untruncated result;
|
|
245
|
+
any deviation from one can serve as a figure of merit of the additional error.
|
|
246
|
+
|
|
247
|
+
```{code-cell}
|
|
248
|
+
import math
|
|
249
|
+
|
|
250
|
+
truncated_hamiltonian = (wp.operator.CartesianKineticEnergy(grid, 0, 1.0, cutoff=50)
|
|
251
|
+
+ wp.operator.Potential1D(grid, 0, lambda x: 0.5 * x ** 2, cutoff=50))
|
|
252
|
+
truncated_equation = wp.expression.SchroedingerEquation(truncated_hamiltonian)
|
|
253
|
+
truncated_solver = wp.solver.OdeSolver(truncated_equation, math.pi / 2)
|
|
254
|
+
truncated_result = truncated_solver.step(psi0, 0)
|
|
255
|
+
|
|
256
|
+
solver = wp.solver.OdeSolver(equation, math.pi/2)
|
|
257
|
+
result = solver.step(psi0, 0)
|
|
258
|
+
|
|
259
|
+
print(f"Overlap after pi/2: {wp.population(truncated_result, result)}")
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
The additional deviation in the sixth decimal is of a similar magnitude
|
|
263
|
+
and therefore consistent with the grid error,
|
|
264
|
+
so we have introduced no undue additional approximation.
|