scipas 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.
Files changed (41) hide show
  1. scipas-0.3.0/LICENSE +21 -0
  2. scipas-0.3.0/PKG-INFO +231 -0
  3. scipas-0.3.0/README.md +209 -0
  4. scipas-0.3.0/pyproject.toml +38 -0
  5. scipas-0.3.0/scipas/__init__.py +23 -0
  6. scipas-0.3.0/scipas/analysis/__init__.py +3 -0
  7. scipas-0.3.0/scipas/analysis/vedb/__init__.py +5 -0
  8. scipas-0.3.0/scipas/analysis/vedb/annihilation_fractions.py +105 -0
  9. scipas-0.3.0/scipas/analysis/vedb/diffusion_length.py +268 -0
  10. scipas-0.3.0/scipas/analysis/vedb/lineshape.py +167 -0
  11. scipas-0.3.0/scipas/analysis/vedb/ve_implanation.py +86 -0
  12. scipas-0.3.0/scipas/core/__init__.py +4 -0
  13. scipas-0.3.0/scipas/core/cdb.py +219 -0
  14. scipas-0.3.0/scipas/core/const.py +3 -0
  15. scipas-0.3.0/scipas/core/db.py +349 -0
  16. scipas-0.3.0/scipas/filter/__init__.py +8 -0
  17. scipas-0.3.0/scipas/filter/pals_coincidence.py +1 -0
  18. scipas-0.3.0/scipas/filter/pas_coincidence.py +215 -0
  19. scipas-0.3.0/scipas/libs/__init__.py +0 -0
  20. scipas-0.3.0/scipas/libs/positron_profile/__init__.py +0 -0
  21. scipas-0.3.0/scipas/libs/positron_profile/gosh_profile_parameters.txt +35 -0
  22. scipas-0.3.0/scipas/libs/positron_profile/makhov_profile_parameters.txt +35 -0
  23. scipas-0.3.0/scipas/model/__init__.py +5 -0
  24. scipas-0.3.0/scipas/model/layer.py +43 -0
  25. scipas-0.3.0/scipas/model/lifetime.py +53 -0
  26. scipas-0.3.0/scipas/model/material.py +86 -0
  27. scipas-0.3.0/scipas/model/sample.py +90 -0
  28. scipas-0.3.0/scipas/transport/__init__.py +4 -0
  29. scipas-0.3.0/scipas/transport/diffusion/__init__.py +3 -0
  30. scipas-0.3.0/scipas/transport/diffusion/positron_profile_solver.py +229 -0
  31. scipas-0.3.0/scipas/transport/implantation/__init__.py +4 -0
  32. scipas-0.3.0/scipas/transport/implantation/material_parameters.py +59 -0
  33. scipas-0.3.0/scipas/transport/implantation/multilayer.py +143 -0
  34. scipas-0.3.0/scipas/transport/implantation/profiles.py +102 -0
  35. scipas-0.3.0/scipas/transport/implantation/utils.py +39 -0
  36. scipas-0.3.0/scipas.egg-info/PKG-INFO +231 -0
  37. scipas-0.3.0/scipas.egg-info/SOURCES.txt +39 -0
  38. scipas-0.3.0/scipas.egg-info/dependency_links.txt +1 -0
  39. scipas-0.3.0/scipas.egg-info/requires.txt +10 -0
  40. scipas-0.3.0/scipas.egg-info/top_level.txt +1 -0
  41. scipas-0.3.0/setup.cfg +4 -0
