wavepacket 0.4.0__tar.gz → 0.5__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 (119) hide show
  1. {wavepacket-0.4.0 → wavepacket-0.5}/CHANGELOG.rst +49 -0
  2. {wavepacket-0.4.0 → wavepacket-0.5}/LICENSE +1 -1
  3. {wavepacket-0.4.0 → wavepacket-0.5}/PKG-INFO +4 -5
  4. {wavepacket-0.4.0 → wavepacket-0.5}/README.rst +3 -2
  5. {wavepacket-0.4.0 → wavepacket-0.5}/doc/advanced/ode_solvers.md +3 -5
  6. {wavepacket-0.4.0 → wavepacket-0.5}/doc/advanced/pendular_states.md +2 -2
  7. {wavepacket-0.4.0 → wavepacket-0.5}/doc/advanced/plane_wave_grid.md +2 -2
  8. {wavepacket-0.4.0 → wavepacket-0.5}/doc/advanced/polynomial_solvers.md +9 -8
  9. wavepacket-0.5/doc/advanced/reduced_density.md +259 -0
  10. {wavepacket-0.4.0 → wavepacket-0.5}/doc/advanced/thermal_states.md +7 -6
  11. {wavepacket-0.4.0 → wavepacket-0.5}/doc/architecture.rst +1 -1
  12. {wavepacket-0.4.0 → wavepacket-0.5}/doc/conf.py +1 -1
  13. {wavepacket-0.4.0 → wavepacket-0.5}/doc/index.rst +2 -0
  14. wavepacket-0.5/doc/tutorials/channels.md +266 -0
  15. {wavepacket-0.4.0 → wavepacket-0.5}/doc/tutorials/chebychev_solvers.md +9 -9
  16. {wavepacket-0.4.0 → wavepacket-0.5}/doc/tutorials/eigenstates.md +2 -1
  17. {wavepacket-0.4.0 → wavepacket-0.5}/doc/tutorials/plotting.md +11 -12
  18. {wavepacket-0.4.0 → wavepacket-0.5}/doc/tutorials/relaxation.md +1 -0
  19. {wavepacket-0.4.0 → wavepacket-0.5}/doc/tutorials/schroedinger_cat.md +9 -5
  20. {wavepacket-0.4.0 → wavepacket-0.5}/pyproject.toml +8 -3
  21. {wavepacket-0.4.0 → wavepacket-0.5}/src/wavepacket/__init__.py +1 -1
  22. {wavepacket-0.4.0 → wavepacket-0.5}/src/wavepacket/builder/density.py +4 -4
  23. {wavepacket-0.4.0 → wavepacket-0.5}/src/wavepacket/builder/wave_function.py +40 -14
  24. {wavepacket-0.4.0 → wavepacket-0.5}/src/wavepacket/exceptions.py +1 -1
  25. {wavepacket-0.4.0 → wavepacket-0.5}/src/wavepacket/expression/liouvillian.py +1 -1
  26. {wavepacket-0.4.0 → wavepacket-0.5}/src/wavepacket/expression/schroedingerequation.py +1 -1
  27. wavepacket-0.5/src/wavepacket/grid/__init__.py +27 -0
  28. wavepacket-0.5/src/wavepacket/grid/channel_dof.py +112 -0
  29. {wavepacket-0.4.0 → wavepacket-0.5}/src/wavepacket/grid/dofbase.py +1 -1
  30. {wavepacket-0.4.0 → wavepacket-0.5}/src/wavepacket/grid/grid.py +23 -2
  31. {wavepacket-0.4.0 → wavepacket-0.5}/src/wavepacket/grid/planewavedof.py +3 -5
  32. {wavepacket-0.4.0 → wavepacket-0.5}/src/wavepacket/grid/spherical_harmonics_dof.py +2 -2
  33. {wavepacket-0.4.0 → wavepacket-0.5}/src/wavepacket/grid/state.py +3 -3
  34. wavepacket-0.5/src/wavepacket/grid/transformation.py +237 -0
  35. wavepacket-0.5/src/wavepacket/logging.py +74 -0
  36. {wavepacket-0.4.0 → wavepacket-0.5}/src/wavepacket/operator/__init__.py +3 -0
  37. wavepacket-0.5/src/wavepacket/operator/channel_operators.py +141 -0
  38. {wavepacket-0.4.0 → wavepacket-0.5}/src/wavepacket/operator/misc_operators.py +2 -2
  39. {wavepacket-0.4.0 → wavepacket-0.5}/src/wavepacket/operator/time_dependent_operators.py +1 -1
  40. {wavepacket-0.4.0 → wavepacket-0.5}/src/wavepacket/operator_utils.py +4 -4
  41. {wavepacket-0.4.0 → wavepacket-0.5}/src/wavepacket/plot/plot_1d.py +77 -15
  42. {wavepacket-0.4.0 → wavepacket-0.5}/src/wavepacket/plot/plot_2d.py +67 -30
  43. {wavepacket-0.4.0 → wavepacket-0.5}/src/wavepacket/solver/chebychev.py +4 -3
  44. {wavepacket-0.4.0 → wavepacket-0.5}/src/wavepacket/solver/odesolver.py +1 -1
  45. {wavepacket-0.4.0 → wavepacket-0.5}/src/wavepacket/special/generators.py +2 -2
  46. {wavepacket-0.4.0 → wavepacket-0.5}/src/wavepacket/special/pulse_shapes.py +1 -1
  47. {wavepacket-0.4.0 → wavepacket-0.5}/src/wavepacket/state_utilities.py +10 -9
  48. {wavepacket-0.4.0 → wavepacket-0.5}/test/builder/test_product_wave_function.py +34 -0
  49. wavepacket-0.5/test/grid/test_channel_dof.py +78 -0
  50. wavepacket-0.5/test/grid/test_channel_projection_transformation.py +80 -0
  51. {wavepacket-0.4.0 → wavepacket-0.5}/test/grid/test_grid.py +13 -0
  52. wavepacket-0.5/test/grid/test_partialtracetransformation.py +93 -0
  53. wavepacket-0.5/test/operator/test_channel_operators.py +144 -0
  54. {wavepacket-0.4.0 → wavepacket-0.5}/test/special/test_pulse_shapes.py +2 -2
  55. {wavepacket-0.4.0 → wavepacket-0.5}/test/test_state_utilities.py +8 -2
  56. wavepacket-0.5/test_acceptance/readme +7 -0
  57. wavepacket-0.5/test_acceptance/test_1d_absorbing_boundaries.txt +75 -0
  58. wavepacket-0.5/test_acceptance/test_2d_harmonic_oscillator.txt +65 -0
  59. wavepacket-0.5/test_acceptance/test_electronic_plotting.py +57 -0
  60. wavepacket-0.5/test_acceptance/test_laser_pulse_with_density_operator.txt +161 -0
  61. wavepacket-0.5/test_acceptance/test_relaxation.txt +37 -0
  62. wavepacket-0.5/test_acceptance/test_tise.txt +49 -0
  63. wavepacket-0.5/test_acceptance/test_tracing_out.txt +118 -0
  64. wavepacket-0.4.0/src/wavepacket/grid/__init__.py +0 -11
  65. wavepacket-0.4.0/src/wavepacket/logging.py +0 -39
  66. {wavepacket-0.4.0 → wavepacket-0.5}/doc/Makefile +0 -0
  67. {wavepacket-0.4.0 → wavepacket-0.5}/doc/license.rst +0 -0
  68. {wavepacket-0.4.0 → wavepacket-0.5}/doc/license.txt +0 -0
  69. {wavepacket-0.4.0 → wavepacket-0.5}/doc/make.bat +0 -0
  70. {wavepacket-0.4.0 → wavepacket-0.5}/doc/representations.rst +0 -0
  71. {wavepacket-0.4.0 → wavepacket-0.5}/doc/requirements.txt +0 -0
  72. {wavepacket-0.4.0 → wavepacket-0.5}/src/wavepacket/builder/__init__.py +0 -0
  73. {wavepacket-0.4.0 → wavepacket-0.5}/src/wavepacket/expression/__init__.py +0 -0
  74. {wavepacket-0.4.0 → wavepacket-0.5}/src/wavepacket/expression/expressionbase.py +0 -0
  75. {wavepacket-0.4.0 → wavepacket-0.5}/src/wavepacket/grid/_utils.py +0 -0
  76. {wavepacket-0.4.0 → wavepacket-0.5}/src/wavepacket/operator/_clipping.py +0 -0
  77. {wavepacket-0.4.0 → wavepacket-0.5}/src/wavepacket/operator/fbroperators.py +0 -0
  78. {wavepacket-0.4.0 → wavepacket-0.5}/src/wavepacket/operator/operatorbase.py +0 -0
  79. {wavepacket-0.4.0 → wavepacket-0.5}/src/wavepacket/operator/potentials.py +0 -0
  80. {wavepacket-0.4.0 → wavepacket-0.5}/src/wavepacket/plot/__init__.py +0 -0
  81. {wavepacket-0.4.0 → wavepacket-0.5}/src/wavepacket/plot/_utilities.py +0 -0
  82. {wavepacket-0.4.0 → wavepacket-0.5}/src/wavepacket/py.typed +0 -0
  83. {wavepacket-0.4.0 → wavepacket-0.5}/src/wavepacket/solver/__init__.py +0 -0
  84. {wavepacket-0.4.0 → wavepacket-0.5}/src/wavepacket/solver/solverbase.py +0 -0
  85. {wavepacket-0.4.0 → wavepacket-0.5}/src/wavepacket/special/__init__.py +0 -0
  86. {wavepacket-0.4.0 → wavepacket-0.5}/src/wavepacket/testing/__init__.py +0 -0
  87. {wavepacket-0.4.0 → wavepacket-0.5}/src/wavepacket/testing/assertions.py +0 -0
  88. {wavepacket-0.4.0 → wavepacket-0.5}/src/wavepacket/testing/dummydof.py +0 -0
  89. {wavepacket-0.4.0 → wavepacket-0.5}/src/wavepacket/testing/dummyoperator.py +0 -0
  90. {wavepacket-0.4.0 → wavepacket-0.5}/src/wavepacket/testing/random.py +0 -0
  91. {wavepacket-0.4.0 → wavepacket-0.5}/src/wavepacket/typing/__init__.py +0 -0
  92. {wavepacket-0.4.0 → wavepacket-0.5}/src/wavepacket/typing/data_types.py +0 -0
  93. {wavepacket-0.4.0 → wavepacket-0.5}/test/builder/test_density.py +0 -0
  94. {wavepacket-0.4.0 → wavepacket-0.5}/test/builder/test_wave_function.py +0 -0
  95. {wavepacket-0.4.0 → wavepacket-0.5}/test/conftest.py +0 -0
  96. {wavepacket-0.4.0 → wavepacket-0.5}/test/expression/test_expression_sum.py +0 -0
  97. {wavepacket-0.4.0 → wavepacket-0.5}/test/expression/test_liouvillian.py +0 -0
  98. {wavepacket-0.4.0 → wavepacket-0.5}/test/expression/test_schroedingerequation.py +0 -0
  99. {wavepacket-0.4.0 → wavepacket-0.5}/test/grid/test_dofbase.py +0 -0
  100. {wavepacket-0.4.0 → wavepacket-0.5}/test/grid/test_planewavedof_basics.py +0 -0
  101. {wavepacket-0.4.0 → wavepacket-0.5}/test/grid/test_planewavedof_transformations.py +0 -0
  102. {wavepacket-0.4.0 → wavepacket-0.5}/test/grid/test_sphericalharmonicsdof_basics.py +0 -0
  103. {wavepacket-0.4.0 → wavepacket-0.5}/test/grid/test_sphericalharmonicsdof_transformations.py +0 -0
  104. {wavepacket-0.4.0 → wavepacket-0.5}/test/grid/test_state.py +0 -0
  105. {wavepacket-0.4.0 → wavepacket-0.5}/test/operator/test_clipping.py +0 -0
  106. {wavepacket-0.4.0 → wavepacket-0.5}/test/operator/test_fbroperators.py +0 -0
  107. {wavepacket-0.4.0 → wavepacket-0.5}/test/operator/test_misc_operators.py +0 -0
  108. {wavepacket-0.4.0 → wavepacket-0.5}/test/operator/test_operator_arithmetic.py +0 -0
  109. {wavepacket-0.4.0 → wavepacket-0.5}/test/operator/test_operatorbase.py +0 -0
  110. {wavepacket-0.4.0 → wavepacket-0.5}/test/operator/test_planewavefbroperator.py +0 -0
  111. {wavepacket-0.4.0 → wavepacket-0.5}/test/operator/test_potential1d.py +0 -0
  112. {wavepacket-0.4.0 → wavepacket-0.5}/test/operator/test_time_dependent_operators.py +0 -0
  113. {wavepacket-0.4.0 → wavepacket-0.5}/test/solver/test_chebychev.py +0 -0
  114. {wavepacket-0.4.0 → wavepacket-0.5}/test/solver/test_odesolver.py +0 -0
  115. {wavepacket-0.4.0 → wavepacket-0.5}/test/solver/test_solverbase.py +0 -0
  116. {wavepacket-0.4.0 → wavepacket-0.5}/test/special/test_generators.py +0 -0
  117. {wavepacket-0.4.0 → wavepacket-0.5}/test/test_base_exports.py +0 -0
  118. {wavepacket-0.4.0 → wavepacket-0.5}/test/test_logging.py +0 -0
  119. {wavepacket-0.4.0 → wavepacket-0.5}/test/test_operator_utilities.py +0 -0
