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.
- scipas-0.3.0/LICENSE +21 -0
- scipas-0.3.0/PKG-INFO +231 -0
- scipas-0.3.0/README.md +209 -0
- scipas-0.3.0/pyproject.toml +38 -0
- scipas-0.3.0/scipas/__init__.py +23 -0
- scipas-0.3.0/scipas/analysis/__init__.py +3 -0
- scipas-0.3.0/scipas/analysis/vedb/__init__.py +5 -0
- scipas-0.3.0/scipas/analysis/vedb/annihilation_fractions.py +105 -0
- scipas-0.3.0/scipas/analysis/vedb/diffusion_length.py +268 -0
- scipas-0.3.0/scipas/analysis/vedb/lineshape.py +167 -0
- scipas-0.3.0/scipas/analysis/vedb/ve_implanation.py +86 -0
- scipas-0.3.0/scipas/core/__init__.py +4 -0
- scipas-0.3.0/scipas/core/cdb.py +219 -0
- scipas-0.3.0/scipas/core/const.py +3 -0
- scipas-0.3.0/scipas/core/db.py +349 -0
- scipas-0.3.0/scipas/filter/__init__.py +8 -0
- scipas-0.3.0/scipas/filter/pals_coincidence.py +1 -0
- scipas-0.3.0/scipas/filter/pas_coincidence.py +215 -0
- scipas-0.3.0/scipas/libs/__init__.py +0 -0
- scipas-0.3.0/scipas/libs/positron_profile/__init__.py +0 -0
- scipas-0.3.0/scipas/libs/positron_profile/gosh_profile_parameters.txt +35 -0
- scipas-0.3.0/scipas/libs/positron_profile/makhov_profile_parameters.txt +35 -0
- scipas-0.3.0/scipas/model/__init__.py +5 -0
- scipas-0.3.0/scipas/model/layer.py +43 -0
- scipas-0.3.0/scipas/model/lifetime.py +53 -0
- scipas-0.3.0/scipas/model/material.py +86 -0
- scipas-0.3.0/scipas/model/sample.py +90 -0
- scipas-0.3.0/scipas/transport/__init__.py +4 -0
- scipas-0.3.0/scipas/transport/diffusion/__init__.py +3 -0
- scipas-0.3.0/scipas/transport/diffusion/positron_profile_solver.py +229 -0
- scipas-0.3.0/scipas/transport/implantation/__init__.py +4 -0
- scipas-0.3.0/scipas/transport/implantation/material_parameters.py +59 -0
- scipas-0.3.0/scipas/transport/implantation/multilayer.py +143 -0
- scipas-0.3.0/scipas/transport/implantation/profiles.py +102 -0
- scipas-0.3.0/scipas/transport/implantation/utils.py +39 -0
- scipas-0.3.0/scipas.egg-info/PKG-INFO +231 -0
- scipas-0.3.0/scipas.egg-info/SOURCES.txt +39 -0
- scipas-0.3.0/scipas.egg-info/dependency_links.txt +1 -0
- scipas-0.3.0/scipas.egg-info/requires.txt +10 -0
- scipas-0.3.0/scipas.egg-info/top_level.txt +1 -0
- 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,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
|
+
|