axionbloch 0.2.3__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 (51) hide show
  1. axionbloch-0.2.3/LICENSE +19 -0
  2. axionbloch-0.2.3/MANIFEST.in +3 -0
  3. axionbloch-0.2.3/PKG-INFO +74 -0
  4. axionbloch-0.2.3/README.md +44 -0
  5. axionbloch-0.2.3/axionbloch/Apparatus.py +221 -0
  6. axionbloch-0.2.3/axionbloch/EarthBoundAxionHalo.py +763 -0
  7. axionbloch-0.2.3/axionbloch/FineGrainedAxionStream.py +306 -0
  8. axionbloch-0.2.3/axionbloch/GravBoundAxionHalo.py +1234 -0
  9. axionbloch-0.2.3/axionbloch/MilkyWay.py +1017 -0
  10. axionbloch-0.2.3/axionbloch/MilkyWayAxionHalo.py +494 -0
  11. axionbloch-0.2.3/axionbloch/Sample.py +215 -0
  12. axionbloch-0.2.3/axionbloch/SimuTools.py +1990 -0
  13. axionbloch-0.2.3/axionbloch/SimuTypes.py +108 -0
  14. axionbloch-0.2.3/axionbloch/Station.py +314 -0
  15. axionbloch-0.2.3/axionbloch/__init__.py +35 -0
  16. axionbloch-0.2.3/axionbloch/constants.py +32 -0
  17. axionbloch-0.2.3/axionbloch/data/Earth_Models/PREM.md +41 -0
  18. axionbloch-0.2.3/axionbloch/data/Earth_Models/PREM_data.txt +752 -0
  19. axionbloch-0.2.3/axionbloch/dependency.py +67 -0
  20. axionbloch-0.2.3/axionbloch/kits/CASPEr_G_HF.py +0 -0
  21. axionbloch-0.2.3/axionbloch/kits/CASPEr_G_LF.py +0 -0
  22. axionbloch-0.2.3/axionbloch/kits/DataAnalysis.py +7930 -0
  23. axionbloch-0.2.3/axionbloch/kits/LockInAmp.py +0 -0
  24. axionbloch-0.2.3/axionbloch/kits/Simu_Signal.py +137 -0
  25. axionbloch-0.2.3/axionbloch/kits/__init__.py +0 -0
  26. axionbloch-0.2.3/axionbloch/kits/utils.py +3383 -0
  27. axionbloch-0.2.3/axionbloch/utils.py +3067 -0
  28. axionbloch-0.2.3/axionbloch.egg-info/PKG-INFO +74 -0
  29. axionbloch-0.2.3/axionbloch.egg-info/SOURCES.txt +49 -0
  30. axionbloch-0.2.3/axionbloch.egg-info/dependency_links.txt +1 -0
  31. axionbloch-0.2.3/axionbloch.egg-info/not-zip-safe +1 -0
  32. axionbloch-0.2.3/axionbloch.egg-info/requires.txt +20 -0
  33. axionbloch-0.2.3/axionbloch.egg-info/top_level.txt +1 -0
  34. axionbloch-0.2.3/blochSimulation_ext/bloch.cpp +327 -0
  35. axionbloch-0.2.3/blochSimulation_ext/bloch.h +90 -0
  36. axionbloch-0.2.3/blochSimulation_ext/bloch_wrapper.cpp +195 -0
  37. axionbloch-0.2.3/pyproject.toml +67 -0
  38. axionbloch-0.2.3/setup.cfg +4 -0
  39. axionbloch-0.2.3/setup.py +50 -0
  40. axionbloch-0.2.3/tests/test_EarthBoundAxionHalo.py +59 -0
  41. axionbloch-0.2.3/tests/test_Magnet.py +48 -0
  42. axionbloch-0.2.3/tests/test_MilkyWay.py +347 -0
  43. axionbloch-0.2.3/tests/test_MilkyWayAxionHalo.py +440 -0
  44. axionbloch-0.2.3/tests/test_Sample.py +156 -0
  45. axionbloch-0.2.3/tests/test_calibrations_cw_nmr.py +435 -0
  46. axionbloch-0.2.3/tests/test_calibrations_free_decay.py +337 -0
  47. axionbloch-0.2.3/tests/test_calibrations_t1_hyperpolarized.py +337 -0
  48. axionbloch-0.2.3/tests/test_calibrations_t1_relaxation.py +324 -0
  49. axionbloch-0.2.3/tests/test_constants.py +28 -0
  50. axionbloch-0.2.3/tests/test_findGradients_EarthLocation.py +95 -0
  51. axionbloch-0.2.3/tests/test_numpy_unit_performance.py +243 -0