@@ -2,6 +2,55 @@
2
2
  ChangeLog
3
3
  =========
4
4
 
5
+ 0.5
6
+ ---
7
+
8
+ Breaking changes:
9
+
10
+ Major changes:
11
+
12
+ - (#39) Added a PartialTraceTransformation to trace out all but one degree of freedom
13
+
14
+ - (#25) Added coupled channels consisting of a degree of freedom, a transformation,
15
+ two operators and a few convenience functions.
16
+
17
+ New documentation / examples:
18
+
19
+ - (#39) Effect of an environment on system states (decoherence) for a simple example
20
+ - (#25) Introduction and usage of coupled channels
21
+
22
+ Minor changes:
23
+
24
+ - documented use of atomic units in the code
25
+
26
+ Bug fixes:
27
+
28
+ - (#63) removed some warnings due to incorrect formula escaping
29
+ - fixed a type error with recent scipy versions when using a SphericalHarmonicsDof
30
+ - (#41) fixed broken references in the documentation
31
+
32
+ 0.4.1
33
+ -----
34
+
35
+ Minor changes:
36
+
37
+ - (#56) Added acceptance / regression tests, also for CI
38
+
39
+ Bug fixes:
40
+
41
+ - The SinSquare function had an incorrect implementation without the "square" part
42
+
43
+ - population() could return complex values for density operators.
44
+ The imaginary part was negligible, but the type was still wrong.
45
+ The return values are also no longer (negligible) negative values.
46
+
47
+ - log() calculated <x> only for the first degree of freedom,
48
+ and reported it for all other degrees of freedom as well.
49
+ Also, fixed a typo in the output.
50
+
51
+ - Stacked1DPlot threw an exception if you exceeded the number of plots.
52
+ It now overwrites the last plot as it is documented.
53
+
5
54
  0.4
6
55
  ---
7
56
 
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2024-2025 Ulf Lorenz
3
+ Copyright (c) 2024-2026 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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wavepacket
3
- Version: 0.4.0
3
+ Version: 0.5
4
4
  Summary: A package for solving Schrödinger and Liouville von Neumann equations.
5
5
  Keywords: wave packet,quantum simulation,quantum system,DVR,Schroedinger equation,Liouville von Neumann equation,open systems,wave function,density operator,Liouvillian
6
6
  Author: Ulf Lorenz
@@ -22,13 +22,11 @@ License-File: LICENSE
22
22
  Requires-Dist: numpy >= 2.1.3
23
23
  Requires-Dist: scipy >= 1.14.1
24
24
  Requires-Dist: matplotlib >= 3.10
25
- Requires-Dist: coverage >= 7 ; extra == "coverage"
26
25
  Requires-Dist: black == 26.* ; extra == "formatting"
27
26
  Requires-Dist: pytest >= 8.3 ; extra == "test"
28
27
  Project-URL: Homepage, https://wavepacket.readthedocs.io
29
28
  Project-URL: Issues, https://github.com/ulflor/wavepacket/issues
30
29
  Project-URL: Source, https://github.com/ulflor/wavepacket
31
- Provides-Extra: coverage
32
30
  Provides-Extra: formatting
33
31
  Provides-Extra: test
34
32
 
@@ -49,6 +47,7 @@ Wavepacket focuses on a particular niche:
49
47
  - Direct solution of Schrödinger / Liouville von Neumann equations.
50
48
  No electronic structure, no MCTDH, no semiclassics, at least for now.
51
49
  - Grid-based representation of operators (DVR approximation).
50
+ Atomic units to simplify equations.
52
51
  - Accessibility is an overriding goal. Wavepacket should be directly usable in teaching.
53
52
  We try to provide good tutorials / docs, but also goodies like simple plotting.
54
53
  - Exotic use cases should be as frictionless as possible.
@@ -74,7 +73,7 @@ Support
74
73
 
75
74
  There are three different avenues for feedback and support:
76
75
 
77
- - The prefered option is through the mailing list wavepacket@python.org.
76
+ - The preferred option is through the mailing list wavepacket@python.org.
78
77
  You may want to subscribe at https://mail.python.org/mailman3/lists/wavepacket.python.org/
79
78
  to ensure you receive all answers.
80
79
  - You can use the Forum on github: https://github.com/ulflor/wavepacket/discussions
@@ -94,7 +93,7 @@ I currently lack a formal procedure for new contributors, but you are
94
93
  very welcome to contribute to the project. If you do not know what to do,
95
94
  use one of the support channels from the previous section ; there is enough
96
95
  work for multiple developers, also for non-coding skills (there is never enough
97
- documentation).
96
+ documentation, for example).
98
97
 
99
98
 
100
99
  History
@@ -15,6 +15,7 @@ Wavepacket focuses on a particular niche:
15
15
  - Direct solution of Schrödinger / Liouville von Neumann equations.
16
16
  No electronic structure, no MCTDH, no semiclassics, at least for now.
17
17
  - Grid-based representation of operators (DVR approximation).
18
+ Atomic units to simplify equations.
18
19
  - Accessibility is an overriding goal. Wavepacket should be directly usable in teaching.
19
20
  We try to provide good tutorials / docs, but also goodies like simple plotting.
20
21
  - Exotic use cases should be as frictionless as possible.
@@ -40,7 +41,7 @@ Support
40
41
 
41
42
  There are three different avenues for feedback and support:
42
43
 
43
- - The prefered option is through the mailing list wavepacket@python.org.
44
+ - The preferred option is through the mailing list wavepacket@python.org.
44
45
  You may want to subscribe at https://mail.python.org/mailman3/lists/wavepacket.python.org/
45
46
  to ensure you receive all answers.
46
47
  - You can use the Forum on github: https://github.com/ulflor/wavepacket/discussions
@@ -60,7 +61,7 @@ I currently lack a formal procedure for new contributors, but you are
60
61
  very welcome to contribute to the project. If you do not know what to do,
61
62
  use one of the support channels from the previous section ; there is enough
62
63
  work for multiple developers, also for non-coding skills (there is never enough
63
- documentation).
64
+ documentation, for example).
64
65
 
65
66
 
66
67
  History
@@ -25,7 +25,7 @@ For the impatient, the main results of the following treatise are:
25
25
 
26
26
  ### ODE solvers theory
27
27
 
28
- We start from the Schrödinger equation
28
+ We start from the Schrödinger equation (we use atomic units throughout the text)
29
29
 
30
30
  $$
31
31
  \imath \dot \psi(t) = \hat H(t) \psi(t)
@@ -245,15 +245,13 @@ To answer this question, we can look at the projection of the truncated result o
245
245
  any deviation from one can serve as a figure of merit of the additional error.
246
246
 
247
247
  ```{code-cell}
248
- import math
249
-
250
248
  truncated_hamiltonian = (wp.operator.CartesianKineticEnergy(grid, 0, 1.0, cutoff=50)
251
249
  + wp.operator.Potential1D(grid, 0, lambda x: 0.5 * x ** 2, cutoff=50))
252
250
  truncated_equation = wp.expression.SchroedingerEquation(truncated_hamiltonian)
253
- truncated_solver = wp.solver.OdeSolver(truncated_equation, math.pi / 2)
251
+ truncated_solver = wp.solver.OdeSolver(truncated_equation, np.pi / 2)
254
252
  truncated_result = truncated_solver.step(psi0, 0)
255
253
 
256
- solver = wp.solver.OdeSolver(equation, math.pi/2)
254
+ solver = wp.solver.OdeSolver(equation, np.pi/2)
257
255
  result = solver.step(psi0, 0)
258
256
 
259
257
  print(f"Overlap after pi/2: {wp.population(truncated_result, result)}")
@@ -17,6 +17,7 @@ how to extract non-trivial data with minimal fuss and plot it.
17
17
 
18
18
  If a molecule interacts with a non-resonant laser field, the electronic ground state is shifted in energy.
19
19
  We can calculate the shift with standard perturbation theory and cavity-dressed states as
20
+ (we use atomic units throughout the text)
20
21
 
21
22
  $$
22
23
  \Delta E \propto - \sum_n \frac{|\langle \Phi_0 | \hat{\vec{\mu}} \vec E |\Phi_n \rangle|^2}{E_n - E_0 - \omega}
@@ -73,7 +74,6 @@ Note that at certain points in time, the rotor exhibits alignment recurrence eve
73
74
  This field-free alignment is used in practice because the molecule is aligned, yet undisturbed by external fields.
74
75
 
75
76
  ```{code-cell}
76
- import math
77
77
  import matplotlib.pyplot as plt
78
78
  import numpy as np
79
79
 
@@ -91,7 +91,7 @@ def calculate_alignment(Delta, sigma, l0=0, m=0):
91
91
  kinetic = wp.operator.RotationalKineticEnergy(grid, 0, 0.5)
92
92
  cos2 = wp.operator.Potential1D(grid, 0, lambda theta: np.cos(theta)**2)
93
93
  laser = wp.operator.TimeDependentOperator(grid,
94
- lambda t: Delta * math.exp(-(t-delay)**2/sigma**2))
94
+ lambda t: Delta * np.exp(-(t-delay)**2/sigma**2))
95
95
 
96
96
  hamiltonian = kinetic - 0.5 * cos2 * laser
97
97
  equation = wp.expression.SchroedingerEquation(hamiltonian)
@@ -143,9 +143,9 @@ There are, however, two major drawbacks:
143
143
  and you may need to converge this as well.
144
144
  * With the NIP term, the Hamiltonian is no longer self-adjoint.
145
145
  Some efficient solvers like the ChebychevSolver only work for self-adjoint Hamiltonians.
146
- The workaround here is to move the NIP out of the Hamiltonian,
146
+ A possible workaround here is to move the NIP out of the Hamiltonian,
147
147
  and absorb the wave packet after every propagation step.
148
- As of Wavepacket 0.4, this is currently not implemented conveniently, though.
148
+ As of Wavepacket 0.4, this is not implemented conveniently, though.
149
149
 
150
150
  Let us demonstrate the first point by adding a steep NIP to the Morse oscillator example:
151
151
 
@@ -25,6 +25,7 @@ was introduced by Huisinga et al. [^faber], but we omit this discussion for simp
25
25
  ## Basic theory
26
26
 
27
27
  For a time-independent, closed system, the time evolution operator is defined by
28
+ (we use atomic units throughout the text)
28
29
 
29
30
  $$
30
31
  \psi(t+\Delta t) = \hat U(\Delta t) \psi(t) = \mathrm{e}^{-\imath \hat H \Delta t} \psi(t).
@@ -96,10 +97,10 @@ We get for a normalized state
96
97
  We do not care about the exact value of the error, just note two properties of the expression:
97
98
 
98
99
  1. There is an upper bound for the error independent of the initial state. That is, the convergence is continuous,
99
- there are no individual states for which the series does not converge.
100
+ there are no singular states for which the series does not converge.
100
101
  2. For large enough N, the right-hand side decreases exponentially with increasing N.
101
102
 
102
- In practice, we do not target a specific value of the (upper bound of the) propagation error,
103
+ For simplicity, we do not target a specific value of the (upper bound of the) propagation error,
103
104
  but simply truncate the series as soon as the Bessel function has reached a certain cutoff.
104
105
  This cutoff can still be considered a proxy for the order of magnitude of the error,
105
106
  but is not a rigorous quantity.
@@ -117,7 +118,7 @@ The size of the time step, that is, the gain, however, is proportional to the al
117
118
  argument of said Bessel function.
118
119
 
119
120
  So we can rephrase the question: At which value of alpha do we get most gain per cost?
120
- Let us plot the behaviour of the Bessel functions for different values of alpha
121
+ Let us plot the behavior of the Bessel functions for different values of alpha
121
122
 
122
123
  ```{code-cell}
123
124
  import matplotlib.pyplot as plt
@@ -149,7 +150,7 @@ Hence, the proposition of using an alpha value of at least 40 to avoid inefficie
149
150
  of increasing alpha beyond, say, 100.
150
151
  Both values are not exact numbers, however, just crude rules of thumb.
151
152
 
152
- However, very large orders of expansion may lead to artefacts.
153
+ However, very large orders of expansion may lead to artifacts.
153
154
  For example the values of the Bessel functions may become inaccurate, depending on the implementation details.
154
155
  Therefore, we would recommend not increasing alpha beyond something like 100, but that is also not a hard value.
155
156
  For example, we encountered a Matlab version around 2010
@@ -166,7 +167,7 @@ so let us back this up with numbers.
166
167
  We choose the truncated harmonic oscillator example from {doc}`/tutorials/chebychev_solvers`.
167
168
 
168
169
  ```{code-cell}
169
- import math
170
+ import numpy as np
170
171
  import wavepacket as wp
171
172
 
172
173
  grid = wp.grid.Grid(wp.grid.PlaneWaveDof(-10, 10, 128))
@@ -200,17 +201,17 @@ Then we only wrap our (truncated) expression and propagate for a common time.
200
201
  ```{code-cell}
201
202
  counting_equation = CountingExpression(equation)
202
203
 
203
- solver_chebychev = wp.solver.ChebychevSolver(counting_equation, math.pi/2, (0, 70))
204
+ solver_chebychev = wp.solver.ChebychevSolver(counting_equation, np.pi/2, (0, 70))
204
205
  solver_chebychev.step(psi0, t=0)
205
206
  print(f"Chebychev solver: count={counting_equation.count}, alpha={solver_chebychev.alpha:.4}")
206
207
 
207
208
  counting_equation.count = 0
208
- solver_rk45 = wp.solver.OdeSolver(counting_equation, math.pi/2)
209
+ solver_rk45 = wp.solver.OdeSolver(counting_equation, np.pi/2)
209
210
  solver_rk45.step(psi0, t=0)
210
211
  print(f"Runge-Kutta 4/5 solver: count={counting_equation.count}")
211
212
 
212
213
  counting_equation.count = 0
213
- solver_rk45_precise = wp.solver.OdeSolver(counting_equation, math.pi/2, rtol=1e-9, atol=1e-9)
214
+ solver_rk45_precise = wp.solver.OdeSolver(counting_equation, np.pi/2, rtol=1e-9, atol=1e-9)
214
215
  solver_rk45_precise.step(psi0, t=0)
215
216
  print(f"High-precision Runge-Kutta 4/5 solver: count={counting_equation.count}")
216
217
  ```
@@ -0,0 +1,259 @@
1
+ ---
2
+ file_format: mystnb
3
+ kernelspec:
4
+ name: python3
5
+ ---
6
+
7
+ # Background theory: Reduced density operators
8
+
9
+ This web page can be downloaded as notebook: {nb-download}`reduced_density.ipynb` (Jupyter)
10
+ or {download}`reduced_density.md` (Markdown)
11
+
12
+ In this notebook, we investigate a toy problem that qualitatively demonstrates the effect of
13
+ an environment on a quantum measurement.
14
+ Basically, we discuss the mechanism behind coherent and incoherent superpositions,
15
+ that is shown in {doc}`/tutorials/schroedinger_cat`.
16
+
17
+ ## Theory: Partial trace
18
+
19
+ Let us consider an excerpt of the universe whose state can be described by a wave function.
20
+ At some point, we want to make a measurement, so we calculate an expectation value of the corresponding operator.
21
+ By inserting a summation over an orthonormal basis {math}`\phi`, we can rewrite this as a trace over
22
+ a (pure) density operator,
23
+
24
+ \begin{align*}
25
+ \langle \hat A \rangle &= \langle \psi | \hat A | \psi \rangle\\
26
+ &= \langle \psi | \hat A \sum_n|\phi_n\rangle \langle \phi_n | \psi \rangle\\
27
+ &= \sum_n \langle \phi_n | \psi \rangle \langle \psi | \hat A |\phi_n\rangle\\
28
+ &= \mathrm{Tr}(|\psi\rangle\langle\psi | \hat A) = \mathrm{Tr} (\hat \rho \hat A)
29
+ .
30
+ \end{align*}
31
+
32
+ Now we decompose our small universe into system and environment degrees of freedom, $\mathcal{H} = S \otimes E$
33
+ with respective orthonormal basis $\varphi, \eta$.
34
+ As "system", we denote those degrees of freedom along which our operator(s) act,
35
+ the rest we call "environment" or "bath".
36
+ By definition, the operator can be rewritten as $\hat A = \hat A_S \otimes \hat 1_E$,
37
+ where the subscripts denote action or integration over the system or environment part.
38
+ The trace now becomes
39
+
40
+ \begin{align*}
41
+ \langle \hat A \rangle &= \sum_{k,l} \Bigl(\langle \varphi_k | \otimes \langle \eta_l|\Bigr) \ \hat \rho
42
+ \hat A_S \otimes \hat 1_E \ \Bigl(|\varphi_k\rangle_S \otimes |\eta_l \rangle_E\Bigr)\\
43
+ &= \sum_k \langle \varphi_k | \ \Bigl( \sum_l \langle \eta_l | \hat \rho |\eta_l\rangle_E \Bigr)
44
+ \ \hat A_S |\varphi_k\rangle_S \\
45
+ &= \sum_k \langle \varphi_k | \hat \rho_\mathrm{red} \hat A_S | \varphi_k \rangle \\
46
+ &= \mathrm{Tr}_S ( \hat \varrho_\mathrm{red} \hat A_S) \qquad \qquad
47
+ \mathrm{with} \qquad \hat \varrho_\mathrm{red} = \mathrm{Tr}_E \hat \rho
48
+ \end{align*}
49
+
50
+ Even though our original state is pure and can be described by a wave function,
51
+ the reduced density may be impure/mixed and describe a statistical ensemble.
52
+ It can easily be checked that
53
+
54
+ \begin{align*}
55
+ \psi = (\varphi_0 + \varphi_1) \otimes \eta_0 \quad &\rightarrow \quad
56
+ \varrho_\mathrm{red} = |\varphi_0+\varphi_1\rangle\langle\varphi_0+\varphi_1|\\
57
+ \psi = \varphi_0 \otimes \eta_0 + \varphi_1 \otimes \eta_1 \quad &\rightarrow \quad
58
+ \varrho_\mathrm{red} = |\varphi_0\rangle\langle\varphi_0| + |\varphi_1\langle\rangle \varphi_1|
59
+ \end{align*}
60
+
61
+ That is, we get a pure reduced density if $\psi$ is a product state,
62
+ and a mixed reduced density if $\psi$ contains correlations between the system and environment.
63
+
64
+ ## The system under study
65
+
66
+ With the basic theory result spelled out, let us look at a toy example, where we can observe these effects,
67
+ yet still understand the full dynamics.
68
+ We use one-dimensional harmonic oscillators for the system and environment, respectively.
69
+ Let us start with the grid definitions:
70
+
71
+ ```{code-cell}
72
+ import wavepacket as wp
73
+
74
+ system_dof = wp.grid.PlaneWaveDof(-10, 10, 96)
75
+ environment_dof = wp.grid.PlaneWaveDof(-5, 5, 96)
76
+ full_grid = wp.grid.Grid([system_dof, environment_dof])
77
+
78
+ trafo = wp.grid.PartialTraceTransformation(full_grid, 0)
79
+ system_grid = trafo.target_grid
80
+ ```
81
+
82
+ We exploit here that the transformation object creates a one-dimensional "target grid"
83
+ containing only the first (/system) degree of freedom during setup.
84
+ We will use this transformation later to calculate the reduced density operator
85
+ from a state defined on the system+environment grid.
86
+
87
+ For system and environment, we define different harmonic oscillators
88
+
89
+ ```{code-cell}
90
+ kinetic_sys = wp.operator.CartesianKineticEnergy(system_grid, 0, mass=1, cutoff=40)
91
+ potential_sys = wp.operator.Potential1D(system_grid, 0, lambda x: 0.5 * x**2, cutoff=40)
92
+
93
+ kinetic_full = (wp.operator.CartesianKineticEnergy(full_grid, 0, mass=1, cutoff=40)
94
+ + wp.operator.CartesianKineticEnergy(full_grid, 1, mass=1, cutoff=40))
95
+ potential_full = (wp.operator.Potential1D(full_grid, 0, lambda x: 0.5 * x**2, cutoff=40)
96
+ + wp.operator.Potential1D(full_grid, 1, lambda x: 0.5 * (2*x)**2, cutoff=40))
97
+ ```
98
+
99
+ ## Dynamics with and without environment
100
+
101
+ ### Product state without coupling
102
+
103
+ To demonstrate the theoretical results, we start with an environment not coupled to the system,
104
+ and with a product state.
105
+ For the initial system states, we choose a squeezed and shifted Gaussian that oscillates in the system potential.
106
+ For the environment state, we choose some Gaussian centered around the potential minimum.
107
+
108
+ ```{code-cell}
109
+ import math
110
+
111
+ left_gauss = wp.special.Gaussian(-5, 0, rms=0.5)
112
+ right_gauss = wp.special.Gaussian(5, 0, rms=0.5)
113
+ env_gauss = wp.special.Gaussian(0, 0, rms=0.5)
114
+
115
+ psi0_sys = math.sqrt(0.5) * (
116
+ wp.builder.product_wave_function(system_grid, left_gauss)
117
+ + wp.builder.product_wave_function(system_grid, right_gauss)
118
+ )
119
+
120
+ psi0_full = math.sqrt(0.5) * (
121
+ wp.builder.product_wave_function(full_grid, [left_gauss, env_gauss])
122
+ + wp.builder.product_wave_function(full_grid, [right_gauss, env_gauss])
123
+ )
124
+ ```
125
+
126
+ As reference, we start with a simulation of only the system degree of freedom.
127
+
128
+ ```{code-cell}
129
+ equation_sys = wp.expression.SchroedingerEquation(kinetic_sys + potential_sys)
130
+
131
+ solver = wp.solver.ChebychevSolver(equation_sys, math.pi/6, (0, 80))
132
+ plotter = wp.plot.StackedPlot1D(4, psi0_sys, potential_sys, kinetic_sys + potential_sys)
133
+ for t, psi in solver.propagate(psi0_sys, 0.0, 3):
134
+ plotter.plot(t, psi);
135
+ ```
136
+
137
+ The two Gaussians breathe and oscillate in the potential and show interference when they cross paths.
138
+ This is just textbook quantum mechanics, no surprises so far.
139
+
140
+ Now what happens if we add an environment degree of freedom?
141
+ We start with a product state, and because of the missing system-environment coupling,
142
+ our solution remains a product state.
143
+ Hence, we expect a pure reduced density matrix that just replicates the
144
+ system-only result including interferences.
145
+
146
+ ```{code-cell}
147
+ equation_uncoupled = wp.expression.SchroedingerEquation(kinetic_full + potential_full)
148
+
149
+ solver = wp.solver.ChebychevSolver(equation_uncoupled, math.pi/6, (0, 160))
150
+ plotter = wp.plot.StackedPlot1D(4, psi0_sys, potential_sys, kinetic_sys + potential_sys)
151
+ for t, psi in solver.propagate(psi0_full, 0.0, 3):
152
+ reduced_density = trafo.transform(psi)
153
+ plotter.plot(t, reduced_density);
154
+ ```
155
+
156
+ This result also makes sense for philosophical reasons.
157
+ If the addition of an arbitrary, uninteresting degree of freedom would change the results,
158
+ we could modify any calculation results by, say, adding the phase of the moon
159
+ which is an obviously silly proposition.
160
+
161
+ ### Couplings and correlations
162
+
163
+ For the environment to have an effect on the system dynamics, we need to introduce a coupling.
164
+ To understand what happens now, let us first have a look at the two-dimensional dynamics.
165
+
166
+ ```{code-cell}
167
+ coupling = -0.5 * (wp.operator.Potential1D(full_grid, 0, lambda x: x)
168
+ * wp.operator.Potential1D(full_grid, 1, lambda x: x))
169
+ equation_coupled = wp.expression.SchroedingerEquation(kinetic_full + potential_full + coupling)
170
+
171
+ # store the results, we iterate twice over them, but want to calculate only once.
172
+ solver = wp.solver.ChebychevSolver(equation_coupled, math.pi/6, (0, 210))
173
+ results = [(t, psi) for t, psi in solver.propagate(psi0_full, 0.0, 3)]
174
+
175
+ plotter = wp.plot.StackedContourPlot2D(2, 2, psi0_full, potential_full + coupling)
176
+ plotter.contours /= 2
177
+ for t, psi in results:
178
+ plotter.plot(t, psi)
179
+ ```
180
+
181
+ Adding a bilinear coupling to a harmonic oscillator results in a harmonic oscillator
182
+ with different frequencies, and rotated normal modes.
183
+ For the parameters here, we see that the new normal modes are roughly
184
+ $n_0 \approx x_s + 0.5 x_e$ and $n_1 \approx x_s - 0.5 x_e$.
185
+
186
+ The two Gaussians that form our initial state move along these new normal modes, and now it gets interesting.
187
+ From a purely formal perspective, we get correlations between the system and environment.
188
+ That is, the total wave function is clearly no longer a direct product of a system and an environment wave function.
189
+ As discussed in the theory section, we expect a mixed density operator and a suppression of interferences.
190
+
191
+ We can also look at the situation from a different angle.
192
+ Because the two Gaussians move along the new normal modes, they mostly miss each other,
193
+ and barely overlap / interfere in the system-environment space.
194
+ The operation of tracing out the environment just projects this situation onto the system axis;
195
+ basically as if you "looked onto a projection of the plot while standing on the system axis".
196
+
197
+ ```{code-cell}
198
+ plotter = wp.plot.StackedPlot1D(4, psi0_sys, potential_sys, kinetic_sys + potential_sys)
199
+ for t, psi in results:
200
+ reduced_density = trafo.transform(psi)
201
+ plotter.plot(t, reduced_density);
202
+ ```
203
+
204
+ In a "real" system, we define observables for a few degrees of freedom at most,
205
+ while the environment around the system has a gazillion degrees of freedom.
206
+ There is then much more "space" for different parts of the total wave function to avoid each other.
207
+ After a while (the "decoherence time"), the likelihood of different parts of
208
+ the wave function overlapping in the system+environment space is negligible,
209
+ and interferences vanish.
210
+
211
+ Note, however, that we introduced a pretty strong system-environment coupling to force visible effects.
212
+ In practical systems, such couplings may be weak, giving significant experimental time scales during which you
213
+ can safely ignore environment effects.
214
+
215
+ But even the weakest coupling affects the initial state.
216
+ Typically, at the start of an experiment, the system has spent plenty of time interacting with the environment;
217
+ it is "thermalized".
218
+ And this interaction has introduced correlations between the system and the environment into the initial state,
219
+ whose reduced density matrix is therefore no longer pure.
220
+ Let us set up such a prototypical situation here again: We use a correlated initial state,
221
+ and ignore coupling between system and environment.
222
+
223
+ ```{code-cell}
224
+ psi0_correlated = math.sqrt(0.5) * (
225
+ wp.builder.product_wave_function(full_grid, [left_gauss, wp.special.Gaussian(-1.5, 0, rms=0.5)])
226
+ + wp.builder.product_wave_function(full_grid, [right_gauss, env_gauss])
227
+ )
228
+
229
+
230
+ # store the results, we iterate twice over them, but want to calculate only once.
231
+ solver = wp.solver.ChebychevSolver(equation_uncoupled, math.pi/6, (0, 180))
232
+ results = [(t, psi) for t, psi in solver.propagate(psi0_correlated, 0.0, 3)]
233
+
234
+ plotter = wp.plot.StackedContourPlot2D(2, 2, psi0_correlated, potential_full)
235
+ plotter.contours /= 2
236
+ for t, psi in results:
237
+ plotter.plot(t, psi)
238
+ ```
239
+
240
+ The "right" Gaussian oscillates only along the system degree of freedom,
241
+ while the "left" Gaussian follows a trajectory from the "bottom left" to the "center top".
242
+ As a consequence, these two wave packets again miss each other, and the interference pattern is suppressed.
243
+ If we look at the system degree of freedom again, we find exactly this pattern:
244
+
245
+ ```{code-cell}
246
+ plotter = wp.plot.StackedPlot1D(4, psi0_sys, potential_sys, kinetic_sys + potential_sys)
247
+ for t, psi in results:
248
+ reduced_density = trafo.transform(psi)
249
+ plotter.plot(t, reduced_density);
250
+ ```
251
+
252
+ While interference patterns _within_ each Gaussian are retained during the simulation (no coupling),
253
+ there is no interference _between_ the two Gaussians.
254
+ Or in other words, even though we can ignore the environment during the simulation, we need to
255
+ take it into account in the initial state.
256
+ Following this thread of thought much further eventually leads to the
257
+ expression of thermal states with mixed density operators,
258
+ without interference terms between the system Hamiltonian's eigentstates,
259
+ but that goes well beyond such a small Jupyter notebook.
@@ -14,6 +14,7 @@ or {download}`thermal_states.md` (Markdown)
14
14
  Sometimes, we wish to study systems at finite temperatures.
15
15
  To describe such systems, we need either a density operator or an ensemble of wave functions.
16
16
  The textbook expression for a thermal state is a density operator
17
+ (we use atomic units throughout the text)
17
18
 
18
19
  $$
19
20
  \hat \varrho_\mathrm{th} = \frac{1}{Z} \ \mathrm{e}^{- \beta \hat H}
@@ -49,7 +50,7 @@ but the path to more complex setups should be readily apparent from the formulas
49
50
  As example system, we calculate the temperature-dependent energy of a Morse oscillator,
50
51
  with parameters chosen for an OH radical.
51
52
  The system is simple, but not entirely trivial.
52
- Because we are going to use the {py:class}`RelaxationSolver`,
53
+ Because we are going to use the {py:class}`wavepacket.solver.RelaxationSolver`,
53
54
  we truncate the operators at energies well beyond the dissociation energy.
54
55
  This has the side effect of providing us bounds for the spectrum of the Hamiltonian that we can use later.
55
56
 
@@ -211,7 +212,7 @@ explicit solver.
211
212
 
212
213
  ### Implementation
213
214
 
214
- For simplicity, we choose to obtain the eigenstates here from diagonalising the Hamiltonian matrix.
215
+ For simplicity, we choose to obtain the eigenstates here from diagonalizing the Hamiltonian matrix.
215
216
  This is not a good choice performance-wise, but keeps the example focused.
216
217
 
217
218
  Conceptually, we need two passes.
@@ -276,7 +277,7 @@ complexity.
276
277
  One solution is the use of imaginary-time relaxation to get the lowest few eigenstates.
277
278
  This method is discussed in {doc}`/tutorials/relaxation`.
278
279
  Curiously, the relaxation limitation to few excited eigenstates aligns naturally
279
- with the this method's limitation to few excited eigenstates.
280
+ with this method's limitation to few excited eigenstates.
280
281
 
281
282
  If relaxation is difficult, you can simply choose a different basis.
282
283
  After all, we do not need energy eigenstates, we need to cover the relevant low-energy subspace.
@@ -326,7 +327,7 @@ $$
326
327
  Random wave functions are only useful for certain manipulations, such as replacing a unit operator.
327
328
  You must not confuse them with ordinary wave functions,
328
329
  as not all operations are well-defined.
329
- For example, it is easy to check that they do not have a finite norm.
330
+ For example, it is easy to check that they are not normalized.
330
331
  ```
331
332
 
332
333
  We now take again [the response expression](#eq_response), split the exponential,
@@ -361,7 +362,7 @@ On first view, this method may look like a slightly deranged variant of the prev
361
362
  So how, and especially why and when does it work?
362
363
  The hand-waving answer is that this is almost method II, but
363
364
  the ensemble of random wave functions (i) is not orthogonal (bad),
364
- and (ii) covers the "typical" Hilbert space space with few wave functions already (good).
365
+ and (ii) covers the "typical" Hilbert space with few wave functions already (good).
365
366
 
366
367
  Non-orthogonality is uniformly bad; it means for example that you may count contributions twice,
367
368
  and this problem only averages out over large samples.
@@ -383,7 +384,7 @@ where the phase is drawn uniformly from the interval $\phi \in [-\pi, \pi]$.
383
384
  The overall implementation is similar to that of method II with three differences.
384
385
 
385
386
  1. Instead of calculating eigenstates, we generate random wave functions using
386
- {py:func}`wp.builder.random_wave_function`.
387
+ {py:func}`wavepacket.builder.random_wave_function`.
387
388
  2. We need to explicitly relax the states.
388
389
  3. We do not sum the individual contributions, but average.
389
390
 
@@ -27,7 +27,7 @@ To get there, the code ended up with a few common concepts.
27
27
  and we did not cover every potential pitfall. Avoid breaking this guarantee, however,
28
28
  because doing so can corrupt object consistency (and therefore results) in
29
29
  subtle ways. In particular, if you create a :py:class:`wavepacket.grid.State`
30
- directly from a numpy array (very advanced use case), dispose the array and
30
+ directly from a numpy array (very advanced use case), forget about this array and
31
31
  do not modify it afterwards.
32
32
 
33
33
 
@@ -9,7 +9,7 @@
9
9
  project = "Wavepacket"
10
10
  copyright = "2024-%Y, Ulf Lorenz"
11
11
  author = "Ulf Lorenz"
12
- release = "0.4"
12
+ release = "0.5"
13
13
 
14
14
  # -- General configuration ---------------------------------------------------
15
15
  # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
@@ -19,6 +19,7 @@ Wavepacket documentation
19
19
  tutorials/schroedinger_cat
20
20
  tutorials/eigenstates
21
21
  tutorials/plotting
22
+ tutorials/channels
22
23
  tutorials/chebychev_solvers
23
24
  tutorials/relaxation
24
25
 
@@ -28,6 +29,7 @@ Wavepacket documentation
28
29
 
29
30
  advanced/pendular_states
30
31
  advanced/thermal_states
32
+ advanced/reduced_density
31
33
  advanced/plane_wave_grid
32
34
  advanced/ode_solvers
33
35
  advanced/polynomial_solvers