mbsolver 0.2.0__tar.gz → 0.3.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mbsolver
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Fast Maxwell-Bloch solvers for masers, superradiance and coherent pulse propagation
5
5
  Author-email: Vahid Anari <vahid.anari8@gmail.com>
6
6
  License-Expression: MIT
@@ -92,6 +92,29 @@ plt.show()
92
92
  `res.time`, `res.z` (positions of the stored planes), `res.intensity` and
93
93
  `res.w` (shape `(n_planes, nt)`), `res.pump`, `res.field_in`, `res.t1`, `res.t2`.
94
94
 
95
+ ## From physical values: `scaling`
96
+
97
+ The solvers take dimensionless parameters. `scaling` computes them from the
98
+ physical sample values (SI units): transition frequency `nu` (Hz), spontaneous
99
+ decay rate `gamma` (s⁻¹), time unit `t0` (s), length `l` (m) and inverted
100
+ population density `n0` (m⁻³).
101
+
102
+ ```python
103
+ import math
104
+ import mbsolver
105
+ from mbsolver.two_level import solve_1d
106
+
107
+ s = mbsolver.scaling(nu=6.67e9, gamma=1.56e-9, t0=86400, l=7.5e12, n0=5.3e-7)
108
+ res = solve_1d(t_max=2000, nt=20001, nz=101, eta=s["eta"], theta0=s["theta0"],
109
+ t1=math.inf, t2=math.inf)
110
+ ```
111
+
112
+ Besides `eta` (η = T₀/T_R) and `theta0` (θ₀ = 2/√N), the result holds `t_r`,
113
+ `N`, `omega`, `lambda`, `k`, `lambda0` (Λ₀ = 1/T₀) and the field, polarization
114
+ and intensity scales for an electric dipole (`d`, `e0`, `p0`, `i0_electric`)
115
+ and a magnetic dipole (`mu`, `b0`, `m0`, `i0_magnetic`). Times in the solver
116
+ are in units of `t0`.
117
+
95
118
  ## Profiles
96
119
 
97
120
  `Pulse(a, x0, w)` is a sum of sech² pulses, `a_i sech²((t − x0_i) / w_i)`.
@@ -113,7 +136,8 @@ pytest
113
136
 
114
137
  The tests include a reference run that must match the solver of the Maxwell
115
138
  Bloch app (version 8.13) exactly, and physics checks (Bloch rotation at the
116
- entrance, relaxation balance, uncoupled propagation).
139
+ entrance, relaxation balance, uncoupled propagation). `scaling` is checked
140
+ against the values of Maxwell Bloch Studio.
117
141
 
118
142
  ## Earlier name
119
143
 
@@ -61,6 +61,29 @@ plt.show()
61
61
  `res.time`, `res.z` (positions of the stored planes), `res.intensity` and
62
62
  `res.w` (shape `(n_planes, nt)`), `res.pump`, `res.field_in`, `res.t1`, `res.t2`.
63
63
 
64
+ ## From physical values: `scaling`
65
+
66
+ The solvers take dimensionless parameters. `scaling` computes them from the
67
+ physical sample values (SI units): transition frequency `nu` (Hz), spontaneous
68
+ decay rate `gamma` (s⁻¹), time unit `t0` (s), length `l` (m) and inverted
69
+ population density `n0` (m⁻³).
70
+
71
+ ```python
72
+ import math
73
+ import mbsolver
74
+ from mbsolver.two_level import solve_1d
75
+
76
+ s = mbsolver.scaling(nu=6.67e9, gamma=1.56e-9, t0=86400, l=7.5e12, n0=5.3e-7)
77
+ res = solve_1d(t_max=2000, nt=20001, nz=101, eta=s["eta"], theta0=s["theta0"],
78
+ t1=math.inf, t2=math.inf)
79
+ ```
80
+
81
+ Besides `eta` (η = T₀/T_R) and `theta0` (θ₀ = 2/√N), the result holds `t_r`,
82
+ `N`, `omega`, `lambda`, `k`, `lambda0` (Λ₀ = 1/T₀) and the field, polarization
83
+ and intensity scales for an electric dipole (`d`, `e0`, `p0`, `i0_electric`)
84
+ and a magnetic dipole (`mu`, `b0`, `m0`, `i0_magnetic`). Times in the solver
85
+ are in units of `t0`.
86
+
64
87
  ## Profiles
65
88
 
