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.
Files changed (125) hide show
  1. wavepacket-0.4.0/CHANGELOG.rst +109 -0
  2. {wavepacket-0.2.0 → wavepacket-0.4.0}/LICENSE +1 -1
  3. wavepacket-0.4.0/PKG-INFO +123 -0
  4. wavepacket-0.4.0/README.rst +88 -0
  5. wavepacket-0.4.0/doc/advanced/ode_solvers.md +264 -0
  6. {wavepacket-0.2.0/doc/demos → wavepacket-0.4.0/doc/advanced}/pendular_states.md +32 -29
  7. wavepacket-0.4.0/doc/advanced/plane_wave_grid.md +298 -0
  8. wavepacket-0.4.0/doc/advanced/polynomial_solvers.md +233 -0
  9. wavepacket-0.4.0/doc/advanced/thermal_states.md +457 -0
  10. {wavepacket-0.2.0 → wavepacket-0.4.0}/doc/architecture.rst +29 -20
  11. {wavepacket-0.2.0 → wavepacket-0.4.0}/doc/conf.py +27 -10
  12. wavepacket-0.4.0/doc/index.rst +48 -0
  13. wavepacket-0.4.0/doc/license.rst +17 -0
  14. wavepacket-0.4.0/doc/license.txt +121 -0
  15. {wavepacket-0.2.0 → wavepacket-0.4.0}/doc/representations.rst +5 -4
  16. wavepacket-0.4.0/doc/tutorials/chebychev_solvers.md +211 -0
  17. wavepacket-0.4.0/doc/tutorials/eigenstates.md +85 -0
  18. {wavepacket-0.2.0 → wavepacket-0.4.0}/doc/tutorials/plotting.md +65 -21
  19. wavepacket-0.4.0/doc/tutorials/relaxation.md +225 -0
  20. wavepacket-0.4.0/doc/tutorials/schroedinger_cat.md +171 -0
  21. wavepacket-0.4.0/pyproject.toml +111 -0
  22. wavepacket-0.4.0/src/wavepacket/__init__.py +62 -0
  23. wavepacket-0.4.0/src/wavepacket/builder/__init__.py +22 -0
  24. {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/builder/density.py +5 -6
  25. {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/builder/wave_function.py +42 -32
  26. {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/exceptions.py +5 -0
  27. wavepacket-0.4.0/src/wavepacket/expression/__init__.py +15 -0
  28. wavepacket-0.4.0/src/wavepacket/expression/expressionbase.py +105 -0
  29. wavepacket-0.4.0/src/wavepacket/expression/liouvillian.py +85 -0
  30. wavepacket-0.4.0/src/wavepacket/expression/schroedingerequation.py +42 -0
  31. {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/grid/__init__.py +1 -3
  32. {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/grid/_utils.py +5 -1
  33. {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/grid/dofbase.py +30 -40
  34. {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/grid/grid.py +30 -54
  35. {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/grid/planewavedof.py +15 -8
  36. {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/grid/spherical_harmonics_dof.py +27 -9
  37. {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/grid/state.py +42 -14
  38. wavepacket-0.4.0/src/wavepacket/logging.py +39 -0
  39. wavepacket-0.4.0/src/wavepacket/operator/__init__.py +27 -0
  40. wavepacket-0.4.0/src/wavepacket/operator/_clipping.py +37 -0
  41. {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/operator/fbroperators.py +77 -17
  42. {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/operator/misc_operators.py +26 -19
  43. {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/operator/operatorbase.py +53 -44
  44. {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/operator/potentials.py +29 -5
  45. {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/operator/time_dependent_operators.py +12 -6
  46. wavepacket-0.4.0/src/wavepacket/operator_utils.py +105 -0
  47. wavepacket-0.4.0/src/wavepacket/plot/__init__.py +17 -0
  48. wavepacket-0.4.0/src/wavepacket/plot/_utilities.py +8 -0
  49. {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/plot/plot_1d.py +70 -48
  50. wavepacket-0.4.0/src/wavepacket/plot/plot_2d.py +276 -0
  51. wavepacket-0.4.0/src/wavepacket/py.typed +0 -0
  52. {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/solver/__init__.py +3 -1
  53. wavepacket-0.4.0/src/wavepacket/solver/chebychev.py +192 -0
  54. {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/solver/odesolver.py +17 -13
  55. {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/solver/solverbase.py +20 -25
  56. wavepacket-0.4.0/src/wavepacket/special/__init__.py +16 -0
  57. {wavepacket-0.2.0/src/wavepacket → wavepacket-0.4.0/src/wavepacket/special}/generators.py +29 -14
  58. wavepacket-0.2.0/src/wavepacket/functions.py → wavepacket-0.4.0/src/wavepacket/special/pulse_shapes.py +9 -7
  59. {wavepacket-0.2.0/src/wavepacket/grid → wavepacket-0.4.0/src/wavepacket}/state_utilities.py +103 -24
  60. {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/testing/__init__.py +1 -2
  61. {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/testing/dummydof.py +9 -3
  62. {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/testing/dummyoperator.py +4 -5
  63. {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/testing/random.py +1 -1
  64. wavepacket-0.4.0/src/wavepacket/typing/__init__.py +7 -0
  65. {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/typing/data_types.py +8 -2
  66. {wavepacket-0.2.0 → wavepacket-0.4.0}/test/builder/test_density.py +6 -2
  67. {wavepacket-0.2.0 → wavepacket-0.4.0}/test/builder/test_product_wave_function.py +6 -3
  68. wavepacket-0.4.0/test/builder/test_wave_function.py +47 -0
  69. {wavepacket-0.2.0 → wavepacket-0.4.0}/test/conftest.py +1 -2
  70. wavepacket-0.4.0/test/expression/test_expression_sum.py +31 -0
  71. wavepacket-0.4.0/test/expression/test_liouvillian.py +74 -0
  72. {wavepacket-0.2.0 → wavepacket-0.4.0}/test/expression/test_schroedingerequation.py +1 -2
  73. {wavepacket-0.2.0 → wavepacket-0.4.0}/test/grid/test_dofbase.py +3 -2
  74. {wavepacket-0.2.0 → wavepacket-0.4.0}/test/grid/test_grid.py +12 -6
  75. {wavepacket-0.2.0 → wavepacket-0.4.0}/test/grid/test_sphericalharmonicsdof_basics.py +1 -1
  76. {wavepacket-0.2.0 → wavepacket-0.4.0}/test/grid/test_sphericalharmonicsdof_transformations.py +6 -2
  77. wavepacket-0.4.0/test/operator/test_clipping.py +52 -0
  78. wavepacket-0.2.0/test/operator/test_fbroperator.py → wavepacket-0.4.0/test/operator/test_fbroperators.py +47 -5
  79. {wavepacket-0.2.0 → wavepacket-0.4.0}/test/operator/test_misc_operators.py +9 -10
  80. {wavepacket-0.2.0 → wavepacket-0.4.0}/test/operator/test_operator_arithmetic.py +49 -9
  81. {wavepacket-0.2.0 → wavepacket-0.4.0}/test/operator/test_operatorbase.py +2 -4
  82. {wavepacket-0.2.0 → wavepacket-0.4.0}/test/operator/test_planewavefbroperator.py +35 -4
  83. {wavepacket-0.2.0 → wavepacket-0.4.0}/test/operator/test_potential1d.py +13 -0
  84. wavepacket-0.4.0/test/solver/test_chebychev.py +124 -0
  85. {wavepacket-0.2.0 → wavepacket-0.4.0}/test/solver/test_odesolver.py +6 -0
  86. {wavepacket-0.2.0 → wavepacket-0.4.0}/test/solver/test_solverbase.py +1 -1
  87. {wavepacket-0.2.0/test → wavepacket-0.4.0/test/special}/test_generators.py +28 -17
  88. wavepacket-0.2.0/test/test_functions.py → wavepacket-0.4.0/test/special/test_pulse_shapes.py +13 -14
  89. {wavepacket-0.2.0 → wavepacket-0.4.0}/test/test_logging.py +1 -1
  90. wavepacket-0.4.0/test/test_operator_utilities.py +117 -0
  91. wavepacket-0.4.0/test/test_state_utilities.py +262 -0
  92. wavepacket-0.2.0/.github/workflows/ci_testing.yml +0 -30
  93. wavepacket-0.2.0/.gitignore +0 -67
  94. wavepacket-0.2.0/.readthedocs.yaml +0 -18
  95. wavepacket-0.2.0/CHANGELOG.rst +0 -41
  96. wavepacket-0.2.0/PKG-INFO +0 -102
  97. wavepacket-0.2.0/README.rst +0 -83
  98. wavepacket-0.2.0/doc/getting-started.rst +0 -55
  99. wavepacket-0.2.0/doc/index.rst +0 -64
  100. wavepacket-0.2.0/doc/tutorials/schroedinger_cat.md +0 -96
  101. wavepacket-0.2.0/pyproject.toml +0 -64
  102. wavepacket-0.2.0/src/wavepacket/__init__.py +0 -27
  103. wavepacket-0.2.0/src/wavepacket/builder/__init__.py +0 -9
  104. wavepacket-0.2.0/src/wavepacket/expression/__init__.py +0 -10
  105. wavepacket-0.2.0/src/wavepacket/expression/expressionbase.py +0 -44
  106. wavepacket-0.2.0/src/wavepacket/expression/liouvillian.py +0 -64
  107. wavepacket-0.2.0/src/wavepacket/expression/schroedingerequation.py +0 -65
  108. wavepacket-0.2.0/src/wavepacket/logging.py +0 -34
  109. wavepacket-0.2.0/src/wavepacket/operator/__init__.py +0 -14
  110. wavepacket-0.2.0/src/wavepacket/operator/operator_utils.py +0 -30
  111. wavepacket-0.2.0/src/wavepacket/plot/__init__.py +0 -3
  112. wavepacket-0.2.0/src/wavepacket/typing/__init__.py +0 -7
  113. wavepacket-0.2.0/test/builder/test_wave_function.py +0 -60
  114. wavepacket-0.2.0/test/expression/test_commutatorliouvillian.py +0 -49
  115. wavepacket-0.2.0/test/grid/test_state_utilities.py +0 -177
  116. wavepacket-0.2.0/test/operator/test_operatorutils.py +0 -40
  117. {wavepacket-0.2.0 → wavepacket-0.4.0}/doc/Makefile +0 -0
  118. {wavepacket-0.2.0 → wavepacket-0.4.0}/doc/make.bat +0 -0
  119. {wavepacket-0.2.0 → wavepacket-0.4.0}/doc/requirements.txt +0 -0
  120. {wavepacket-0.2.0 → wavepacket-0.4.0}/src/wavepacket/testing/assertions.py +0 -0
  121. {wavepacket-0.2.0 → wavepacket-0.4.0}/test/grid/test_planewavedof_basics.py +0 -0
  122. {wavepacket-0.2.0 → wavepacket-0.4.0}/test/grid/test_planewavedof_transformations.py +0 -0
  123. {wavepacket-0.2.0 → wavepacket-0.4.0}/test/grid/test_state.py +0 -0
  124. {wavepacket-0.2.0 → wavepacket-0.4.0}/test/operator/test_time_dependent_operators.py +0 -0
  125. {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.
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2024 Ulf Lorenz
3
+ Copyright (c) 2024-2025 Ulf Lorenz
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -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.