@@ -0,0 +1,19 @@
1
+ Creative Commons Attribution-NonCommercial 4.0 International (CC BY-NC 4.0)
2
+
3
+ Copyright (c) 2026 Yuzhe Zhang
4
+
5
+ You are free to:
6
+
7
+ - Share — copy and redistribute the material in any medium or format
8
+ - Adapt — remix, transform, and build upon the material
9
+
10
+ Under the following terms:
11
+
12
+ - Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made.
13
+ - NonCommercial — You may not use the material for commercial purposes.
14
+
15
+ No additional restrictions — You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits.
16
+
17
+ ---
18
+
19
+ Full license text: https://creativecommons.org/licenses/by-nc/4.0/legalcode
@@ -0,0 +1,3 @@
1
+ include blochSimulation_ext/*.h
2
+ include blochSimulation_ext/*.cpp
3
+ recursive-include axionbloch/data *
@@ -0,0 +1,74 @@
1
+ Metadata-Version: 2.4
2
+ Name: axionbloch
3
+ Version: 0.2.3
4
+ Summary: Simulation tool for axion-induced spin dynamics.
5
+ Author-email: Yuzhe Zhang <yuhzhang@uni-mainz.de>
6
+ License-Expression: MIT
7
+ Project-URL: Documentation, https://axionbloch.readthedocs.io/en/latest/
8
+ Project-URL: Source, https://github.com/Yuzhe98/AxionBloch
9
+ Requires-Python: >=3.10
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Requires-Dist: numpy
13
+ Requires-Dist: matplotlib
14
+ Requires-Dist: astropy
15
+ Requires-Dist: scipy
16
+ Requires-Dist: h5py
17
+ Provides-Extra: dev
18
+ Requires-Dist: pytest>=7.0; extra == "dev"
19
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
20
+ Requires-Dist: black>=23.0; extra == "dev"
21
+ Requires-Dist: ruff>=0.1; extra == "dev"
22
+ Provides-Extra: docs
23
+ Requires-Dist: sphinx>=5.0; extra == "docs"
24
+ Requires-Dist: sphinx-rtd-theme>=1.0; extra == "docs"
25
+ Requires-Dist: sphinx-autodoc-typehints>=1.0; extra == "docs"
26
+ Provides-Extra: test
27
+ Requires-Dist: pytest>=7.0; extra == "test"
28
+ Requires-Dist: pytest-cov>=4.0; extra == "test"
29
+ Dynamic: license-file
30
+
31
+ # AxionBloch
32
+ A package for simulating **axion**-induced spin dynamics based on **Bloch equations**, implemented in **Python** and **C++**.
33
+
34
+ Yuzhe Zhang - GSI HIM & Uni Mainz - yuhzhang@uni-mainz.de
35
+
36
+ 📖 Documentation: [axionbloch.readthedocs.io](https://axionbloch.readthedocs.io/en/latest/)
37
+
38
+ ## Features
39
+
40
+ In AxionBloch, the axion (including axionlike-particle or ALP) field acts like a **pseudomagnetic field** coupling to (nuclear) spins.
41
+
42
+ The package provides a **numerical platform** to study these subtle effects using Bloch-equation-based simulations.
43
+
44
+ ## Requirements
45
+
46
+ - Python >= 3.10
47
+
48
+ ### Dependencies
49
+
50
+ - **numpy** — Numerical computing
51
+ - **scipy** — Scientific computing and numerical integration
52
+ - **matplotlib** — Plotting and visualization
53
+ - **astropy** — Astronomical utilities
54
+ - **h5py** — HDF5 file I/O
55
+
56
+ ## Installation
57
+
58
+ Install from [PyPI](https://pypi.org/project/axionbloch/):
59
+ ```bash
60
+ pip install axionbloch
61
+ ```
62
+
63
+ Or build and install from source (requires a C++17 compiler for the simulation extension):
64
+ ```bash
65
+ git clone https://github.com/Yuzhe98/AxionBloch.git
66
+ cd AxionBloch
67
+ pip install .
68
+ ```
69
+
70
+ ## Quick Start
71
+ Examples can be found in the examples/.
72
+
73
+ ## License
74
+ Creative Commons Attribution-NonCommercial 4.0 International (CC BY-NC 4.0)
@@ -0,0 +1,44 @@
1
+ # AxionBloch
2
+ A package for simulating **axion**-induced spin dynamics based on **Bloch equations**, implemented in **Python** and **C++**.
3
+
4
+ Yuzhe Zhang - GSI HIM & Uni Mainz - yuhzhang@uni-mainz.de
5
+
6
+ 📖 Documentation: [axionbloch.readthedocs.io](https://axionbloch.readthedocs.io/en/latest/)
7
+
8
+ ## Features
9
+
10
+ In AxionBloch, the axion (including axionlike-particle or ALP) field acts like a **pseudomagnetic field** coupling to (nuclear) spins.
11
+
12
+ The package provides a **numerical platform** to study these subtle effects using Bloch-equation-based simulations.
13
+
14
+ ## Requirements
15
+
16
+ - Python >= 3.10
17
+
18
+ ### Dependencies
19
+
20
+ - **numpy** — Numerical computing
21
+ - **scipy** — Scientific computing and numerical integration
22
+ - **matplotlib** — Plotting and visualization
23
+ - **astropy** — Astronomical utilities
24
+ - **h5py** — HDF5 file I/O
25
+
26
+ ## Installation
27
+
28
+ Install from [PyPI](https://pypi.org/project/axionbloch/):
29
+ ```bash
30
+ pip install axionbloch
31
+ ```
32
+
33
+ Or build and install from source (requires a C++17 compiler for the simulation extension):
34
+ ```bash
35
+ git clone https://github.com/Yuzhe98/AxionBloch.git
36
+ cd AxionBloch
37
+ pip install .
38
+ ```
39
+
40
+ ## Quick Start
41
+ Examples can be found in the examples/.
42
+
43
+ ## License
44
+ Creative Commons Attribution-NonCommercial 4.0 International (CC BY-NC 4.0)
@@ -0,0 +1,221 @@
1
+ from functools import partial
2
+
3
+ from axionbloch.dependency import *
4
+ from axionbloch.utils import Lorentzian_0edge
5
+
6
+
7
+ class Magnet:
8
+ """DC magnetic field apparatus.
9
+
10
+ Models a static polarising magnet whose field may be spatially inhomogeneous.
11
+ Inhomogeneity is characterised by a Lorentzian lineshape with a given FWHM,
12
+ and the field distribution is discretised into ``numPt`` spin packets for
13
+ simulation.
14
+
15
+ Attributes
16
+ ----------
17
+ name : str
18
+ B0 : astropy Quantity
19
+ Nominal field strength.
20
+ direction : list or None
21
+ Unit vector along B0 (laboratory frame).
22
+ FWHM : astropy Quantity
23
+ Fractional field inhomogeneity (dimensionless, e.g. ppm).
24
+ B_spread : ndarray
25
+ Sampled B0 values for each spin packet.
26
+ ratios : ndarray
27
+ Fractional weight of each spin packet (sums to 1).
28
+ """
29
+
30
+ name: str
31
+ B0: Quantity | None = None
32
+ direction: list | None = None
33
+ FWHM: Quantity | None = None
34
+ numPt: float = 1
35
+ nFWHM: float = 10.0
36
+
37
+ def __init__(
38
+ self,
39
+ name="magnet",
40
+ B0: Quantity[unit.T] | None = None,
41
+ direction: list | None = None,
42
+ FWHM: Quantity[ppm] | None = None,
43
+ numPt: float = 1,
44
+ nFWHM: float = 10.0,
45
+ verbose: bool = False,
46
+ ):
47
+ """
48
+ Parameters
49
+ ----------
50
+ name : str
51
+ Human-readable label for this magnet.
52
+ B0 : Quantity
53
+ Nominal DC field strength (e.g. ``0.1 * unit.T``).
54
+ direction : list, optional
55
+ Unit vector specifying the field orientation in the lab frame.
56
+ FWHM : Quantity
57
+ Fractional full-width at half-maximum of the field inhomogeneity
58
+ (dimensionless, e.g. ``1e-6 * unit.one`` for 1 ppm).
59
+ numPt : int
60
+ Number of discrete spin packets used to model field inhomogeneity.
61
+ ``numPt=1`` treats the field as perfectly homogeneous.
62
+ nFWHM : float
63
+ Half-range of the field spread expressed in units of FWHM.
64
+ verbose : bool
65
+ Print diagnostic information.
66
+ """
67
+ logPrefix = f"[{self.__class__.__name__}.{self.__init__.__name__}]"
68
+ self.name = name
69
+ assert nFWHM >= 0
70
+ self.nFWHM = nFWHM
71
+ if B0 is None or FWHM is None:
72
+ raise ValueError(logPrefix + " B0 and FWHM must be provided")
73
+
74
+ self.B0 = B0
75
+ self.direction = direction
76
+ self.FWHM = FWHM
77
+ self.B0_nW = self.nFWHM * self.FWHM * self.B0
78
+ self.numPt = numPt
79
+ self.FWHM_B0 = (self.B0 * self.FWHM).to(unit.T)
80
+ self.B_spread = np.ones(1) * self.B0
81
+ self.ratios = np.ones(1)
82
+ self.setHomogeneity(verbose=verbose)
83
+
84
+ def setHomogeneity(
85
+ self,
86
+ # lineshape:str = "Lorentizan",
87
+ numPt: int | float = None,
88
+ showPlot: bool = False,
89
+ verbose: bool = False,
90
+ ):
91
+ """Set the spin-packet sampling of the field inhomogeneity.
92
+
93
+ Discretises a Lorentzian field distribution into ``numPt`` spin packets
94
+ using a non-uniform sampling scheme (density proportional to
95
+ ``|x|^(1/2)`` in normalized coordinates) to oversample the peak region.
96
+ Each packet is assigned a weight ``ratio`` equal to its fraction of the
97
+ total Lorentzian area so that weighted averages reproduce the continuous
98
+ lineshape.
99
+
100
+ For ``numPt == 1`` or zero FWHM the field is treated as homogeneous and
101
+ a single packet at ``B0`` with weight 1 is used.
102
+
103
+ Parameters
104
+ ----------
105
+ numPt : int or float, optional
106
+ Override the stored ``self.numPt``.
107
+ showPlot : bool
108
+ Display diagnostic histograms of the sampled B values and weights.
109
+ verbose : bool
110
+ Print diagnostic information.
111
+ """
112
+ logPrefix = f"[{self.__class__.__name__}.{self.setHomogeneity.__name__}]"
113
+ # update self.numPt if it is None
114
+ if numPt is not None:
115
+ self.numPt = max(1, int(numPt))
116
+ elif self.numPt is None:
117
+ self.numPt = 1
118
+ if verbose:
119
+ print(logPrefix, f"numPt = {numPt}")
120
+ print(logPrefix, f"self.numPt = {self.numPt}")
121
+ # homogeneous field
122
+ if self.numPt == 1 or self.FWHM_B0 == 0.0 or self.nFWHM == 0:
123
+ self.B_spread = np.ones(1) * self.B0
124
+ self.ratios = np.ones(1)
125
+ # inhomogeneous field
126
+ else:
127
+ pdf = partial(
128
+ Lorentzian_0edge,
129
+ center=self.B0,
130
+ FWHM=self.FWHM_B0,
131
+ area=1,
132
+ offset=0,
133
+ )
134
+ if verbose:
135
+ print(logPrefix, f"inhomogeneous field. self.numPt = {self.numPt}")
136
+ # uniform sampling over [-1, 1]
137
+ uni_samp = np.linspace(start=-1, stop=1, num=self.numPt, endpoint=True)
138
+ # transform uniform sampling to the desired distribution
139
+ self.B_spread = (
140
+ self.nFWHM * np.sign(uni_samp) * np.abs(uni_samp) ** 2
141
+ ) * self.FWHM_B0 + self.B0
142
+ if verbose:
143
+ print(logPrefix, f"B_spread.shape = {self.B_spread.shape}")
144
+ print(
145
+ f"{logPrefix} B_spread: {len(self.B_spread)} points "
146
+ f"range=[{self.B_spread.min():.6g}, {self.B_spread.max():.6g}]"
147
+ )
148
+
149
+ if showPlot:
150
+ fig = plt.figure(
151
+ figsize=(8.5 / 2.54, 6.5 / 2.54), dpi=300
152
+ ) # initialize a figure
153
+ gs = gridspec.GridSpec(
154
+ nrows=1, ncols=1
155
+ ) # create grid for multiple figures
156
+ ax = fig.add_subplot(gs[0, 0])
157
+ hist, bin_edges = np.histogram(self.B_spread, bins=30)
158
+ ax.plot(
159
+ (bin_edges[1:] - self.B0) / self.FWHM_B0,
160
+ hist,
161
+ label="",
162
+ marker="o",
163
+ color="goldenrod",
164
+ markeredgecolor="darkgoldenrod",
165
+ )
166
+ ax.set_xlabel("Magnetic field - $B_0$ (FWHM)")
167
+ ax.set_ylabel("Number of sampling points")
168
+ ax.set_title(
169
+ "Histogram of number of sampling \nover magnetic field spread"
170
+ )
171
+ plt.tight_layout()
172
+ # plt.savefig('example figure - one-column.png', transparent=False)
173
+ plt.show()
174
+
175
+ # Define interval edges
176
+ edges = np.zeros(len(self.B_spread) + 1) * self.B_spread[0].unit
177
+ edges[1:-1] = (self.B_spread[:-1] + self.B_spread[1:]) / 2
178
+ edges[0] = self.B_spread[0] - abs(edges[2] - edges[1]) / 2
179
+ edges[-1] = self.B_spread[-1] + abs(edges[-2] - edges[-3]) / 2
180
+
181
+ self.ratios = np.zeros(self.B_spread.shape)
182
+ for i in range(len(self.B_spread)):
183
+ a, b = edges[i], edges[i + 1]
184
+ x = np.linspace(a, b, 32)
185
+ self.ratios[i] = np.trapezoid(pdf(x), x)
186
+ # high-frequency spins can create wiggles in the magnetization signal
187
+ # to suppress wiggles, add hamming window to decrease the ratio of high-frequency spins
188
+ # this is not ideal or physical. However, in simulations, we cannot use extremely large nFWHM
189
+ # to account for the real magnetic field distribution. We truncate at edges, instead.
190
+ self.ratios *= np.hamming(len(self.ratios)) ** 2
191
+ # normalize ratios to sum to 1
192
+ self.ratios /= np.sum(self.ratios)
193
+
194
+ if verbose:
195
+ print(
196
+ f"{logPrefix} ratios computed sum={self.ratios.sum():.6g} "
197
+ f"min={self.ratios.min():.4g} max={self.ratios.max():.4g}"
198
+ )
199
+
200
+ if showPlot:
201
+ fig = plt.figure(figsize=(8.5 / 2.54, 6.5 / 2.54), dpi=300)
202
+ gs = gridspec.GridSpec(
203
+ nrows=1, ncols=1
204
+ ) # create grid for multiple figures
205
+ ax = fig.add_subplot(gs[0, 0])
206
+ ax.plot(
207
+ (self.B_spread - self.B0) / self.FWHM_B0,
208
+ self.ratios,
209
+ label="",
210
+ marker="o",
211
+ )
212
+ ax.set_xlabel("Magnetic field - $B_0$ (FWHM)")
213
+ ax.set_ylabel("")
214
+ ax.set_title(
215
+ "Histogram of spin packet ratio over magnetic field spread"
216
+ )
217
+ plt.tight_layout()
218
+ plt.show()
219
+
220
+ if verbose:
221
+ print(logPrefix, f"self.ratios normalized sum={self.ratios.sum():g}")