66
89
  `Pulse(a, x0, w)` is a sum of sech² pulses, `a_i sech²((t − x0_i) / w_i)`.
@@ -82,7 +105,8 @@ pytest
82
105
 
83
106
  The tests include a reference run that must match the solver of the Maxwell
84
107
  Bloch app (version 8.13) exactly, and physics checks (Bloch rotation at the
85
- entrance, relaxation balance, uncoupled propagation).
108
+ entrance, relaxation balance, uncoupled propagation). `scaling` is checked
109
+ against the values of Maxwell Bloch Studio.
86
110
 
87
111
  ## Earlier name
88
112
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "mbsolver"
7
- version = "0.2.0"
7
+ version = "0.3.0"
8
8
  description = "Fast Maxwell-Bloch solvers for masers, superradiance and coherent pulse propagation"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -2,16 +2,18 @@
2
2
 
3
3
  Profiles (pump pulses, boundary fields, relaxation-time schedules) are built
4
4
  with :class:`Pulse` and :class:`Steps`; the solvers live in model subpackages,
5
- e.g. ``mbsolver.two_level.solve_1d``.
5
+ e.g. ``mbsolver.two_level.solve_1d``. :func:`scaling` turns the physical
6
+ sample values into the solvers' ``eta`` and ``theta0``.
6
7
  """
7
8
 
8
9
  from importlib.metadata import PackageNotFoundError, version
9
10
 
10
11
  from mbsolver.profiles import Pulse, Steps
12
+ from mbsolver.sample import scaling
11
13
 
12
14
  try:
13
15
  __version__ = version("mbsolver")
14
16
  except PackageNotFoundError: # running from a source tree without install
15
17
  __version__ = "0+unknown"
16
18
 
17
- __all__ = ["Pulse", "Steps", "__version__"]
19
+ __all__ = ["Pulse", "Steps", "scaling", "__version__"]
@@ -0,0 +1,18 @@
1
+ """Physical constants in SI units, used by :mod:`mbsolver.sample`."""
2
+
3
+ import math
4
+
5
+ PI: float = math.pi
6
+ """Circle constant."""
7
+
8
+ H_BAR: float = 6.62607015e-34 / (2.0 * PI)
9
+ """Reduced Planck constant in joule-seconds."""
10
+
11
+ C: float = 2.99792458e8
12
+ """Speed of light in meters per second."""
13
+
14
+ MU_0: float = 4.0 * PI * 1e-7
15
+ """Vacuum permeability in henries per meter."""
16
+
17
+ EPSILON_0: float = 1.0 / (MU_0 * C**2)
18
+ """Vacuum permittivity in farads per meter."""
@@ -0,0 +1,74 @@
1
+ """From the physical sample to the solver's dimensionless parameters.
2
+
3
+ The solvers work in dimensionless units: time in units of ``T0``, the medium
4
+ from ``z = 0`` to ``z = 1``. :func:`scaling` turns the physical values of a
5
+ maser sample into the coupling ``eta`` and initial Bloch angle ``theta0`` the
6
+ solvers take, together with the other derived scales::
7
+
8
+ import math
9
+ import mbsolver
10
+ from mbsolver.two_level import solve_1d
11
+
12
+ s = mbsolver.scaling(nu=6.67e9, gamma=1.56e-9, t0=86400, l=7.5e12, n0=5.3e-7)
13
+ res = solve_1d(t_max=2000, nt=20001, nz=101, eta=s["eta"], theta0=s["theta0"],
14
+ t1=math.inf, t2=math.inf)
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import math
20
+ from typing import Dict
21
+
22
+ from mbsolver.constants import C, EPSILON_0, H_BAR, MU_0, PI
23
+
24
+ __all__ = ["scaling"]
25
+
26
+
27
+ def scaling(nu: float, gamma: float, t0: float, l: float, n0: float) -> Dict[str, float]:
28
+ """Compute the solver parameters and derived scales from the sample values.
29
+
30
+ Args:
31
+ nu: Transition frequency ν in Hz.
32
+ gamma: Spontaneous decay rate Γ in s⁻¹.
33
+ t0: Time scale T₀ in s (the solver's unit of time).
34
+ l: Length of the medium L in m.
35
+ n0: Inverted population density n₀ in m⁻³.
36
+
37
+ Returns:
38
+ Mapping, all in SI units:
39
+
40
+ - ``eta``: coupling coefficient η = T₀ / T_R (the solvers' ``eta``),
41
+ - ``theta0``: initial Bloch angle θ₀ = 2 / √N (the solvers' ``theta0``),
42
+ - ``t_r``: superradiance time scale T_R,
43
+ - ``N``: number of molecules in a column of one wavelength squared,
44
+ - ``omega``, ``lambda``, ``k``: angular frequency, wavelength, wave number,
45
+ - ``lambda0``: pump scale Λ₀ = 1 / T₀,
46
+ - electric dipole: ``d`` (dipole moment), ``e0`` (field scale),
47
+ ``p0`` (polarization scale), ``i0_electric`` (intensity scale),
48
+ - magnetic dipole: ``mu``, ``b0``, ``m0``, ``i0_magnetic``.
49
+ """
50
+ omega0 = 2 * PI * nu
51
+ k = omega0 / C
52
+ _lambda = C / nu
53
+ t_r = 2.0 * k**2 / (3.0 * PI * gamma * n0 * l)
54
+ N = n0 * _lambda * l**2
55
+ eta = t0 / t_r
56
+ theta0 = 2.0 / math.sqrt(N)
57
+ lambda0 = 1.0 / t0
58
+
59
+ d = math.sqrt(3 * PI * EPSILON_0 * H_BAR * gamma / k**3)
60
+ p0 = n0 * d
61
+ e0 = H_BAR / (t0 * d)
62
+ i0_electric = 0.5 * EPSILON_0 * C * e0**2
63
+
64
+ mu = math.sqrt(3 * PI * H_BAR * gamma / (MU_0 * k**3))
65
+ m0 = n0 * mu
66
+ b0 = H_BAR / (t0 * mu)
67
+ i0_magnetic = C / (2.0 * MU_0) * b0**2
68
+
69
+ return {
70
+ "omega": omega0, "lambda": _lambda, "k": k, "t_r": t_r, "eta": eta,
71
+ "N": N, "theta0": theta0, "lambda0": lambda0,
72
+ "d": d, "e0": e0, "p0": p0, "i0_electric": i0_electric,
73
+ "mu": mu, "b0": b0, "m0": m0, "i0_magnetic": i0_magnetic,
74
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mbsolver
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Fast Maxwell-Bloch solvers for masers, superradiance and coherent pulse propagation
5
5
  Author-email: Vahid Anari <vahid.anari8@gmail.com>
6
6
  License-Expression: MIT
@@ -92,6 +92,29 @@ plt.show()
92
92
  `res.time`, `res.z` (positions of the stored planes), `res.intensity` and
93
93
  `res.w` (shape `(n_planes, nt)`), `res.pump`, `res.field_in`, `res.t1`, `res.t2`.
94
94
 
95
+ ## From physical values: `scaling`
96
+
97
+ The solvers take dimensionless parameters. `scaling` computes them from the
98
+ physical sample values (SI units): transition frequency `nu` (Hz), spontaneous
99
+ decay rate `gamma` (s⁻¹), time unit `t0` (s), length `l` (m) and inverted
100
+ population density `n0` (m⁻³).
101
+
102
+ ```python
103
+ import math
104
+ import mbsolver
105
+ from mbsolver.two_level import solve_1d
106
+
107
+ s = mbsolver.scaling(nu=6.67e9, gamma=1.56e-9, t0=86400, l=7.5e12, n0=5.3e-7)
108
+ res = solve_1d(t_max=2000, nt=20001, nz=101, eta=s["eta"], theta0=s["theta0"],
109
+ t1=math.inf, t2=math.inf)
110
+ ```
111
+
112
+ Besides `eta` (η = T₀/T_R) and `theta0` (θ₀ = 2/√N), the result holds `t_r`,
113
+ `N`, `omega`, `lambda`, `k`, `lambda0` (Λ₀ = 1/T₀) and the field, polarization
114
+ and intensity scales for an electric dipole (`d`, `e0`, `p0`, `i0_electric`)
115
+ and a magnetic dipole (`mu`, `b0`, `m0`, `i0_magnetic`). Times in the solver
116
+ are in units of `t0`.
117
+
95
118
  ## Profiles
96
119
 
97
120
  `Pulse(a, x0, w)` is a sum of sech² pulses, `a_i sech²((t − x0_i) / w_i)`.
@@ -113,7 +136,8 @@ pytest
113
136
 
114
137
  The tests include a reference run that must match the solver of the Maxwell
115
138
  Bloch app (version 8.13) exactly, and physics checks (Bloch rotation at the
116
- entrance, relaxation balance, uncoupled propagation).
139
+ entrance, relaxation balance, uncoupled propagation). `scaling` is checked
140
+ against the values of Maxwell Bloch Studio.
117
141
 