scipas-0.3.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Achiya Yosef Amrusi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
scipas-0.3.0/PKG-INFO ADDED
@@ -0,0 +1,231 @@
1
+ Metadata-Version: 2.4
2
+ Name: scipas
3
+ Version: 0.3.0
4
+ Summary: Positron Annihilation Spectroscopy analysis tools
5
+ Author-email: Achiya Yosef Amrusi <ahia.amrosi@mail.huji.ac.il>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/achiyaAmrusi/scipas
8
+ Project-URL: Repository, https://github.com/achiyaAmrusi/scipas
9
+ Requires-Python: >=3.11
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Requires-Dist: numpy<3.0,>=2.0.0
13
+ Requires-Dist: pandas<4.0,>=2.3
14
+ Requires-Dist: scipy>=1.14.0
15
+ Requires-Dist: xarray>=2024.6.0
16
+ Requires-Dist: uncertainties>=3.1
17
+ Requires-Dist: scispectrum>=0.3
18
+ Provides-Extra: dev
19
+ Requires-Dist: pytest>=8.0; extra == "dev"
20
+ Requires-Dist: pytest-cov; extra == "dev"
21
+ Dynamic: license-file
22
+
23
+ # SciPAS: Positron Annihilation Spectroscopy in Python
24
+
25
+ A Python package for **Doppler Broadening (DB)** and **Coincidence Doppler Broadening (CDB)** analysis, positron implantation profiling, transport simulation, and variable-energy Doppler broadening (VEDB) diffusion-length fitting.
26
+
27
+ SciPAS provides a unified, modular workflow — from raw detector data to material parameters — built on standard scientific Python.
28
+
29
+ ---
30
+
31
+ ## Features
32
+
33
+ - **DB spectrum analysis** — S and W parameter extraction with Poisson uncertainty propagation; automatic 511 keV peak identification and axis centralization
34
+ - **CDB analysis** — 2D coincidence histogram; DB and resolution projections from detector-pair data
35
+ - **Event filtering** — time and energy coincidence filtering for synchronized detector pairs
36
+ - **Implantation profiles** — Makhovian and Ghosh positron stopping profiles; multilayer cumulative stitching; support for external (MC-simulated) profiles
37
+ - **Positron transport solver** — 1D finite-difference solver for the diffusion–drift–annihilation equation with electric fields and radiative boundary conditions
38
+ - **Layered sample model** — `Sample` / `Layer` / `Material` descriptors with depth-dependent diffusion, mobility, and annihilation rates
39
+ - **VEDB fitting** — diffusion-length optimization with covariance estimation; single- and multi-layer support; weighted nonlinear least squares
40
+ - **xarray throughout** — labeled, sliceable data with named coordinates
41
+ - **Uncertainty propagation** via the `uncertainties` library
42
+
43
+ ---
44
+
45
+ ## Installation
46
+
47
+ ```bash
48
+ git clone https://github.com/achiyaAmrusi/scipas
49
+ cd scipas
50
+ pip install .
51
+ ```
52
+
53
+ For a development install (includes test dependencies):
54
+
55
+ ```bash
56
+ pip install ".[dev]"
57
+ ```
58
+
59
+ The companion spectrum library [scispectrum](https://github.com/achiyaAmrusi/scispectrum) is installed automatically from PyPI.
60
+
61
+ ---
62
+
63
+ ## Quick Start
64
+
65
+ ### Doppler Broadening — S and W parameters
66
+
67
+ ```python
68
+ import pandas as pd
69
+ from scispectrum.core import Spectrum
70
+ from scispectrum.calibration import AxisCalibration, ResolutionCalibration
71
+ from scipas.core import DB
72
+
73
+ calib = AxisCalibration(lambda ch: 0.5 * ch + 1.0, name="energy_keV")
74
+ res = ResolutionCalibration(lambda e: 1.8) # constant FWHM in keV
75
+ spec = Spectrum.from_dataframe(df, channel_col="channel", counts_col="counts",
76
+ axis_calib=calib, resolution_calib=res)
77
+
78
+ # Identify the 511 keV peak automatically
79
+ db = DB.from_spectrum(spec)
80
+
81
+ # Extract S and W parameters (energy in keV, relative to 511 keV)
82
+ s = db.s_parameter_calculation(energy_domain_total=(-8, 8),
83
+ energy_domain_s=(-0.8, 0.8))
84
+ w = db.w_parameter_calculation(energy_domain_total=(-8, 8),
85
+ energy_domain_w_left=(-8, -2),
86
+ energy_domain_w_right=(2, 8))
87
+ print(s, w) # ufloat values with propagated uncertainties
88
+ ```
89
+
90
+ ### Coincidence Doppler Broadening
91
+
92
+ ```python
93
+ from scipas.filter import PasCoincidenceFilter
94
+ from scipas.core import CDB
95
+
96
+ # Step 1: find time-coincident events
97
+ pairs = PasCoincidenceFilter.time_coincidence_filter(
98
+ det_1_df, det_2_df, max_time_interval=10)
99
+
100
+ # Step 2: apply energy-conservation window (keeps E1 + E2 ≈ 1022 keV)
101
+ energy_pairs = PasCoincidenceFilter.energy_coincidence_filter(
102
+ pairs,
103
+ axis_calibration_1=calib_1,
104
+ axis_calibration_2=calib_2,
105
+ local_fwhm_1=1.2,
106
+ local_fwhm_2=1.2)
107
+
108
+ # Step 3: build CDB object (histogram computed once at construction)
109
+ cdb = CDB(energy_pairs, energy_min=-4, energy_max=4, mesh_interval=0.05)
110
+
111
+ db = cdb.doppler_broadening() # DB ready for S/W analysis
112
+ res = cdb.resolution() # 1D resolution spectrum
113
+ ```
114
+
115
+ ### Implantation profiles
116
+
117
+ ```python
118
+ import numpy as np
119
+ from scipas.transport import makhov_profile, makhov_material_parameters
120
+
121
+ depth = np.arange(0, 5000, 1) # nm
122
+ params = makhov_material_parameters()
123
+ si = params[params["Material"] == "Si"].iloc[0]
124
+
125
+ profile = makhov_profile(positron_energy=10, depth_vector=depth,
126
+ density=si.density, makhov_parms=si)
127
+ ```
128
+
129
+ ### Multilayer implantation profile
130
+
131
+ ```python
132
+ from scipas.transport import (multilayer_implantation_profile,
133
+ makhov_profile, makhov_material_parameters)
134
+
135
+ params = makhov_material_parameters()
136
+ cu = params[params["Material"] == "Cu"].iloc[0]
137
+ si = params[params["Material"] == "Si"].iloc[0]
138
+
139
+ profile = multilayer_implantation_profile(
140
+ positron_energy=10,
141
+ depth_vector=np.arange(0, 5000, 1),
142
+ widths=[500], # 500 nm Cu film on Si substrate
143
+ materials_parameters=[cu, si],
144
+ densities=[cu.density, si.density],
145
+ implantation_profile_function=makhov_profile)
146
+ ```
147
+
148
+ ### Positron transport — diffusion solver
149
+
150
+ ```python
151
+ from scipas.model import Sample, Layer, Material
152
+ from scipas.transport import profile_solver
153
+
154
+ silicon = Material(name="Si", diffusion=1.0, mobility=0.0,
155
+ bulk_annihilation_rate=2.0)
156
+ layer = Layer(width=10000.0, material=silicon)
157
+ sample = Sample(layers=[layer], absorption_length=0.5)
158
+
159
+ # Returns xr.DataArray of c(z) on a uniform mesh
160
+ positron_profile = profile_solver(implantation_profile, sample)
161
+ ```
162
+
163
+ ### VEDB diffusion-length fitting
164
+
165
+ ```python
166
+ from scipas.analysis import DiffusionLengthOptimization
167
+
168
+ optimizer = DiffusionLengthOptimization(
169
+ positron_implantation_profiles=profiles, # list of xr.DataArray, one per energy
170
+ s_measurement=s_series, # pd.Series of ufloat
171
+ initial_guess=initial_sample)
172
+
173
+ best_fit, covariance = optimizer.optimize_diffusion_length(bounds=(0, 1000))
174
+ sigma = np.sqrt(np.diag(covariance))
175
+ print(f"L+ = {best_fit} ± {sigma} nm")
176
+ ```
177
+
178
+ ---
179
+
180
+ ## Examples
181
+
182
+ Full worked examples are in the [`examples/`](./examples) directory:
183
+
184
+ **DB / CDB Analysis**
185
+ - [DB spectrum analysis](./examples/core/pas_db.ipynb) — load, calibrate, and extract S/W from a DB spectrum
186
+ - [CDB analysis](./examples/core/pas_cdb.ipynb) — process coincidence data into a DB spectrum and S/W parameters
187
+
188
+ **VEDB Analysis**
189
+ - [S(E) and W(E) lineshape extraction](./examples/vedb%20analysis/vedb_lineshape.ipynb) — load multi-energy DB spectra; compute S(E) and W(E) curves with errorbars and S–W parametric plot
190
+ - [Diffusion-length fitting — measurement](./examples/vedb%20analysis/vedb_diffusion_length_measurement.ipynb) — fit L₊ from measured S(E) using the transport model; plot fit vs data and annihilation fractions per channel
191
+ - [Diffusion-length fitting — two-layer simulation](./examples/vedb%20analysis/vedb_diffusion_length_2layer_simulation.ipynb) — simulate a damaged surface layer over bulk, fit both L₀ and L₁ simultaneously, and visualise the 2D χ² joint confidence region
192
+
193
+ **Implantation Profiles and Transport**
194
+ - [Positron profile in Si](./examples/positron%20profile/positron%20profile%20in%20Si.ipynb) — Makhov and Ghosh profiles, multilayer stitching, transport solver, annihilation fractions
195
+ - [Transport solver benchmark](./examples/transport%20benchmark/solver_benchmark.ipynb) — validates `profile_solver` against two analytical results: exact surface fraction formula and closed-form full profile; confirms O(N⁻²) convergence
196
+
197
+ ---
198
+
199
+ ## Requirements
200
+
201
+ Following the [SPEC 0](https://scientific-python.org/specs/spec-0000/) support policy:
202
+
203
+ | Package | Version |
204
+ |---|---|
205
+ | Python | ≥ 3.11 |
206
+ | numpy | ≥ 2.0, < 3 |
207
+ | pandas | ≥ 2.3, < 4 |
208
+ | scipy | ≥ 1.14 |
209
+ | xarray | ≥ 2024.6 |
210
+ | uncertainties | ≥ 3.1 |
211
+ | scispectrum | ≥ 0.3 |
212
+
213
+ ---
214
+
215
+ ## Project Status
216
+
217
+ SciPAS is under active development. The DB/CDB analysis, implantation profiles, diffusion solver, and VEDB fitting are stable. Planned additions include positron lifetime spectrum analysis and extended Bayesian workflows for model comparison and uncertainty quantification.
218
+
219
+ ---
220
+
221
+ ## License
222
+
223
+ MIT License. See [LICENSE](LICENSE) for details.
224
+
225
+ ---
226
+
227
+ ## Author
228
+
229
+ Achiya Yosef Amrusi — [GitHub](https://github.com/achiyaAmrusi)
230
+
231
+ Contributions and issues are welcome. Please include a minimal reproducible example when reporting a bug.
scipas-0.3.0/README.md ADDED
@@ -0,0 +1,209 @@
1
+ # SciPAS: Positron Annihilation Spectroscopy in Python
2
+
3
+ A Python package for **Doppler Broadening (DB)** and **Coincidence Doppler Broadening (CDB)** analysis, positron implantation profiling, transport simulation, and variable-energy Doppler broadening (VEDB) diffusion-length fitting.
4
+
5
+ SciPAS provides a unified, modular workflow — from raw detector data to material parameters — built on standard scientific Python.
6
+
7
+ ---
8
+
9
+ ## Features
10
+
11
+ - **DB spectrum analysis** — S and W parameter extraction with Poisson uncertainty propagation; automatic 511 keV peak identification and axis centralization
12
+ - **CDB analysis** — 2D coincidence histogram; DB and resolution projections from detector-pair data
13
+ - **Event filtering** — time and energy coincidence filtering for synchronized detector pairs
14
+ - **Implantation profiles** — Makhovian and Ghosh positron stopping profiles; multilayer cumulative stitching; support for external (MC-simulated) profiles
15
+ - **Positron transport solver** — 1D finite-difference solver for the diffusion–drift–annihilation equation with electric fields and radiative boundary conditions
16
+ - **Layered sample model** — `Sample` / `Layer` / `Material` descriptors with depth-dependent diffusion, mobility, and annihilation rates
17
+ - **VEDB fitting** — diffusion-length optimization with covariance estimation; single- and multi-layer support; weighted nonlinear least squares
18
+ - **xarray throughout** — labeled, sliceable data with named coordinates
19
+ - **Uncertainty propagation** via the `uncertainties` library
20
+
21
+ ---
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ git clone https://github.com/achiyaAmrusi/scipas
27
+ cd scipas
28
+ pip install .
29
+ ```
30
+
31
+ For a development install (includes test dependencies):
32
+
33
+ ```bash
34
+ pip install ".[dev]"
35
+ ```
36
+
37
+ The companion spectrum library [scispectrum](https://github.com/achiyaAmrusi/scispectrum) is installed automatically from PyPI.
38
+
39
+ ---
40
+
41
+ ## Quick Start
42
+
43
+ ### Doppler Broadening — S and W parameters
44
+
45
+ ```python
46
+ import pandas as pd
47
+ from scispectrum.core import Spectrum
48
+ from scispectrum.calibration import AxisCalibration, ResolutionCalibration
49
+ from scipas.core import DB
50
+
51
+ calib = AxisCalibration(lambda ch: 0.5 * ch + 1.0, name="energy_keV")
52
+ res = ResolutionCalibration(lambda e: 1.8) # constant FWHM in keV
53
+ spec = Spectrum.from_dataframe(df, channel_col="channel", counts_col="counts",
54
+ axis_calib=calib, resolution_calib=res)
55
+
56
+ # Identify the 511 keV peak automatically
57
+ db = DB.from_spectrum(spec)
58
+
59
+ # Extract S and W parameters (energy in keV, relative to 511 keV)
60
+ s = db.s_parameter_calculation(energy_domain_total=(-8, 8),
61
+ energy_domain_s=(-0.8, 0.8))
62
+ w = db.w_parameter_calculation(energy_domain_total=(-8, 8),
63
+ energy_domain_w_left=(-8, -2),
64
+ energy_domain_w_right=(2, 8))
65
+ print(s, w) # ufloat values with propagated uncertainties
66
+ ```
67
+
68
+ ### Coincidence Doppler Broadening
69
+
70
+ ```python
71
+ from scipas.filter import PasCoincidenceFilter
72
+ from scipas.core import CDB
73
+
74
+ # Step 1: find time-coincident events
75
+ pairs = PasCoincidenceFilter.time_coincidence_filter(
76
+ det_1_df, det_2_df, max_time_interval=10)
77
+
78
+ # Step 2: apply energy-conservation window (keeps E1 + E2 ≈ 1022 keV)
79
+ energy_pairs = PasCoincidenceFilter.energy_coincidence_filter(
80
+ pairs,
81
+ axis_calibration_1=calib_1,
82
+ axis_calibration_2=calib_2,
83
+ local_fwhm_1=1.2,
84
+ local_fwhm_2=1.2)
85
+
86
+ # Step 3: build CDB object (histogram computed once at construction)
87
+ cdb = CDB(energy_pairs, energy_min=-4, energy_max=4, mesh_interval=0.05)
88
+
89
+ db = cdb.doppler_broadening() # DB ready for S/W analysis
90
+ res = cdb.resolution() # 1D resolution spectrum
91
+ ```
92
+
93
+ ### Implantation profiles
94
+
95
+ ```python
96
+ import numpy as np
97
+ from scipas.transport import makhov_profile, makhov_material_parameters
98
+
99
+ depth = np.arange(0, 5000, 1) # nm
100
+ params = makhov_material_parameters()
101
+ si = params[params["Material"] == "Si"].iloc[0]
102
+
103
+ profile = makhov_profile(positron_energy=10, depth_vector=depth,
104
+ density=si.density, makhov_parms=si)
105
+ ```
106
+
107
+ ### Multilayer implantation profile
108
+
109
+ ```python
110
+ from scipas.transport import (multilayer_implantation_profile,
111
+ makhov_profile, makhov_material_parameters)
112
+
113
+ params = makhov_material_parameters()
114
+ cu = params[params["Material"] == "Cu"].iloc[0]
115
+ si = params[params["Material"] == "Si"].iloc[0]
116
+
117
+ profile = multilayer_implantation_profile(
118
+ positron_energy=10,
119
+ depth_vector=np.arange(0, 5000, 1),
120
+ widths=[500], # 500 nm Cu film on Si substrate
121
+ materials_parameters=[cu, si],
122
+ densities=[cu.density, si.density],
123
+ implantation_profile_function=makhov_profile)
124
+ ```
125
+
126
+ ### Positron transport — diffusion solver
127
+
128
+ ```python
129
+ from scipas.model import Sample, Layer, Material
130
+ from scipas.transport import profile_solver
131
+
132
+ silicon = Material(name="Si", diffusion=1.0, mobility=0.0,
133
+ bulk_annihilation_rate=2.0)
134
+ layer = Layer(width=10000.0, material=silicon)
135
+ sample = Sample(layers=[layer], absorption_length=0.5)
136
+
137
+ # Returns xr.DataArray of c(z) on a uniform mesh
138
+ positron_profile = profile_solver(implantation_profile, sample)
139
+ ```
140
+
141
+ ### VEDB diffusion-length fitting
142
+
143
+ ```python
144
+ from scipas.analysis import DiffusionLengthOptimization
145
+
146
+ optimizer = DiffusionLengthOptimization(
147
+ positron_implantation_profiles=profiles, # list of xr.DataArray, one per energy
148
+ s_measurement=s_series, # pd.Series of ufloat
149
+ initial_guess=initial_sample)
150
+
151
+ best_fit, covariance = optimizer.optimize_diffusion_length(bounds=(0, 1000))
152
+ sigma = np.sqrt(np.diag(covariance))
153
+ print(f"L+ = {best_fit} ± {sigma} nm")
154
+ ```
155
+
156
+ ---
157
+
158
+ ## Examples
159
+
160
+ Full worked examples are in the [`examples/`](./examples) directory:
161
+
162
+ **DB / CDB Analysis**
163
+ - [DB spectrum analysis](./examples/core/pas_db.ipynb) — load, calibrate, and extract S/W from a DB spectrum
164
+ - [CDB analysis](./examples/core/pas_cdb.ipynb) — process coincidence data into a DB spectrum and S/W parameters
165
+
166
+ **VEDB Analysis**
167
+ - [S(E) and W(E) lineshape extraction](./examples/vedb%20analysis/vedb_lineshape.ipynb) — load multi-energy DB spectra; compute S(E) and W(E) curves with errorbars and S–W parametric plot
168
+ - [Diffusion-length fitting — measurement](./examples/vedb%20analysis/vedb_diffusion_length_measurement.ipynb) — fit L₊ from measured S(E) using the transport model; plot fit vs data and annihilation fractions per channel
169
+ - [Diffusion-length fitting — two-layer simulation](./examples/vedb%20analysis/vedb_diffusion_length_2layer_simulation.ipynb) — simulate a damaged surface layer over bulk, fit both L₀ and L₁ simultaneously, and visualise the 2D χ² joint confidence region
170
+
171
+ **Implantation Profiles and Transport**
172
+ - [Positron profile in Si](./examples/positron%20profile/positron%20profile%20in%20Si.ipynb) — Makhov and Ghosh profiles, multilayer stitching, transport solver, annihilation fractions
173
+ - [Transport solver benchmark](./examples/transport%20benchmark/solver_benchmark.ipynb) — validates `profile_solver` against two analytical results: exact surface fraction formula and closed-form full profile; confirms O(N⁻²) convergence
174
+
175
+ ---
176
+
177
+ ## Requirements
178
+
179
+ Following the [SPEC 0](https://scientific-python.org/specs/spec-0000/) support policy:
180
+
181
+ | Package | Version |
182
+ |---|---|
183
+ | Python | ≥ 3.11 |
184
+ | numpy | ≥ 2.0, < 3 |
185
+ | pandas | ≥ 2.3, < 4 |
186
+ | scipy | ≥ 1.14 |
187
+ | xarray | ≥ 2024.6 |
188
+ | uncertainties | ≥ 3.1 |
189
+ | scispectrum | ≥ 0.3 |
190
+
191
+ ---
192
+
193
+ ## Project Status
194
+
195
+ SciPAS is under active development. The DB/CDB analysis, implantation profiles, diffusion solver, and VEDB fitting are stable. Planned additions include positron lifetime spectrum analysis and extended Bayesian workflows for model comparison and uncertainty quantification.
196
+
197
+ ---
198
+
199
+ ## License
200
+
201
+ MIT License. See [LICENSE](LICENSE) for details.
202
+
203
+ ---
204
+
205
+ ## Author
206
+
207
+ Achiya Yosef Amrusi — [GitHub](https://github.com/achiyaAmrusi)
208
+
209
+ Contributions and issues are welcome. Please include a minimal reproducible example when reporting a bug.
@@ -0,0 +1,38 @@
1
+ [build-system]
2
+ requires = ["setuptools>=74.1.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "scipas"
7
+ version = "0.3.0"
8
+ description = "Positron Annihilation Spectroscopy analysis tools"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ authors = [
12
+ { name = "Achiya Yosef Amrusi", email = "ahia.amrosi@mail.huji.ac.il" }
13
+ ]
14
+ requires-python = ">=3.11"
15
+ dependencies = [
16
+ "numpy>=2.0.0,<3.0",
17
+ "pandas>=2.3,<4.0",
18
+ "scipy>=1.14.0",
19
+ "xarray>=2024.6.0",
20
+ "uncertainties>=3.1",
21
+ "scispectrum>=0.3",
22
+ ]
23
+
24
+ [project.optional-dependencies]
25
+ dev = [
26
+ "pytest>=8.0",
27
+ "pytest-cov",
28
+ ]
29
+
30
+ [tool.setuptools.packages.find]
31
+ include = ["scipas*"]
32
+
33
+ [tool.setuptools.package-data]
34
+ "scipas" = ["libs/**/*.txt"]
35
+
36
+ [project.urls]
37
+ Homepage = "https://github.com/achiyaAmrusi/scipas"
38
+ Repository = "https://github.com/achiyaAmrusi/scipas"
@@ -0,0 +1,23 @@
1
+ # Core analysis classes
2
+ from scipas.core import DB, CDB
3
+ from scipas.core.const import ELECTRON_REST_MASS_KEV
4
+
5
+ # Model / geometry
6
+ from scipas.model import Material, Defect, Layer, Sample
7
+
8
+ # Coincidence filter
9
+ from scipas.filter import PasCoincidenceFilter
10
+
11
+ # Transport
12
+ from scipas.transport import (
13
+ ghosh_profile,
14
+ makhov_profile,
15
+ ghosh_material_parameters,
16
+ makhov_material_parameters,
17
+ multilayer_implantation_profile,
18
+ profile_solver,
19
+ )
20
+
21
+ # VEDB analysis
22
+ from scipas.analysis import (DiffusionLengthOptimization, compute_s_lineshape, compute_w_lineshape,
23
+ variable_energy_implantation_profiles)
@@ -0,0 +1,3 @@
1
+ from .vedb import (compute_annihilation_fractions, DiffusionLengthOptimization,
2
+ compute_s_lineshape, compute_w_lineshape,
3
+ variable_energy_implantation_profiles)
@@ -0,0 +1,5 @@
1
+ """Package for Variable Energy Doppler Broadening (VEDB) analysis"""
2
+ from .annihilation_fractions import compute_annihilation_fractions
3
+ from .diffusion_length import DiffusionLengthOptimization
4
+ from .lineshape import compute_s_lineshape, compute_w_lineshape
5
+ from .ve_implanation import variable_energy_implantation_profiles
@@ -0,0 +1,105 @@
1
+ import numpy as np
2
+ import xarray as xr
3
+ from warnings import warn
4
+ from scipas.model.sample import Sample
5
+
6
+
7
+ def compute_annihilation_fractions(positron_profile: xr.DataArray, sample: Sample) -> xr.DataArray:
8
+ """
9
+ Compute the annihilation fraction of positrons in each layer and at the surface.
10
+
11
+ The total annihilation rate is decomposed into contributions from:
12
+ - Surface (layer = -1): positrons annihilating at the sample surface,
13
+ governed by the surface absorption length.
14
+ - Bulk layers (layer = 0, 1, ...): positrons annihilating in each
15
+ material layer, weighted by the effective annihilation rate.
16
+
17
+ The fractions are normalized so that they sum to 1.
18
+
19
+ Parameters
20
+ ----------
21
+ positron_profile : xr.DataArray
22
+ 1D positron density profile after diffusion [positrons/nm],
23
+ with a single depth coordinate (any name is accepted).
24
+ Must be normalized such that its integral equals 1.
25
+ sample : Sample
26
+ Multilayer sample defining geometry, material annihilation rates,
27
+ and surface absorption length.
28
+
29
+ Returns
30
+ -------
31
+ xr.DataArray
32
+ Normalized annihilation fractions with coordinate 'layer':
33
+ - layer = -1 : surface annihilation fraction
34
+ - layer = 0, 1, 2, ... : bulk annihilation fraction per layer
35
+
36
+ Raises
37
+ ------
38
+ ValueError
39
+ If positron_profile is not 1D.
40
+
41
+ Warns
42
+ -----
43
+ UserWarning
44
+ If the positron profile extends beyond the sample length.
45
+ Annihilation beyond the sample boundary is ignored and
46
+ fractions will not sum to 1 correctly.
47
+
48
+ Examples
49
+ --------
50
+ >>> from scipas.model import Sample, Layer, Material
51
+ >>> from scipas.analysis import compute_annihilation_fractions
52
+ >>> import xarray as xr
53
+ >>> import numpy as np
54
+ >>> silicon = Material(name="Silicon",
55
+ ... diffusion=1,
56
+ ... mobility=1,
57
+ ... bulk_annihilation_rate=1)
58
+ >>> layer = Layer(start=0.0, width=10000.0, material=silicon)
59
+ >>> sample = Sample(layers=[layer], absorption_length=1)
60
+ >>> depth = np.arange(0, layer.width+1, 1)
61
+ >>> positron_annihilation_profile = xr.DataArray(np.ones_like(depth), coords={'x':depth})
62
+ >>> positron_annihilation_profile /= positron_annihilation_profile.integrate('x')
63
+ >>> res = compute_annihilation_fractions(positron_annihilation_profile, sample)
64
+ >>> round(float(res.sum())) == 1.0
65
+ True
66
+ >>> round(float(res.sel(layer=-1)), 5) == 1e-4
67
+ True
68
+ """
69
+ # check input
70
+
71
+ if positron_profile.ndim != 1:
72
+ raise ValueError(f"positron_profile must be 1D, got {positron_profile.ndim}D")
73
+ depth_dim = positron_profile.dims[0] # infer axis name
74
+
75
+ profile_max_depth = positron_profile.coords[depth_dim].max().item()
76
+ sample_length = sample.sample_length()
77
+ if profile_max_depth > sample_length:
78
+ warn(
79
+ f"Positron profile extends to {profile_max_depth:.1f} nm but sample ends at "
80
+ f"{sample_length:.1f} nm. Annihilation beyond the sample boundary is ignored, "
81
+ f"fractions will not sum to 1 correctly. Consider extending the last layer."
82
+ )
83
+ # defs
84
+ layers = sample.layers
85
+ num_of_layers = len(layers)
86
+ layers_names = range(-1, num_of_layers)
87
+ annihilation_rate = np.zeros(num_of_layers+1) # layers and surface
88
+
89
+ # surface positrons annihilation rate
90
+ annihilation_rate[0] = (positron_profile.sel({depth_dim: 0.0}, method='nearest').item() *
91
+ sample.layers[0].material.diffusion / sample.absorption_length)
92
+
93
+ # layers positrons annihilation rates
94
+ for i, layer in enumerate(layers):
95
+ layer_positron_profile = positron_profile.sel({depth_dim: slice(layer.start, layer.start + layer.width)})
96
+ positron_fraction_in_layer = layer_positron_profile.integrate(depth_dim)
97
+ annihilation_rate[i+1] = layer.material.effective_annihilation_rate() * positron_fraction_in_layer.item()
98
+
99
+ # norm
100
+ annihilation_fractions = xr.DataArray(annihilation_rate, coords={'layer':layers_names})/annihilation_rate.sum()
101
+
102
+ return annihilation_fractions
103
+
104
+
105
+