reflectosim 0.1.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.
- reflectosim-0.1.0/LICENSE +21 -0
- reflectosim-0.1.0/PKG-INFO +128 -0
- reflectosim-0.1.0/README.md +95 -0
- reflectosim-0.1.0/pyproject.toml +85 -0
- reflectosim-0.1.0/pyproject.toml.orig +74 -0
- reflectosim-0.1.0/src/reflectosim/__init__.py +32 -0
- reflectosim-0.1.0/src/reflectosim/cli.py +76 -0
- reflectosim-0.1.0/src/reflectosim/dataset.py +84 -0
- reflectosim-0.1.0/src/reflectosim/forward.py +305 -0
- reflectosim-0.1.0/src/reflectosim/grid.py +16 -0
- reflectosim-0.1.0/src/reflectosim/kernel.py +148 -0
- reflectosim-0.1.0/src/reflectosim/materials.py +15 -0
- reflectosim-0.1.0/src/reflectosim/noise.py +27 -0
- reflectosim-0.1.0/src/reflectosim/priors.py +216 -0
- reflectosim-0.1.0/src/reflectosim/py.typed +0 -0
- reflectosim-0.1.0/src/reflectosim/resolution.py +494 -0
- reflectosim-0.1.0/src/reflectosim/simulate.py +142 -0
- reflectosim-0.1.0/src/reflectosim/structure.py +79 -0
- reflectosim-0.1.0/src/reflectosim/torch_backend.py +131 -0
- reflectosim-0.1.0/src/reflectosim/units.py +31 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Isaac Yong
|
|
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.
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: reflectosim
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: X-ray reflectivity forward physics and synthetic data generation
|
|
5
|
+
Keywords: x-ray reflectivity,XRR,simulation,synthetic data,machine learning
|
|
6
|
+
Author: Isaac Yong
|
|
7
|
+
Author-email: Isaac Yong <isaacyong@naver.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
18
|
+
Classifier: Typing :: Typed
|
|
19
|
+
Requires-Dist: numpy>=2.0
|
|
20
|
+
Requires-Dist: numba>=0.61
|
|
21
|
+
Requires-Dist: scipy>=1.13
|
|
22
|
+
Requires-Dist: periodictable>=2.1.0
|
|
23
|
+
Requires-Dist: h5py>=3.10 ; extra == 'hdf5'
|
|
24
|
+
Requires-Dist: torch>=2.5 ; extra == 'torch'
|
|
25
|
+
Requires-Python: >=3.12
|
|
26
|
+
Project-URL: Homepage, https://github.com/NaNIsaac/ReflectoSim
|
|
27
|
+
Project-URL: Repository, https://github.com/NaNIsaac/ReflectoSim
|
|
28
|
+
Project-URL: Issues, https://github.com/NaNIsaac/ReflectoSim/issues
|
|
29
|
+
Project-URL: Changelog, https://github.com/NaNIsaac/ReflectoSim/blob/main/CHANGELOG.md
|
|
30
|
+
Provides-Extra: hdf5
|
|
31
|
+
Provides-Extra: torch
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# ReflectoSim
|
|
35
|
+
|
|
36
|
+
[](https://github.com/NaNIsaac/ReflectoSim/actions/workflows/ci.yml) [](LICENSE)
|
|
37
|
+
|
|
38
|
+
<img src="https://raw.githubusercontent.com/NaNIsaac/ReflectoSim/main/docs/media/curves.png" width="720" alt="Four curves drawn from examples/lab_films.toml: the reflectivity before noise and the Poisson counts">
|
|
39
|
+
|
|
40
|
+
Realistic synthetic X-ray reflectivity (XRR) data for machine learning: priors over layer stacks and
|
|
41
|
+
instruments, counting noise, and a dataset format that records how to regenerate it.
|
|
42
|
+
|
|
43
|
+
It is for anyone training or testing an inverse model for XRR who needs simulated curves that look
|
|
44
|
+
like measured ones: random multilayers with roughness tied to thickness and a minimum contrast
|
|
45
|
+
between neighbours, a native oxide placed where you say, Gaussian resolution, footprint, Kα₂, 2θ
|
|
46
|
+
offset, background and Poisson counts. The package holds all the forward physics (Parratt
|
|
47
|
+
recursion with Névot-Croce roughness, the resolution integral, a differentiable torch forward) and
|
|
48
|
+
the generation on top of it. [ReflectoFit](https://github.com/NaNIsaac/ReflectoFit), the fitting
|
|
49
|
+
engine, depends on it, so a network trained on these curves and a fit of the same measurement use
|
|
50
|
+
the same physics.
|
|
51
|
+
|
|
52
|
+
Status: early development.
|
|
53
|
+
|
|
54
|
+
## Visual overview
|
|
55
|
+
|
|
56
|
+

|
|
57
|
+
|
|
58
|
+
The **example prior bounds** below are configurable input ranges, not empirical distributions. Coupled thickness/roughness and SLD-contrast constraints can alter the accepted distributions.
|
|
59
|
+
|
|
60
|
+

|
|
61
|
+
|
|
62
|
+
The XRR curves at the top of this README are generated by the simulator; solid curves show the pre-noise signal and dots show normalized Poisson counts. For a detailed interpretation, the layer-order diagram, units, reproducibility and caveats, see the [visual guide](docs/visual-guide.md).
|
|
63
|
+
|
|
64
|
+
## Install
|
|
65
|
+
|
|
66
|
+
Not on PyPI yet. From a clone, with [uv](https://docs.astral.sh/uv/):
|
|
67
|
+
|
|
68
|
+
```sh
|
|
69
|
+
git clone https://github.com/NaNIsaac/ReflectoSim
|
|
70
|
+
cd ReflectoSim
|
|
71
|
+
uv sync
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## First run
|
|
75
|
+
|
|
76
|
+
Simulate 1000 curves of one to three films on SiO₂ on Si from the example prior, then summarise the
|
|
77
|
+
file:
|
|
78
|
+
|
|
79
|
+
<!-- test -->
|
|
80
|
+
```sh
|
|
81
|
+
uv run reflectosim sim --prior examples/lab_films.toml --n 1000 --seed 20261024,1 -o train.npz
|
|
82
|
+
uv run reflectosim describe train.npz
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
`train.npz` holds `q`, `counts`, `reflectivity` (before noise), `counts_scale` and one array per
|
|
86
|
+
parameter (`layer0.thickness`, `oxide.sld`, `tth_offset`, ...; NaN where a curve has fewer layers).
|
|
87
|
+
Its metadata holds the priors, the seed and the versions; `reflectosim.priors.from_metadata` turns it
|
|
88
|
+
back into priors and the same seed gives the same file. Use a `.h5` name for HDF5 (`uv sync --extra
|
|
89
|
+
hdf5`).
|
|
90
|
+
|
|
91
|
+
## Priors
|
|
92
|
+
|
|
93
|
+
A prior is a TOML file; `examples/lab_films.toml` shows every field. The native oxide position has no
|
|
94
|
+
default: `oxide = "under"` puts it on the substrate, `"over"` on top of the films, `"none"` leaves it
|
|
95
|
+
out. The two orders give curves that differ by tens of percent, so every prior states its choice.
|
|
96
|
+
|
|
97
|
+
## Python
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
import numpy as np
|
|
101
|
+
from reflectosim import InstrumentPrior, StackPrior, simulate, tth_to_q
|
|
102
|
+
|
|
103
|
+
stack = StackPrior(
|
|
104
|
+
n_layers=(1, 3),
|
|
105
|
+
thickness=(20, 250),
|
|
106
|
+
sld=(5, 80),
|
|
107
|
+
roughness=(2, 10),
|
|
108
|
+
min_contrast=4.0,
|
|
109
|
+
oxide="none",
|
|
110
|
+
)
|
|
111
|
+
instrument = InstrumentPrior(
|
|
112
|
+
sigma_q=(5e-4, 3e-3), tth_offset=(-0.01, 0.01), log_background=(-8.5, -7.0), log_counts=(6, 8)
|
|
113
|
+
)
|
|
114
|
+
batch = simulate(
|
|
115
|
+
stack, instrument, tth_to_q(np.linspace(0.05, 8, 500)), n=10_000, seed=(20261024, 1)
|
|
116
|
+
)
|
|
117
|
+
batch.counts, batch.reflectivity, batch.params["layer0.thickness"]
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Development
|
|
121
|
+
|
|
122
|
+
```sh
|
|
123
|
+
uv run pytest
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## License
|
|
127
|
+
|
|
128
|
+
MIT
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# ReflectoSim
|
|
2
|
+
|
|
3
|
+
[](https://github.com/NaNIsaac/ReflectoSim/actions/workflows/ci.yml) [](LICENSE)
|
|
4
|
+
|
|
5
|
+
<img src="https://raw.githubusercontent.com/NaNIsaac/ReflectoSim/main/docs/media/curves.png" width="720" alt="Four curves drawn from examples/lab_films.toml: the reflectivity before noise and the Poisson counts">
|
|
6
|
+
|
|
7
|
+
Realistic synthetic X-ray reflectivity (XRR) data for machine learning: priors over layer stacks and
|
|
8
|
+
instruments, counting noise, and a dataset format that records how to regenerate it.
|
|
9
|
+
|
|
10
|
+
It is for anyone training or testing an inverse model for XRR who needs simulated curves that look
|
|
11
|
+
like measured ones: random multilayers with roughness tied to thickness and a minimum contrast
|
|
12
|
+
between neighbours, a native oxide placed where you say, Gaussian resolution, footprint, Kα₂, 2θ
|
|
13
|
+
offset, background and Poisson counts. The package holds all the forward physics (Parratt
|
|
14
|
+
recursion with Névot-Croce roughness, the resolution integral, a differentiable torch forward) and
|
|
15
|
+
the generation on top of it. [ReflectoFit](https://github.com/NaNIsaac/ReflectoFit), the fitting
|
|
16
|
+
engine, depends on it, so a network trained on these curves and a fit of the same measurement use
|
|
17
|
+
the same physics.
|
|
18
|
+
|
|
19
|
+
Status: early development.
|
|
20
|
+
|
|
21
|
+
## Visual overview
|
|
22
|
+
|
|
23
|
+

|
|
24
|
+
|
|
25
|
+
The **example prior bounds** below are configurable input ranges, not empirical distributions. Coupled thickness/roughness and SLD-contrast constraints can alter the accepted distributions.
|
|
26
|
+
|
|
27
|
+

|
|
28
|
+
|
|
29
|
+
The XRR curves at the top of this README are generated by the simulator; solid curves show the pre-noise signal and dots show normalized Poisson counts. For a detailed interpretation, the layer-order diagram, units, reproducibility and caveats, see the [visual guide](docs/visual-guide.md).
|
|
30
|
+
|
|
31
|
+
## Install
|
|
32
|
+
|
|
33
|
+
Not on PyPI yet. From a clone, with [uv](https://docs.astral.sh/uv/):
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
git clone https://github.com/NaNIsaac/ReflectoSim
|
|
37
|
+
cd ReflectoSim
|
|
38
|
+
uv sync
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## First run
|
|
42
|
+
|
|
43
|
+
Simulate 1000 curves of one to three films on SiO₂ on Si from the example prior, then summarise the
|
|
44
|
+
file:
|
|
45
|
+
|
|
46
|
+
<!-- test -->
|
|
47
|
+
```sh
|
|
48
|
+
uv run reflectosim sim --prior examples/lab_films.toml --n 1000 --seed 20261024,1 -o train.npz
|
|
49
|
+
uv run reflectosim describe train.npz
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`train.npz` holds `q`, `counts`, `reflectivity` (before noise), `counts_scale` and one array per
|
|
53
|
+
parameter (`layer0.thickness`, `oxide.sld`, `tth_offset`, ...; NaN where a curve has fewer layers).
|
|
54
|
+
Its metadata holds the priors, the seed and the versions; `reflectosim.priors.from_metadata` turns it
|
|
55
|
+
back into priors and the same seed gives the same file. Use a `.h5` name for HDF5 (`uv sync --extra
|
|
56
|
+
hdf5`).
|
|
57
|
+
|
|
58
|
+
## Priors
|
|
59
|
+
|
|
60
|
+
A prior is a TOML file; `examples/lab_films.toml` shows every field. The native oxide position has no
|
|
61
|
+
default: `oxide = "under"` puts it on the substrate, `"over"` on top of the films, `"none"` leaves it
|
|
62
|
+
out. The two orders give curves that differ by tens of percent, so every prior states its choice.
|
|
63
|
+
|
|
64
|
+
## Python
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
import numpy as np
|
|
68
|
+
from reflectosim import InstrumentPrior, StackPrior, simulate, tth_to_q
|
|
69
|
+
|
|
70
|
+
stack = StackPrior(
|
|
71
|
+
n_layers=(1, 3),
|
|
72
|
+
thickness=(20, 250),
|
|
73
|
+
sld=(5, 80),
|
|
74
|
+
roughness=(2, 10),
|
|
75
|
+
min_contrast=4.0,
|
|
76
|
+
oxide="none",
|
|
77
|
+
)
|
|
78
|
+
instrument = InstrumentPrior(
|
|
79
|
+
sigma_q=(5e-4, 3e-3), tth_offset=(-0.01, 0.01), log_background=(-8.5, -7.0), log_counts=(6, 8)
|
|
80
|
+
)
|
|
81
|
+
batch = simulate(
|
|
82
|
+
stack, instrument, tth_to_q(np.linspace(0.05, 8, 500)), n=10_000, seed=(20261024, 1)
|
|
83
|
+
)
|
|
84
|
+
batch.counts, batch.reflectivity, batch.params["layer0.thickness"]
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Development
|
|
88
|
+
|
|
89
|
+
```sh
|
|
90
|
+
uv run pytest
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## License
|
|
94
|
+
|
|
95
|
+
MIT
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "reflectosim"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "X-ray reflectivity forward physics and synthetic data generation"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
8
|
+
keywords = [
|
|
9
|
+
"x-ray reflectivity",
|
|
10
|
+
"XRR",
|
|
11
|
+
"simulation",
|
|
12
|
+
"synthetic data",
|
|
13
|
+
"machine learning",
|
|
14
|
+
]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 2 - Pre-Alpha",
|
|
17
|
+
"Intended Audience :: Science/Research",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
"Programming Language :: Python :: 3.13",
|
|
21
|
+
"Programming Language :: Python :: 3.14",
|
|
22
|
+
"Operating System :: OS Independent",
|
|
23
|
+
"Topic :: Scientific/Engineering :: Physics",
|
|
24
|
+
"Typing :: Typed",
|
|
25
|
+
]
|
|
26
|
+
requires-python = ">=3.12"
|
|
27
|
+
dependencies = [
|
|
28
|
+
"numpy>=2.0",
|
|
29
|
+
"numba>=0.61",
|
|
30
|
+
"scipy>=1.13",
|
|
31
|
+
"periodictable>=2.1.0",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[[project.authors]]
|
|
35
|
+
name = "Isaac Yong"
|
|
36
|
+
email = "isaacyong@naver.com"
|
|
37
|
+
|
|
38
|
+
[project.optional-dependencies]
|
|
39
|
+
hdf5 = ["h5py>=3.10"]
|
|
40
|
+
torch = ["torch>=2.5"]
|
|
41
|
+
|
|
42
|
+
[project.scripts]
|
|
43
|
+
reflectosim = "reflectosim.cli:main"
|
|
44
|
+
|
|
45
|
+
[project.urls]
|
|
46
|
+
Homepage = "https://github.com/NaNIsaac/ReflectoSim"
|
|
47
|
+
Repository = "https://github.com/NaNIsaac/ReflectoSim"
|
|
48
|
+
Issues = "https://github.com/NaNIsaac/ReflectoSim/issues"
|
|
49
|
+
Changelog = "https://github.com/NaNIsaac/ReflectoSim/blob/main/CHANGELOG.md"
|
|
50
|
+
|
|
51
|
+
[dependency-groups]
|
|
52
|
+
dev = [
|
|
53
|
+
"pytest>=8.0",
|
|
54
|
+
"h5py>=3.10",
|
|
55
|
+
"refnx>=0.1.60",
|
|
56
|
+
"jax>=0.4.30",
|
|
57
|
+
"torch>=2.5",
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
[[tool.uv.index]]
|
|
61
|
+
name = "pytorch-cpu"
|
|
62
|
+
url = "https://download.pytorch.org/whl/cpu"
|
|
63
|
+
explicit = true
|
|
64
|
+
|
|
65
|
+
[[tool.uv.sources.torch]]
|
|
66
|
+
index = "pytorch-cpu"
|
|
67
|
+
|
|
68
|
+
[tool.pytest.ini_options]
|
|
69
|
+
testpaths = ["tests"]
|
|
70
|
+
pythonpath = ["."]
|
|
71
|
+
|
|
72
|
+
[tool.ruff]
|
|
73
|
+
line-length = 100
|
|
74
|
+
extend-exclude = ["tests/reference/reflectolearn_abeles.py"]
|
|
75
|
+
|
|
76
|
+
[tool.ruff.lint]
|
|
77
|
+
extend-select = [
|
|
78
|
+
"B",
|
|
79
|
+
"I",
|
|
80
|
+
"UP",
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
[build-system]
|
|
84
|
+
requires = ["uv_build>=0.12.7,<0.13"]
|
|
85
|
+
build-backend = "uv_build"
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "reflectosim"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "X-ray reflectivity forward physics and synthetic data generation"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
8
|
+
authors = [{ name = "Isaac Yong", email = "isaacyong@naver.com" }]
|
|
9
|
+
keywords = ["x-ray reflectivity", "XRR", "simulation", "synthetic data", "machine learning"]
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Development Status :: 2 - Pre-Alpha",
|
|
12
|
+
"Intended Audience :: Science/Research",
|
|
13
|
+
"Programming Language :: Python :: 3",
|
|
14
|
+
"Programming Language :: Python :: 3.12",
|
|
15
|
+
"Programming Language :: Python :: 3.13",
|
|
16
|
+
"Programming Language :: Python :: 3.14",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
"Topic :: Scientific/Engineering :: Physics",
|
|
19
|
+
"Typing :: Typed",
|
|
20
|
+
]
|
|
21
|
+
requires-python = ">=3.12"
|
|
22
|
+
dependencies = [
|
|
23
|
+
"numpy>=2.0",
|
|
24
|
+
"numba>=0.61",
|
|
25
|
+
"scipy>=1.13",
|
|
26
|
+
"periodictable>=2.1.0",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.optional-dependencies]
|
|
30
|
+
hdf5 = ["h5py>=3.10"]
|
|
31
|
+
torch = ["torch>=2.5"]
|
|
32
|
+
|
|
33
|
+
[project.scripts]
|
|
34
|
+
reflectosim = "reflectosim.cli:main"
|
|
35
|
+
|
|
36
|
+
[project.urls]
|
|
37
|
+
Homepage = "https://github.com/NaNIsaac/ReflectoSim"
|
|
38
|
+
Repository = "https://github.com/NaNIsaac/ReflectoSim"
|
|
39
|
+
Issues = "https://github.com/NaNIsaac/ReflectoSim/issues"
|
|
40
|
+
Changelog = "https://github.com/NaNIsaac/ReflectoSim/blob/main/CHANGELOG.md"
|
|
41
|
+
|
|
42
|
+
[dependency-groups]
|
|
43
|
+
dev = [
|
|
44
|
+
"pytest>=8.0",
|
|
45
|
+
"h5py>=3.10",
|
|
46
|
+
"refnx>=0.1.60",
|
|
47
|
+
"jax>=0.4.30",
|
|
48
|
+
"torch>=2.5",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
# CPU torch wheels for this project's own lock (tests, CI); installs from PyPI are not affected.
|
|
52
|
+
[[tool.uv.index]]
|
|
53
|
+
name = "pytorch-cpu"
|
|
54
|
+
url = "https://download.pytorch.org/whl/cpu"
|
|
55
|
+
explicit = true
|
|
56
|
+
|
|
57
|
+
[tool.uv.sources]
|
|
58
|
+
torch = [{ index = "pytorch-cpu" }]
|
|
59
|
+
|
|
60
|
+
[build-system]
|
|
61
|
+
requires = ["uv_build>=0.12.7,<0.13"]
|
|
62
|
+
build-backend = "uv_build"
|
|
63
|
+
|
|
64
|
+
[tool.pytest.ini_options]
|
|
65
|
+
testpaths = ["tests"]
|
|
66
|
+
pythonpath = ["."]
|
|
67
|
+
|
|
68
|
+
[tool.ruff]
|
|
69
|
+
line-length = 100
|
|
70
|
+
# verbatim copies of other engines, compared against in tests/test_reference_engines.py
|
|
71
|
+
extend-exclude = ["tests/reference/reflectolearn_abeles.py"]
|
|
72
|
+
|
|
73
|
+
[tool.ruff.lint]
|
|
74
|
+
extend-select = ["B", "I", "UP"]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""X-ray reflectivity forward physics and realistic synthetic data for machine learning."""
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import version
|
|
4
|
+
|
|
5
|
+
from reflectosim import materials
|
|
6
|
+
from reflectosim.forward import Instrument, forward
|
|
7
|
+
from reflectosim.priors import InstrumentPrior, OxidePrior, StackPrior, SubstratePrior
|
|
8
|
+
from reflectosim.simulate import Batch, simulate
|
|
9
|
+
from reflectosim.structure import Layer, Stack
|
|
10
|
+
from reflectosim.units import CU_KALPHA1, CU_KALPHA2, fwhm_to_sigma, q_to_tth, tth_to_q
|
|
11
|
+
|
|
12
|
+
__version__ = version("reflectosim")
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"CU_KALPHA1",
|
|
16
|
+
"CU_KALPHA2",
|
|
17
|
+
"Batch",
|
|
18
|
+
"Instrument",
|
|
19
|
+
"InstrumentPrior",
|
|
20
|
+
"Layer",
|
|
21
|
+
"OxidePrior",
|
|
22
|
+
"Stack",
|
|
23
|
+
"StackPrior",
|
|
24
|
+
"SubstratePrior",
|
|
25
|
+
"__version__",
|
|
26
|
+
"forward",
|
|
27
|
+
"fwhm_to_sigma",
|
|
28
|
+
"materials",
|
|
29
|
+
"q_to_tth",
|
|
30
|
+
"simulate",
|
|
31
|
+
"tth_to_q",
|
|
32
|
+
]
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Command line: reflectosim sim | describe."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
from reflectosim import __version__, dataset, priors
|
|
10
|
+
from reflectosim.grid import q_grid
|
|
11
|
+
from reflectosim.simulate import simulate
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _seed(text: str):
|
|
15
|
+
parts = [int(p) for p in text.split(",")]
|
|
16
|
+
return parts[0] if len(parts) == 1 else tuple(parts)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _sim(args) -> int:
|
|
20
|
+
stack, inst = priors.load(args.prior)
|
|
21
|
+
q = q_grid(args.grid, inst.wavelength)
|
|
22
|
+
batch = simulate(stack, inst, q, n=args.n, seed=_seed(args.seed))
|
|
23
|
+
out = dataset.write(args.out, batch, stack, inst)
|
|
24
|
+
print(f"wrote {out}: {args.n} curves x {q.size} points, oxide {stack.oxide}, "
|
|
25
|
+
f"resolution nodes {batch.nodes or 'per point'}") # fmt: skip
|
|
26
|
+
return 0
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _describe(args) -> int:
|
|
30
|
+
arrays, params, meta = dataset.read(args.dataset)
|
|
31
|
+
n, nq = arrays["counts"].shape
|
|
32
|
+
print(
|
|
33
|
+
f"{args.dataset}: {n} curves x {nq} points, q {arrays['q'][0]:.4g}-{arrays['q'][-1]:.4g} 1/A"
|
|
34
|
+
)
|
|
35
|
+
width = max(len(k) for k in params)
|
|
36
|
+
for k, v in params.items():
|
|
37
|
+
ok = np.isfinite(v)
|
|
38
|
+
if ok.any():
|
|
39
|
+
print(f" {k:<{width}} {v[ok].min():.4g} .. {v[ok].max():.4g} ({ok.sum()} rows)")
|
|
40
|
+
print(
|
|
41
|
+
json.dumps(
|
|
42
|
+
{
|
|
43
|
+
k: meta[k]
|
|
44
|
+
for k in ("reflectosim", "reflectofit", "seed", "resolution_nodes")
|
|
45
|
+
if k in meta
|
|
46
|
+
}
|
|
47
|
+
)
|
|
48
|
+
)
|
|
49
|
+
return 0
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def main(argv: list[str] | None = None) -> int:
|
|
53
|
+
p = argparse.ArgumentParser(prog="reflectosim", description="synthetic X-ray reflectivity data")
|
|
54
|
+
p.add_argument("--version", action="version", version=f"reflectosim {__version__}")
|
|
55
|
+
sub = p.add_subparsers(dest="command", required=True)
|
|
56
|
+
|
|
57
|
+
s = sub.add_parser("sim", help="simulate a dataset from a prior TOML")
|
|
58
|
+
s.add_argument(
|
|
59
|
+
"--prior", required=True, help="prior TOML: [stack], [oxide], [substrate], [instrument]"
|
|
60
|
+
)
|
|
61
|
+
s.add_argument("--n", type=int, required=True, help="number of curves")
|
|
62
|
+
s.add_argument("--grid", default="tth:0.05:8:500", help="tth:START:STOP:N or q:START:STOP:N")
|
|
63
|
+
s.add_argument("--seed", default="0", help="an int or comma-separated ints, e.g. 20261024,1")
|
|
64
|
+
s.add_argument("-o", "--out", required=True, help="output .h5 (needs h5py) or .npz")
|
|
65
|
+
s.set_defaults(run=_sim)
|
|
66
|
+
|
|
67
|
+
d = sub.add_parser("describe", help="summarise a dataset file")
|
|
68
|
+
d.add_argument("dataset")
|
|
69
|
+
d.set_defaults(run=_describe)
|
|
70
|
+
|
|
71
|
+
args = p.parse_args(argv)
|
|
72
|
+
return args.run(args)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
if __name__ == "__main__":
|
|
76
|
+
sys.exit(main())
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Dataset files: HDF5 (with the `hdf5` extra) or compressed npz, chosen by the file suffix.
|
|
2
|
+
|
|
3
|
+
Arrays: `q` (Q,), `counts` and `reflectivity` (n, Q), `counts_scale` (n,), and one (n,) array per
|
|
4
|
+
parameter under `params/<name>` (HDF5) or `params.<name>` (npz). The metadata is one JSON string with
|
|
5
|
+
the priors, the seed, the resolution node count, the package versions and the conventions, enough
|
|
6
|
+
to regenerate the file.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
from importlib.metadata import version
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
|
|
15
|
+
from reflectosim import priors
|
|
16
|
+
from reflectosim.simulate import Batch
|
|
17
|
+
|
|
18
|
+
CONVENTIONS = (
|
|
19
|
+
"ambient first, roughness of the interface above, Im SLD > 0 "
|
|
20
|
+
"absorbs, SLD 1e-6 A^-2, q A^-1, angles in degrees; reflectivity is I0 * smeared R + background "
|
|
21
|
+
"before noise; counts are Poisson(counts_scale * reflectivity)"
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def metadata(batch: Batch, stack: priors.StackPrior, instrument: priors.InstrumentPrior) -> dict:
|
|
26
|
+
seed = list(batch.seed) if isinstance(batch.seed, tuple | list) else batch.seed
|
|
27
|
+
return {
|
|
28
|
+
"reflectosim": version("reflectosim"),
|
|
29
|
+
"stack_prior": priors.to_dict(stack),
|
|
30
|
+
"instrument_prior": priors.to_dict(instrument),
|
|
31
|
+
"seed": seed,
|
|
32
|
+
"n": int(batch.counts.shape[0]),
|
|
33
|
+
"resolution_nodes": batch.nodes,
|
|
34
|
+
"backend": "reflectosim numba float64",
|
|
35
|
+
"conventions": CONVENTIONS,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def write(
|
|
40
|
+
path: str | Path, batch: Batch, stack: priors.StackPrior, instrument: priors.InstrumentPrior
|
|
41
|
+
) -> Path:
|
|
42
|
+
path = Path(path)
|
|
43
|
+
meta = json.dumps(metadata(batch, stack, instrument))
|
|
44
|
+
arrays = {
|
|
45
|
+
"q": batch.q,
|
|
46
|
+
"counts": batch.counts.astype(np.float32),
|
|
47
|
+
"reflectivity": batch.reflectivity.astype(np.float32),
|
|
48
|
+
"counts_scale": batch.counts_scale,
|
|
49
|
+
}
|
|
50
|
+
if path.suffix in (".h5", ".hdf5"):
|
|
51
|
+
import h5py
|
|
52
|
+
|
|
53
|
+
with h5py.File(path, "w") as f:
|
|
54
|
+
for k, v in arrays.items():
|
|
55
|
+
f.create_dataset(k, data=v, compression="gzip" if v.ndim == 2 else None)
|
|
56
|
+
g = f.create_group("params")
|
|
57
|
+
for k, v in batch.params.items():
|
|
58
|
+
g.create_dataset(k, data=v)
|
|
59
|
+
f.attrs["metadata"] = meta
|
|
60
|
+
elif path.suffix == ".npz":
|
|
61
|
+
np.savez_compressed(
|
|
62
|
+
path, **arrays, **{f"params.{k}": v for k, v in batch.params.items()}, metadata=meta
|
|
63
|
+
)
|
|
64
|
+
else:
|
|
65
|
+
raise ValueError(f"unknown dataset suffix {path.suffix!r}; use .h5 or .npz")
|
|
66
|
+
return path
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def read(path: str | Path) -> tuple[dict[str, np.ndarray], dict[str, np.ndarray], dict]:
|
|
70
|
+
"""(arrays, params, metadata) of a dataset file."""
|
|
71
|
+
path = Path(path)
|
|
72
|
+
if path.suffix in (".h5", ".hdf5"):
|
|
73
|
+
import h5py
|
|
74
|
+
|
|
75
|
+
with h5py.File(path, "r") as f:
|
|
76
|
+
arrays = {k: f[k][()] for k in ("q", "counts", "reflectivity", "counts_scale")}
|
|
77
|
+
params = {k: f["params"][k][()] for k in f["params"]}
|
|
78
|
+
meta = json.loads(f.attrs["metadata"])
|
|
79
|
+
return arrays, params, meta
|
|
80
|
+
with np.load(path) as z:
|
|
81
|
+
arrays = {k: z[k] for k in ("q", "counts", "reflectivity", "counts_scale")}
|
|
82
|
+
params = {k.removeprefix("params."): z[k] for k in z.files if k.startswith("params.")}
|
|
83
|
+
meta = json.loads(str(z["metadata"]))
|
|
84
|
+
return arrays, params, meta
|