118
142
  ## Earlier name
119
143
 
@@ -2,8 +2,10 @@ LICENSE
2
2
  README.md
3
3
  pyproject.toml
4
4
  src/mbsolver/__init__.py
5
+ src/mbsolver/constants.py
5
6
  src/mbsolver/grids.py
6
7
  src/mbsolver/profiles.py
8
+ src/mbsolver/sample.py
7
9
  src/mbsolver.egg-info/PKG-INFO
8
10
  src/mbsolver.egg-info/SOURCES.txt
9
11
  src/mbsolver.egg-info/dependency_links.txt
@@ -12,4 +14,5 @@ src/mbsolver.egg-info/top_level.txt
12
14
  src/mbsolver/two_level/__init__.py
13
15
  src/mbsolver/two_level/_kernel_1d.py
14
16
  tests/test_profiles.py
17
+ tests/test_sample.py
15
18
  tests/test_two_level.py
@@ -0,0 +1,61 @@
1
+ """Tests for mbsolver.scaling (physical sample -> solver parameters)."""
2
+
3
+ import math
4
+
5
+ import pytest
6
+
7
+ import mbsolver
8
+ from mbsolver.constants import C, PI
9
+
10
+ SAMPLE = dict(nu=6668.519e6, gamma=1.56e-9, t0=86400.0, l=7.5e12, n0=5.3e-7)
11
+
12
+ # Values from Maxwell Bloch Studio 10.0.3 (mbstudio.scaling) for SAMPLE.
13
+ REFERENCE = {
14
+ "N": 1.340262006320294e+18,
15
+ "b0": 1.8155752119506778e-18,
16
+ "d": 2.2424742818774253e-30,
17
+ "e0": 5.442957554745647e-10,
18
+ "eta": 0.12925265980137085,
19
+ "i0_electric": 3.9319621867605606e-22,
20
+ "i0_magnetic": 3.9319621867605615e-22,
21
+ "k": 139.76182349940206,
22
+ "lambda": 0.04495637757049204,
23
+ "lambda0": 1.1574074074074073e-05,
24
+ "m0": 3.5630674479188363e-28,
25
+ "mu": 6.722768769658182e-22,
26
+ "omega": 41899540601.44791,
27
+ "p0": 1.1885113693950354e-36,
28
+ "t_r": 668458.1975548919,
29
+ "theta0": 1.7275679662364094e-09,
30
+ }
31
+
32
+
33
+ def test_matches_studio():
34
+ s = mbsolver.scaling(**SAMPLE)
35
+ assert sorted(s) == sorted(REFERENCE)
36
+ for key, value in REFERENCE.items():
37
+ assert s[key] == pytest.approx(value, rel=1e-13), key
38
+
39
+
40
+ def test_relations():
41
+ s = mbsolver.scaling(**SAMPLE)
42
+ assert s["eta"] == pytest.approx(SAMPLE["t0"] / s["t_r"], rel=1e-15)
43
+ assert s["theta0"] == pytest.approx(2.0 / math.sqrt(s["N"]), rel=1e-15)
44
+ assert s["lambda"] == pytest.approx(C / SAMPLE["nu"], rel=1e-15)
45
+ assert s["k"] == pytest.approx(2 * PI / s["lambda"], rel=1e-14)
46
+ # electric and magnetic dipole give the same intensity scale
47
+ assert s["i0_electric"] == pytest.approx(s["i0_magnetic"], rel=1e-12)
48
+
49
+
50
+ def test_eta_scales_with_length_and_density():
51
+ s1 = mbsolver.scaling(**SAMPLE)
52
+ s2 = mbsolver.scaling(**{**SAMPLE, "l": 2 * SAMPLE["l"], "n0": 3 * SAMPLE["n0"]})
53
+ assert s2["eta"] == pytest.approx(6 * s1["eta"], rel=1e-14)
54
+
55
+
56
+ def test_feeds_the_solver():
57
+ from mbsolver.two_level import solve_1d
58
+
59
+ s = mbsolver.scaling(**SAMPLE)
60
+ res = solve_1d(t_max=10, nt=101, nz=11, eta=s["eta"], theta0=s["theta0"], t1=math.inf, t2=math.inf)
61
+ assert res.intensity.shape == (2, 101)
File without changes
File without changes
File without changes