midas-pdf 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.
- midas_pdf-0.1.0/LICENSE +53 -0
- midas_pdf-0.1.0/PKG-INFO +127 -0
- midas_pdf-0.1.0/README.md +93 -0
- midas_pdf-0.1.0/midas_pdf/__init__.py +118 -0
- midas_pdf-0.1.0/midas_pdf/aniso_refine.py +206 -0
- midas_pdf-0.1.0/midas_pdf/bayesian_refine.py +332 -0
- midas_pdf-0.1.0/midas_pdf/cif.py +445 -0
- midas_pdf-0.1.0/midas_pdf/cli/__init__.py +20 -0
- midas_pdf-0.1.0/midas_pdf/cli/_common.py +84 -0
- midas_pdf-0.1.0/midas_pdf/cli/cif_cmd.py +110 -0
- midas_pdf-0.1.0/midas_pdf/cli/coreshell_cmd.py +101 -0
- midas_pdf-0.1.0/midas_pdf/cli/joint_cmd.py +146 -0
- midas_pdf-0.1.0/midas_pdf/cli/multiphase_cmd.py +112 -0
- midas_pdf-0.1.0/midas_pdf/cli/refine_cmd.py +95 -0
- midas_pdf-0.1.0/midas_pdf/cli/rmc_cmd.py +174 -0
- midas_pdf-0.1.0/midas_pdf/composition.py +275 -0
- midas_pdf-0.1.0/midas_pdf/compton.py +133 -0
- midas_pdf-0.1.0/midas_pdf/conventions.py +126 -0
- midas_pdf-0.1.0/midas_pdf/corrections.py +360 -0
- midas_pdf-0.1.0/midas_pdf/cross_section.py +131 -0
- midas_pdf-0.1.0/midas_pdf/data/fluor_edges.json +1 -0
- midas_pdf-0.1.0/midas_pdf/data/incoherent_hubbell.json +1 -0
- midas_pdf-0.1.0/midas_pdf/deltapdf.py +215 -0
- midas_pdf-0.1.0/midas_pdf/fluorescence.py +123 -0
- midas_pdf-0.1.0/midas_pdf/frontend.py +107 -0
- midas_pdf-0.1.0/midas_pdf/gr.py +27 -0
- midas_pdf-0.1.0/midas_pdf/ionic_form_factors.py +188 -0
- midas_pdf-0.1.0/midas_pdf/model_comparison.py +219 -0
- midas_pdf-0.1.0/midas_pdf/ms.py +511 -0
- midas_pdf-0.1.0/midas_pdf/ms_transport.py +168 -0
- midas_pdf-0.1.0/midas_pdf/multi_phase.py +433 -0
- midas_pdf-0.1.0/midas_pdf/multiple_scattering.py +73 -0
- midas_pdf-0.1.0/midas_pdf/normalize.py +127 -0
- midas_pdf-0.1.0/midas_pdf/pipeline.py +62 -0
- midas_pdf-0.1.0/midas_pdf/placzek.py +61 -0
- midas_pdf-0.1.0/midas_pdf/refine.py +179 -0
- midas_pdf-0.1.0/midas_pdf/rmc/__init__.py +54 -0
- midas_pdf-0.1.0/midas_pdf/rmc/analysis.py +248 -0
- midas_pdf-0.1.0/midas_pdf/rmc/cluster_moves.py +199 -0
- midas_pdf-0.1.0/midas_pdf/rmc/driver.py +139 -0
- midas_pdf-0.1.0/midas_pdf/rmc/ensemble.py +195 -0
- midas_pdf-0.1.0/midas_pdf/rmc/gc_moves.py +194 -0
- midas_pdf-0.1.0/midas_pdf/rmc/histogram.py +100 -0
- midas_pdf-0.1.0/midas_pdf/rmc/moves.py +226 -0
- midas_pdf-0.1.0/midas_pdf/rmc/supercell.py +170 -0
- midas_pdf-0.1.0/midas_pdf/saxs/__init__.py +64 -0
- midas_pdf-0.1.0/midas_pdf/saxs/core_shell.py +122 -0
- midas_pdf-0.1.0/midas_pdf/saxs/form_factors.py +212 -0
- midas_pdf-0.1.0/midas_pdf/saxs/joint.py +205 -0
- midas_pdf-0.1.0/midas_pdf/saxs/joint_bayesian.py +334 -0
- midas_pdf-0.1.0/midas_pdf/saxs/joint_three_way.py +198 -0
- midas_pdf-0.1.0/midas_pdf/saxs/joint_three_way_bayesian.py +374 -0
- midas_pdf-0.1.0/midas_pdf/saxs/model.py +165 -0
- midas_pdf-0.1.0/midas_pdf/saxs/wide_band.py +265 -0
- midas_pdf-0.1.0/midas_pdf/strain_pdf.py +255 -0
- midas_pdf-0.1.0/midas_pdf/structure.py +391 -0
- midas_pdf-0.1.0/midas_pdf/validate.py +135 -0
- midas_pdf-0.1.0/midas_pdf.egg-info/PKG-INFO +127 -0
- midas_pdf-0.1.0/midas_pdf.egg-info/SOURCES.txt +107 -0
- midas_pdf-0.1.0/midas_pdf.egg-info/dependency_links.txt +1 -0
- midas_pdf-0.1.0/midas_pdf.egg-info/entry_points.txt +7 -0
- midas_pdf-0.1.0/midas_pdf.egg-info/requires.txt +12 -0
- midas_pdf-0.1.0/midas_pdf.egg-info/top_level.txt +1 -0
- midas_pdf-0.1.0/pyproject.toml +79 -0
- midas_pdf-0.1.0/setup.cfg +4 -0
- midas_pdf-0.1.0/tests/test_aniso_occupancy.py +196 -0
- midas_pdf-0.1.0/tests/test_bayesian_refine.py +118 -0
- midas_pdf-0.1.0/tests/test_bug_fixes_rev15.py +248 -0
- midas_pdf-0.1.0/tests/test_cif.py +414 -0
- midas_pdf-0.1.0/tests/test_cli.py +245 -0
- midas_pdf-0.1.0/tests/test_cli_multi_coreshell.py +195 -0
- midas_pdf-0.1.0/tests/test_composition.py +71 -0
- midas_pdf-0.1.0/tests/test_compton.py +56 -0
- midas_pdf-0.1.0/tests/test_conventions.py +52 -0
- midas_pdf-0.1.0/tests/test_corrections.py +138 -0
- midas_pdf-0.1.0/tests/test_cross_section.py +57 -0
- midas_pdf-0.1.0/tests/test_cylinder_ms.py +44 -0
- midas_pdf-0.1.0/tests/test_double_scattering.py +60 -0
- midas_pdf-0.1.0/tests/test_fluorescence.py +28 -0
- midas_pdf-0.1.0/tests/test_frontend.py +74 -0
- midas_pdf-0.1.0/tests/test_ionic_form_factors.py +149 -0
- midas_pdf-0.1.0/tests/test_model_comparison.py +146 -0
- midas_pdf-0.1.0/tests/test_ms.py +81 -0
- midas_pdf-0.1.0/tests/test_multi_phase.py +268 -0
- midas_pdf-0.1.0/tests/test_multiple_scattering.py +59 -0
- midas_pdf-0.1.0/tests/test_normalize.py +68 -0
- midas_pdf-0.1.0/tests/test_pipeline.py +72 -0
- midas_pdf-0.1.0/tests/test_refine.py +52 -0
- midas_pdf-0.1.0/tests/test_refine_background.py +53 -0
- midas_pdf-0.1.0/tests/test_rev4_additions.py +186 -0
- midas_pdf-0.1.0/tests/test_rmc_cluster.py +156 -0
- midas_pdf-0.1.0/tests/test_rmc_day1.py +192 -0
- midas_pdf-0.1.0/tests/test_rmc_day2.py +229 -0
- midas_pdf-0.1.0/tests/test_rmc_day3.py +158 -0
- midas_pdf-0.1.0/tests/test_rmc_day4.py +134 -0
- midas_pdf-0.1.0/tests/test_rmc_gc.py +181 -0
- midas_pdf-0.1.0/tests/test_saxs_core_shell.py +131 -0
- midas_pdf-0.1.0/tests/test_saxs_day1.py +167 -0
- midas_pdf-0.1.0/tests/test_saxs_joint.py +197 -0
- midas_pdf-0.1.0/tests/test_saxs_joint_bayesian.py +121 -0
- midas_pdf-0.1.0/tests/test_saxs_three_way.py +145 -0
- midas_pdf-0.1.0/tests/test_saxs_three_way_bayesian.py +149 -0
- midas_pdf-0.1.0/tests/test_saxs_wide_band.py +158 -0
- midas_pdf-0.1.0/tests/test_sequence_dpdf.py +152 -0
- midas_pdf-0.1.0/tests/test_strain_pdf.py +130 -0
- midas_pdf-0.1.0/tests/test_structure.py +78 -0
- midas_pdf-0.1.0/tests/test_transport_ms.py +67 -0
- midas_pdf-0.1.0/tests/test_uncertainty.py +48 -0
- midas_pdf-0.1.0/tests/test_validate.py +42 -0
midas_pdf-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
Copyright (c) 2012, UChicago Argonne, LLC
|
|
2
|
+
|
|
3
|
+
All Rights Reserved
|
|
4
|
+
|
|
5
|
+
MIDAS Microstructural Imaging using Diffraction Analysis Software
|
|
6
|
+
|
|
7
|
+
Materials Physics and Engineering
|
|
8
|
+
Computational X-ray Science
|
|
9
|
+
Advanced Photon Source
|
|
10
|
+
Argonne National Laboratory
|
|
11
|
+
|
|
12
|
+
Contributing Authors:
|
|
13
|
+
Hemant Sharma (hsharma@anl.gov)
|
|
14
|
+
|
|
15
|
+
OPEN SOURCE LICENSE
|
|
16
|
+
|
|
17
|
+
Redistribution and use in source and binary forms, with or without
|
|
18
|
+
modification, are permitted provided that the following conditions are met:
|
|
19
|
+
|
|
20
|
+
1. Redistributions of source code must retain the above copyright notice,
|
|
21
|
+
this list of conditions and the following disclaimer. Software changes,
|
|
22
|
+
modifications, or derivative works, should be noted with comments and
|
|
23
|
+
the author and organization's name.
|
|
24
|
+
|
|
25
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
26
|
+
this list of conditions and the following disclaimer in the documentation
|
|
27
|
+
and/or other materials provided with the distribution.
|
|
28
|
+
|
|
29
|
+
3. Neither the names of UChicago Argonne, LLC or the Department of Energy
|
|
30
|
+
nor the names of its contributors may be used to endorse or promote
|
|
31
|
+
products derived from this software without specific prior written
|
|
32
|
+
permission.
|
|
33
|
+
|
|
34
|
+
4. The software and the end-user documentation included with the
|
|
35
|
+
redistribution, if any, must include the following acknowledgment:
|
|
36
|
+
|
|
37
|
+
"This product includes software produced by UChicago Argonne, LLC
|
|
38
|
+
under Contract No. DE-AC02-06CH11357 with the Department of Energy."
|
|
39
|
+
|
|
40
|
+
****************************************************************************
|
|
41
|
+
|
|
42
|
+
DISCLAIMER
|
|
43
|
+
|
|
44
|
+
THE SOFTWARE IS SUPPLIED "AS IS" WITHOUT WARRANTY OF ANY KIND.
|
|
45
|
+
|
|
46
|
+
Neither the United States GOVERNMENT, nor the United States Department
|
|
47
|
+
of Energy, NOR UChicago Argonne, LLC, nor any of their employees, makes
|
|
48
|
+
any warranty, express or implied, or assumes any legal liability or
|
|
49
|
+
responsibility for the accuracy, completeness, or usefulness of any
|
|
50
|
+
information, data, apparatus, product, or process disclosed, or
|
|
51
|
+
represents that its use would not infringe privately owned rights.
|
|
52
|
+
|
|
53
|
+
****************************************************************************
|
midas_pdf-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: midas-pdf
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Differentiable, error-propagating total-scattering / pair-distribution-function (PDF, G(r)) pipeline. Thin Faber-Ziman layer over midas-hkls (form factors) and midas-integrate-v2 (integration, Compton, sine FT).
|
|
5
|
+
Author-email: Hemant Sharma <hsharma@anl.gov>
|
|
6
|
+
License-Expression: BSD-3-Clause
|
|
7
|
+
Project-URL: Homepage, https://github.com/marinerhemant/MIDAS
|
|
8
|
+
Project-URL: Issues, https://github.com/marinerhemant/MIDAS/issues
|
|
9
|
+
Keywords: MIDAS,X-ray,total scattering,pair distribution function,PDF,G(r),delta-PDF,Faber-Ziman,differentiable,PyTorch,uncertainty propagation
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Chemistry
|
|
20
|
+
Requires-Python: >=3.9
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: numpy>=1.22
|
|
24
|
+
Requires-Dist: torch>=2.1
|
|
25
|
+
Requires-Dist: midas-hkls>=0.5.0
|
|
26
|
+
Requires-Dist: midas-integrate-v2>=0.1.0
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
29
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
30
|
+
Requires-Dist: matplotlib>=3.6; extra == "dev"
|
|
31
|
+
Provides-Extra: bayes
|
|
32
|
+
Requires-Dist: pyro-ppl>=1.9.0; extra == "bayes"
|
|
33
|
+
Dynamic: license-file
|
|
34
|
+
|
|
35
|
+
# midas-pdf
|
|
36
|
+
|
|
37
|
+
**Differentiable, error-propagating total-scattering / pair-distribution-function
|
|
38
|
+
(PDF, *G(r)*) pipeline.**
|
|
39
|
+
|
|
40
|
+
`midas-pdf` is a deliberately thin layer. Almost everything it needs already
|
|
41
|
+
exists elsewhere in MIDAS and is reused rather than reimplemented:
|
|
42
|
+
|
|
43
|
+
| Stage | Provided by | Status |
|
|
44
|
+
|-------|-------------|--------|
|
|
45
|
+
| detector geometry + wavelength (+ covariance) | `midas-calibrate-v2` | existing |
|
|
46
|
+
| pixels → I(Q) with σ (polygon-exact, pol/solid-angle/dark) | `midas-integrate-v2` | existing |
|
|
47
|
+
| atomic form factors f(Q), anomalous f′,f″ (differentiable) | `midas-hkls` | existing |
|
|
48
|
+
| Compton / incoherent subtraction | `midas-integrate-v2.corrections.compton` | existing |
|
|
49
|
+
| S(Q) → G(r) sine FT **with σ propagation** | `midas-integrate-v2.pdf` | existing |
|
|
50
|
+
| **polyatomic Faber-Ziman normalization** ⟨f²⟩, ⟨f⟩² | **`midas-pdf` (new)** | this package |
|
|
51
|
+
| **Δ-PDF (difference PDF) for time-resolved/operando** | **`midas-pdf` (new)** | this package |
|
|
52
|
+
|
|
53
|
+
The single piece that did not exist anywhere was the **composition layer**: the
|
|
54
|
+
existing `midas_integrate_v2.pdf.normalize_to_S` is *monoatomic* (it divides by a
|
|
55
|
+
single ⟨f²⟩). Real total scattering of a polyatomic sample needs the Faber-Ziman
|
|
56
|
+
form, which requires both ⟨f²⟩(Q) and ⟨f⟩²(Q) built from the sample composition.
|
|
57
|
+
That bridge — and the Δ-PDF helper — is all `midas-pdf` adds.
|
|
58
|
+
|
|
59
|
+
## What is novel
|
|
60
|
+
|
|
61
|
+
Every arrow in the chain is a torch operation carrying a 1σ uncertainty, so the
|
|
62
|
+
pipeline is **end-to-end differentiable** *and* **end-to-end error-propagating** —
|
|
63
|
+
a combination no production total-scattering tool (PDFgetX3 / PDFgetN / GudrunX)
|
|
64
|
+
offers. Concretely this enables:
|
|
65
|
+
|
|
66
|
+
* gradient-based **normalization refinement** (`refine.py`): `scale`/`offset`/ρ₀
|
|
67
|
+
are fit by L-BFGS against model-free physics (⟨S⟩→1 at high Q, G(r)=−4πρ₀r at
|
|
68
|
+
low r) — the "ad hoc scale twiddling" of PDF analysis becomes an optimization;
|
|
69
|
+
* an analytic 1σ band on every G(r) point, **validated** against a Monte-Carlo
|
|
70
|
+
bootstrap to <1% (`dev/demo_sigma_validation.py`) — error propagation is the
|
|
71
|
+
one thing existing software tends to drop;
|
|
72
|
+
* statistically-meaningful **Δ-PDF** difference maps for time-resolved studies:
|
|
73
|
+
σ²(ΔG) = σ²(G₁) + σ²(G₂), so a feature change can be tested against noise;
|
|
74
|
+
* differentiability in atomic positions (`validate.py`, Debye equation), so the
|
|
75
|
+
same code is a forward model for structure refinement against G(r).
|
|
76
|
+
|
|
77
|
+
## Modules
|
|
78
|
+
|
|
79
|
+
| Module | Contents |
|
|
80
|
+
|--------|----------|
|
|
81
|
+
| `composition.py` | `Composition` → ⟨f⟩(Q), ⟨f²⟩(Q), Laue term, Compton |
|
|
82
|
+
| `compton.py` | Hubbell tabulated incoherent scattering + Breit-Dirac recoil |
|
|
83
|
+
| `corrections.py` | Q-dependent detector efficiency, flat-plate self-absorption (MAC-backed) |
|
|
84
|
+
| `fluorescence.py` | `expected_fluorescence`: which elements fluoresce at a given energy |
|
|
85
|
+
| `multiple_scattering.py` | `lumped_background`: Tier-1 smooth MS/fluorescence/air background |
|
|
86
|
+
| `cross_section.py` | `differential_cross_section`: per-atom dσ/dΩ(Q) (MS engine) |
|
|
87
|
+
| `ms.py` | first-principles MS: analytic single + double scattering, Monte-Carlo references (slab + cylinder) |
|
|
88
|
+
| `ms_transport.py` | all-orders MS by differentiable discrete-ordinates radiative transfer (slab) |
|
|
89
|
+
| `structure.py` | differentiable small-box PDF (PDFfit-style) forward model + error-aware refinement |
|
|
90
|
+
| `normalize.py` | `faber_ziman_S`: I(Q) → S(Q) with σ (and lumped `background`) |
|
|
91
|
+
|
|
92
|
+
Runnable, one-per-capability demonstrations live in [`examples/`](examples/).
|
|
93
|
+
| `gr.py` | re-export of the reused sine FT (S(Q) → G(r) with σ) |
|
|
94
|
+
| `pipeline.py` | `i_of_q_to_Gr`: I(Q) → G(r) end to end |
|
|
95
|
+
| `frontend.py` | `image_to_iq`, `image_to_Gr`: detector pixels → G(r) |
|
|
96
|
+
| `conventions.py` | `structure_function_F`, `pair_distribution_g`, `total_correlation_T`, `radial_distribution_R` |
|
|
97
|
+
| `refine.py` | `refine_normalization`: differentiable scale/offset/ρ₀ fit |
|
|
98
|
+
| `deltapdf.py` | `delta_pdf`, `significant_mask`: difference PDF + n-σ test |
|
|
99
|
+
| `validate.py` | `debye_scattering_intensity`, `synthetic_powder_image`: model-free references |
|
|
100
|
+
|
|
101
|
+
## Quick start
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
import torch
|
|
105
|
+
from midas_pdf import Composition, i_of_q_to_Gr
|
|
106
|
+
|
|
107
|
+
comp = Composition({"Si": 1, "O": 2}) # SiO2, number fractions
|
|
108
|
+
q = torch.linspace(0.5, 25.0, 2000, dtype=torch.float64)
|
|
109
|
+
r = torch.linspace(0.0, 10.0, 1000, dtype=torch.float64)
|
|
110
|
+
|
|
111
|
+
# I_q, sigma_I come from midas-integrate-v2 (pixels -> I(Q) with sigma)
|
|
112
|
+
G, sigma_G, S = i_of_q_to_Gr(
|
|
113
|
+
q, I_q, comp, r,
|
|
114
|
+
wavelength_A=0.1665, sigma_intensity=sigma_I,
|
|
115
|
+
compton=True, q_max=22.0,
|
|
116
|
+
)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Conventions
|
|
120
|
+
|
|
121
|
+
Default is the **Faber-Ziman** total structure factor (X-ray, neutral-atom
|
|
122
|
+
form factors), matching the PDFgetX3 default. `S(Q) → 1` as `Q → ∞`;
|
|
123
|
+
`G(r) = (2/π) ∫ Q[S(Q)-1] sin(Qr) W(Q) dQ`. Window defaults to Lorch.
|
|
124
|
+
Convention choice (FZ vs Keen; which of S/F/G/g/D/T to report) is intended to be
|
|
125
|
+
settled with the experimental collaborators — see `dev/PLAN.md`.
|
|
126
|
+
|
|
127
|
+
See `dev/PLAN.md` for the phased build plan and open items.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# midas-pdf
|
|
2
|
+
|
|
3
|
+
**Differentiable, error-propagating total-scattering / pair-distribution-function
|
|
4
|
+
(PDF, *G(r)*) pipeline.**
|
|
5
|
+
|
|
6
|
+
`midas-pdf` is a deliberately thin layer. Almost everything it needs already
|
|
7
|
+
exists elsewhere in MIDAS and is reused rather than reimplemented:
|
|
8
|
+
|
|
9
|
+
| Stage | Provided by | Status |
|
|
10
|
+
|-------|-------------|--------|
|
|
11
|
+
| detector geometry + wavelength (+ covariance) | `midas-calibrate-v2` | existing |
|
|
12
|
+
| pixels → I(Q) with σ (polygon-exact, pol/solid-angle/dark) | `midas-integrate-v2` | existing |
|
|
13
|
+
| atomic form factors f(Q), anomalous f′,f″ (differentiable) | `midas-hkls` | existing |
|
|
14
|
+
| Compton / incoherent subtraction | `midas-integrate-v2.corrections.compton` | existing |
|
|
15
|
+
| S(Q) → G(r) sine FT **with σ propagation** | `midas-integrate-v2.pdf` | existing |
|
|
16
|
+
| **polyatomic Faber-Ziman normalization** ⟨f²⟩, ⟨f⟩² | **`midas-pdf` (new)** | this package |
|
|
17
|
+
| **Δ-PDF (difference PDF) for time-resolved/operando** | **`midas-pdf` (new)** | this package |
|
|
18
|
+
|
|
19
|
+
The single piece that did not exist anywhere was the **composition layer**: the
|
|
20
|
+
existing `midas_integrate_v2.pdf.normalize_to_S` is *monoatomic* (it divides by a
|
|
21
|
+
single ⟨f²⟩). Real total scattering of a polyatomic sample needs the Faber-Ziman
|
|
22
|
+
form, which requires both ⟨f²⟩(Q) and ⟨f⟩²(Q) built from the sample composition.
|
|
23
|
+
That bridge — and the Δ-PDF helper — is all `midas-pdf` adds.
|
|
24
|
+
|
|
25
|
+
## What is novel
|
|
26
|
+
|
|
27
|
+
Every arrow in the chain is a torch operation carrying a 1σ uncertainty, so the
|
|
28
|
+
pipeline is **end-to-end differentiable** *and* **end-to-end error-propagating** —
|
|
29
|
+
a combination no production total-scattering tool (PDFgetX3 / PDFgetN / GudrunX)
|
|
30
|
+
offers. Concretely this enables:
|
|
31
|
+
|
|
32
|
+
* gradient-based **normalization refinement** (`refine.py`): `scale`/`offset`/ρ₀
|
|
33
|
+
are fit by L-BFGS against model-free physics (⟨S⟩→1 at high Q, G(r)=−4πρ₀r at
|
|
34
|
+
low r) — the "ad hoc scale twiddling" of PDF analysis becomes an optimization;
|
|
35
|
+
* an analytic 1σ band on every G(r) point, **validated** against a Monte-Carlo
|
|
36
|
+
bootstrap to <1% (`dev/demo_sigma_validation.py`) — error propagation is the
|
|
37
|
+
one thing existing software tends to drop;
|
|
38
|
+
* statistically-meaningful **Δ-PDF** difference maps for time-resolved studies:
|
|
39
|
+
σ²(ΔG) = σ²(G₁) + σ²(G₂), so a feature change can be tested against noise;
|
|
40
|
+
* differentiability in atomic positions (`validate.py`, Debye equation), so the
|
|
41
|
+
same code is a forward model for structure refinement against G(r).
|
|
42
|
+
|
|
43
|
+
## Modules
|
|
44
|
+
|
|
45
|
+
| Module | Contents |
|
|
46
|
+
|--------|----------|
|
|
47
|
+
| `composition.py` | `Composition` → ⟨f⟩(Q), ⟨f²⟩(Q), Laue term, Compton |
|
|
48
|
+
| `compton.py` | Hubbell tabulated incoherent scattering + Breit-Dirac recoil |
|
|
49
|
+
| `corrections.py` | Q-dependent detector efficiency, flat-plate self-absorption (MAC-backed) |
|
|
50
|
+
| `fluorescence.py` | `expected_fluorescence`: which elements fluoresce at a given energy |
|
|
51
|
+
| `multiple_scattering.py` | `lumped_background`: Tier-1 smooth MS/fluorescence/air background |
|
|
52
|
+
| `cross_section.py` | `differential_cross_section`: per-atom dσ/dΩ(Q) (MS engine) |
|
|
53
|
+
| `ms.py` | first-principles MS: analytic single + double scattering, Monte-Carlo references (slab + cylinder) |
|
|
54
|
+
| `ms_transport.py` | all-orders MS by differentiable discrete-ordinates radiative transfer (slab) |
|
|
55
|
+
| `structure.py` | differentiable small-box PDF (PDFfit-style) forward model + error-aware refinement |
|
|
56
|
+
| `normalize.py` | `faber_ziman_S`: I(Q) → S(Q) with σ (and lumped `background`) |
|
|
57
|
+
|
|
58
|
+
Runnable, one-per-capability demonstrations live in [`examples/`](examples/).
|
|
59
|
+
| `gr.py` | re-export of the reused sine FT (S(Q) → G(r) with σ) |
|
|
60
|
+
| `pipeline.py` | `i_of_q_to_Gr`: I(Q) → G(r) end to end |
|
|
61
|
+
| `frontend.py` | `image_to_iq`, `image_to_Gr`: detector pixels → G(r) |
|
|
62
|
+
| `conventions.py` | `structure_function_F`, `pair_distribution_g`, `total_correlation_T`, `radial_distribution_R` |
|
|
63
|
+
| `refine.py` | `refine_normalization`: differentiable scale/offset/ρ₀ fit |
|
|
64
|
+
| `deltapdf.py` | `delta_pdf`, `significant_mask`: difference PDF + n-σ test |
|
|
65
|
+
| `validate.py` | `debye_scattering_intensity`, `synthetic_powder_image`: model-free references |
|
|
66
|
+
|
|
67
|
+
## Quick start
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
import torch
|
|
71
|
+
from midas_pdf import Composition, i_of_q_to_Gr
|
|
72
|
+
|
|
73
|
+
comp = Composition({"Si": 1, "O": 2}) # SiO2, number fractions
|
|
74
|
+
q = torch.linspace(0.5, 25.0, 2000, dtype=torch.float64)
|
|
75
|
+
r = torch.linspace(0.0, 10.0, 1000, dtype=torch.float64)
|
|
76
|
+
|
|
77
|
+
# I_q, sigma_I come from midas-integrate-v2 (pixels -> I(Q) with sigma)
|
|
78
|
+
G, sigma_G, S = i_of_q_to_Gr(
|
|
79
|
+
q, I_q, comp, r,
|
|
80
|
+
wavelength_A=0.1665, sigma_intensity=sigma_I,
|
|
81
|
+
compton=True, q_max=22.0,
|
|
82
|
+
)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Conventions
|
|
86
|
+
|
|
87
|
+
Default is the **Faber-Ziman** total structure factor (X-ray, neutral-atom
|
|
88
|
+
form factors), matching the PDFgetX3 default. `S(Q) → 1` as `Q → ∞`;
|
|
89
|
+
`G(r) = (2/π) ∫ Q[S(Q)-1] sin(Qr) W(Q) dQ`. Window defaults to Lorch.
|
|
90
|
+
Convention choice (FZ vs Keen; which of S/F/G/g/D/T to report) is intended to be
|
|
91
|
+
settled with the experimental collaborators — see `dev/PLAN.md`.
|
|
92
|
+
|
|
93
|
+
See `dev/PLAN.md` for the phased build plan and open items.
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""midas-pdf — differentiable, error-propagating total-scattering / G(r) pipeline.
|
|
2
|
+
|
|
3
|
+
Thin Faber-Ziman normalization layer over ``midas-hkls`` (form factors) and
|
|
4
|
+
``midas-integrate-v2`` (integration, Compton, sine FT with σ propagation).
|
|
5
|
+
|
|
6
|
+
Typical use::
|
|
7
|
+
|
|
8
|
+
from midas_pdf import Composition, i_of_q_to_Gr, delta_pdf
|
|
9
|
+
comp = Composition({"Si": 1, "O": 2})
|
|
10
|
+
G, sigma_G, S = i_of_q_to_Gr(q, I_q, comp, r, wavelength_A=0.1665,
|
|
11
|
+
sigma_intensity=sigma_I, q_max=22.0)
|
|
12
|
+
"""
|
|
13
|
+
# macOS (and some Linux setups) can hit an OpenMP-duplicate abort at import
|
|
14
|
+
# time because torch and numpy each ship their own libomp copy. We set the
|
|
15
|
+
# env var *before* torch loads so users get a clean import instead of a
|
|
16
|
+
# cryptic runtime crash on their first line. Users who prefer to control
|
|
17
|
+
# it themselves can already have set it, in which case setdefault is a no-op.
|
|
18
|
+
import os as _os
|
|
19
|
+
_os.environ.setdefault("KMP_DUPLICATE_LIB_OK", "TRUE")
|
|
20
|
+
|
|
21
|
+
from .composition import Composition
|
|
22
|
+
from .compton import breit_dirac_factor, incoherent_scattering
|
|
23
|
+
from .conventions import (
|
|
24
|
+
pair_distribution_g,
|
|
25
|
+
radial_distribution_R,
|
|
26
|
+
structure_function_F,
|
|
27
|
+
total_correlation_T,
|
|
28
|
+
)
|
|
29
|
+
from .corrections import (
|
|
30
|
+
apply_detector_efficiency,
|
|
31
|
+
detector_efficiency,
|
|
32
|
+
flat_plate_transmission,
|
|
33
|
+
linear_attenuation_um,
|
|
34
|
+
)
|
|
35
|
+
from .deltapdf import delta_pdf, significant_mask
|
|
36
|
+
from .fluorescence import expected_fluorescence
|
|
37
|
+
from .frontend import image_to_Gr, image_to_iq
|
|
38
|
+
from .gr import G_of_r, R_px_to_Q, estimate_background, fourier_sine_transform
|
|
39
|
+
from .cross_section import differential_cross_section, total_cross_section
|
|
40
|
+
from .ms import (
|
|
41
|
+
CYLINDER_SLAB_FACTOR,
|
|
42
|
+
cylinder_effective_tau,
|
|
43
|
+
ms_background_on_grid,
|
|
44
|
+
multiple_scattering_mc,
|
|
45
|
+
multiple_scattering_mc_cylinder,
|
|
46
|
+
slab_double_scattering,
|
|
47
|
+
slab_optical_params,
|
|
48
|
+
slab_single_scattering_factor,
|
|
49
|
+
)
|
|
50
|
+
from .ms_transport import slab_transport_ms
|
|
51
|
+
from .multiple_scattering import lumped_background, polynomial_basis
|
|
52
|
+
from .structure import build_pair_list, pdffit_gr, refine_structure
|
|
53
|
+
from .normalize import faber_ziman_S
|
|
54
|
+
from .pipeline import i_of_q_to_Gr
|
|
55
|
+
from .refine import RefineResult, refine_normalization
|
|
56
|
+
from .validate import (
|
|
57
|
+
debye_scattering_intensity,
|
|
58
|
+
interatomic_distances,
|
|
59
|
+
synthetic_powder_image,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
__version__ = "0.1.0"
|
|
63
|
+
|
|
64
|
+
__all__ = [
|
|
65
|
+
# core
|
|
66
|
+
"Composition",
|
|
67
|
+
"faber_ziman_S",
|
|
68
|
+
"i_of_q_to_Gr",
|
|
69
|
+
"fourier_sine_transform",
|
|
70
|
+
"G_of_r",
|
|
71
|
+
"R_px_to_Q",
|
|
72
|
+
"estimate_background",
|
|
73
|
+
# front-end (pixels → G(r))
|
|
74
|
+
"image_to_iq",
|
|
75
|
+
"image_to_Gr",
|
|
76
|
+
# convention / output-function family
|
|
77
|
+
"structure_function_F",
|
|
78
|
+
"pair_distribution_g",
|
|
79
|
+
"total_correlation_T",
|
|
80
|
+
"radial_distribution_R",
|
|
81
|
+
# corrections (Hubbell Compton, detector efficiency, absorption, fluorescence)
|
|
82
|
+
"incoherent_scattering",
|
|
83
|
+
"breit_dirac_factor",
|
|
84
|
+
"detector_efficiency",
|
|
85
|
+
"apply_detector_efficiency",
|
|
86
|
+
"flat_plate_transmission",
|
|
87
|
+
"linear_attenuation_um",
|
|
88
|
+
"expected_fluorescence",
|
|
89
|
+
# multiple scattering (Tier-1 lumped smooth background)
|
|
90
|
+
"lumped_background",
|
|
91
|
+
"polynomial_basis",
|
|
92
|
+
# multiple scattering (Tier-2/3: cross-section + first-principles estimators)
|
|
93
|
+
"differential_cross_section",
|
|
94
|
+
"total_cross_section",
|
|
95
|
+
"slab_single_scattering_factor",
|
|
96
|
+
"slab_optical_params",
|
|
97
|
+
"multiple_scattering_mc",
|
|
98
|
+
"slab_double_scattering",
|
|
99
|
+
"slab_transport_ms",
|
|
100
|
+
"multiple_scattering_mc_cylinder",
|
|
101
|
+
"cylinder_effective_tau",
|
|
102
|
+
"CYLINDER_SLAB_FACTOR",
|
|
103
|
+
"ms_background_on_grid",
|
|
104
|
+
# differentiable refinement
|
|
105
|
+
"refine_normalization",
|
|
106
|
+
"RefineResult",
|
|
107
|
+
# small-box structure modelling (PDFfit-style, error-aware)
|
|
108
|
+
"pdffit_gr",
|
|
109
|
+
"build_pair_list",
|
|
110
|
+
"refine_structure",
|
|
111
|
+
# Δ-PDF
|
|
112
|
+
"delta_pdf",
|
|
113
|
+
"significant_mask",
|
|
114
|
+
# model-free references / forward model
|
|
115
|
+
"debye_scattering_intensity",
|
|
116
|
+
"interatomic_distances",
|
|
117
|
+
"synthetic_powder_image",
|
|
118
|
+
]
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"""Rev-15 anisotropic-ADP + partial-occupancy PDF refinement.
|
|
2
|
+
|
|
3
|
+
The Rev-8 :func:`midas_pdf.structure.refine_structure` refines a single
|
|
4
|
+
isotropic ``u_iso`` and full occupancy on every site. Real fits of low-
|
|
5
|
+
symmetry crystals, oxide superlattices, and solid solutions need:
|
|
6
|
+
|
|
7
|
+
* per-site **anisotropic** displacement tensors ``U_ij`` (3×3 symmetric,
|
|
8
|
+
6 unique entries), and
|
|
9
|
+
* per-site **partial occupancy** (defect-vacancy / solid-solution work).
|
|
10
|
+
|
|
11
|
+
This module adds a differentiable L-BFGS refiner that co-refines those two
|
|
12
|
+
sets of DOFs together with the cubic-lattice constant and PDF scale, and
|
|
13
|
+
reports Hessian-derived 1σ on every DOF. It preserves the diagonal
|
|
14
|
+
constraint on ``U`` (only the six unique entries — ``U11 U22 U33 U12 U13 U23``
|
|
15
|
+
— are stored and refined; the tensor is reconstructed symmetrically each
|
|
16
|
+
forward pass). A positive-definiteness soft penalty keeps the fit
|
|
17
|
+
physical without needing a hard constraint.
|
|
18
|
+
"""
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from typing import Optional
|
|
22
|
+
|
|
23
|
+
import numpy as np
|
|
24
|
+
import torch
|
|
25
|
+
|
|
26
|
+
from .structure import PairList, pdffit_gr
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
"u_vector_to_matrix",
|
|
31
|
+
"u_matrix_to_vector",
|
|
32
|
+
"refine_aniso_occupancy",
|
|
33
|
+
"AnisoRefineResult",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def u_vector_to_matrix(u_vec: torch.Tensor) -> torch.Tensor:
|
|
38
|
+
"""Expand (n_uc, 6) unique entries [U11 U22 U33 U12 U13 U23] to
|
|
39
|
+
(n_uc, 3, 3) symmetric tensors, differentiable."""
|
|
40
|
+
u = torch.as_tensor(u_vec, dtype=torch.float64)
|
|
41
|
+
if u.ndim != 2 or u.shape[1] != 6:
|
|
42
|
+
raise ValueError(f"u_vec must be (n_uc, 6), got {tuple(u.shape)}")
|
|
43
|
+
n = u.shape[0]
|
|
44
|
+
U = torch.zeros((n, 3, 3), dtype=torch.float64)
|
|
45
|
+
U[:, 0, 0] = u[:, 0]
|
|
46
|
+
U[:, 1, 1] = u[:, 1]
|
|
47
|
+
U[:, 2, 2] = u[:, 2]
|
|
48
|
+
U[:, 0, 1] = u[:, 3]; U[:, 1, 0] = u[:, 3]
|
|
49
|
+
U[:, 0, 2] = u[:, 4]; U[:, 2, 0] = u[:, 4]
|
|
50
|
+
U[:, 1, 2] = u[:, 5]; U[:, 2, 1] = u[:, 5]
|
|
51
|
+
return U
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def u_matrix_to_vector(U: torch.Tensor) -> torch.Tensor:
|
|
55
|
+
"""Pack a (n_uc, 3, 3) symmetric tensor into (n_uc, 6) unique entries."""
|
|
56
|
+
U = torch.as_tensor(U, dtype=torch.float64)
|
|
57
|
+
if U.ndim != 3 or U.shape[1:] != (3, 3):
|
|
58
|
+
raise ValueError(f"U must be (n_uc, 3, 3), got {tuple(U.shape)}")
|
|
59
|
+
return torch.stack([U[:, 0, 0], U[:, 1, 1], U[:, 2, 2],
|
|
60
|
+
U[:, 0, 1], U[:, 0, 2], U[:, 1, 2]], dim=1)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class AnisoRefineResult(dict):
|
|
64
|
+
def __getattr__(self, name):
|
|
65
|
+
try: return self[name]
|
|
66
|
+
except KeyError: raise AttributeError(name) from None
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def refine_aniso_occupancy(
|
|
70
|
+
crystal_tensor,
|
|
71
|
+
r: torch.Tensor | np.ndarray,
|
|
72
|
+
G_obs: torch.Tensor | np.ndarray,
|
|
73
|
+
pairs: PairList,
|
|
74
|
+
*,
|
|
75
|
+
sigma_obs: Optional[torch.Tensor | np.ndarray] = None,
|
|
76
|
+
init_a: Optional[float] = None,
|
|
77
|
+
init_u_iso: float = 0.006,
|
|
78
|
+
init_scale: float = 1.0,
|
|
79
|
+
refine_aniso: bool = True,
|
|
80
|
+
refine_occupancy: bool = False,
|
|
81
|
+
steps: int = 200,
|
|
82
|
+
lr: float = 0.05,
|
|
83
|
+
positive_definite_penalty: float = 1e3,
|
|
84
|
+
) -> AnisoRefineResult:
|
|
85
|
+
"""Co-refine cubic-``a``, ``scale``, per-site aniso ADPs and (optional)
|
|
86
|
+
per-site occupancy against ``G_obs``.
|
|
87
|
+
|
|
88
|
+
Parameter layout is:
|
|
89
|
+
|
|
90
|
+
θ = [a, scale] + (6 * n_uc aniso entries if refine_aniso else 0)
|
|
91
|
+
+ (n_uc occupancies if refine_occupancy else 0)
|
|
92
|
+
|
|
93
|
+
On ``refine_aniso=False`` the fit still uses ``u_iso`` on every site so
|
|
94
|
+
the interface stays uniform. The positive-definite penalty adds
|
|
95
|
+
``λ · sum(relu(-eigvals(U)))²`` to χ² — cheap, differentiable, and
|
|
96
|
+
it turns off automatically once every U is p.d.
|
|
97
|
+
"""
|
|
98
|
+
r_t = torch.as_tensor(r, dtype=torch.float64)
|
|
99
|
+
G_t = torch.as_tensor(G_obs, dtype=torch.float64)
|
|
100
|
+
w = (torch.ones_like(G_t) if sigma_obs is None
|
|
101
|
+
else 1.0 / torch.as_tensor(sigma_obs, dtype=torch.float64).clamp(min=1e-12) ** 2)
|
|
102
|
+
|
|
103
|
+
lat0 = crystal_tensor.lattice_params.detach()
|
|
104
|
+
angles = lat0[3:]
|
|
105
|
+
a0 = float(lat0[0]) if init_a is None else float(init_a)
|
|
106
|
+
n_uc = pairs.n_uc
|
|
107
|
+
|
|
108
|
+
theta_parts = [torch.tensor([a0, init_scale], dtype=torch.float64)]
|
|
109
|
+
n_aniso = 6 * n_uc if refine_aniso else 0
|
|
110
|
+
if refine_aniso:
|
|
111
|
+
u_init = torch.tensor([init_u_iso, init_u_iso, init_u_iso, 0.0, 0.0, 0.0],
|
|
112
|
+
dtype=torch.float64)
|
|
113
|
+
theta_parts.append(u_init.repeat(n_uc))
|
|
114
|
+
n_occ = n_uc if refine_occupancy else 0
|
|
115
|
+
if refine_occupancy:
|
|
116
|
+
theta_parts.append(torch.ones(n_uc, dtype=torch.float64))
|
|
117
|
+
theta = torch.cat(theta_parts).clone().requires_grad_(True)
|
|
118
|
+
|
|
119
|
+
def unpack(th):
|
|
120
|
+
a = th[0]
|
|
121
|
+
scale = th[1]
|
|
122
|
+
idx = 2
|
|
123
|
+
u_aniso = None
|
|
124
|
+
occupancy = None
|
|
125
|
+
if refine_aniso:
|
|
126
|
+
u_flat = th[idx:idx + n_aniso].reshape(n_uc, 6)
|
|
127
|
+
u_aniso = u_vector_to_matrix(u_flat)
|
|
128
|
+
idx += n_aniso
|
|
129
|
+
if refine_occupancy:
|
|
130
|
+
occupancy = th[idx:idx + n_occ].clamp(min=0.0, max=1.0)
|
|
131
|
+
idx += n_occ
|
|
132
|
+
return a, scale, u_aniso, occupancy
|
|
133
|
+
|
|
134
|
+
def model(th):
|
|
135
|
+
a, scale, u_aniso, occupancy = unpack(th)
|
|
136
|
+
lp = torch.cat([a.reshape(1).expand(3), angles])
|
|
137
|
+
kwargs = {"lattice_params": lp, "scale": scale}
|
|
138
|
+
if u_aniso is not None:
|
|
139
|
+
kwargs["u_aniso"] = u_aniso
|
|
140
|
+
else:
|
|
141
|
+
kwargs["u_iso"] = init_u_iso
|
|
142
|
+
if occupancy is not None:
|
|
143
|
+
kwargs["occupancy"] = occupancy
|
|
144
|
+
return pdffit_gr(crystal_tensor, r_t, pairs, **kwargs)
|
|
145
|
+
|
|
146
|
+
def chi2(th):
|
|
147
|
+
res = G_t - model(th)
|
|
148
|
+
loss = (w * res * res).sum()
|
|
149
|
+
if refine_aniso and positive_definite_penalty > 0.0:
|
|
150
|
+
_, _, u_aniso, _ = unpack(th)
|
|
151
|
+
eigs = torch.linalg.eigvalsh(u_aniso) # (n_uc, 3)
|
|
152
|
+
neg = torch.relu(-eigs)
|
|
153
|
+
loss = loss + positive_definite_penalty * (neg * neg).sum()
|
|
154
|
+
return loss
|
|
155
|
+
|
|
156
|
+
opt = torch.optim.LBFGS([theta], lr=lr, max_iter=steps,
|
|
157
|
+
line_search_fn="strong_wolfe")
|
|
158
|
+
history: list[float] = []
|
|
159
|
+
|
|
160
|
+
def closure():
|
|
161
|
+
opt.zero_grad()
|
|
162
|
+
loss = chi2(theta)
|
|
163
|
+
loss.backward()
|
|
164
|
+
history.append(float(loss.detach()))
|
|
165
|
+
return loss
|
|
166
|
+
|
|
167
|
+
opt.step(closure)
|
|
168
|
+
|
|
169
|
+
with torch.no_grad():
|
|
170
|
+
th_det = theta.detach()
|
|
171
|
+
a_val, scale_val, u_aniso_val, occ_val = unpack(th_det)
|
|
172
|
+
G_fit = model(th_det)
|
|
173
|
+
ndof = max(int(G_t.numel()) - theta.numel(), 1)
|
|
174
|
+
chi2_red = float((w * (G_t - G_fit) ** 2).sum()) / ndof
|
|
175
|
+
|
|
176
|
+
# Hessian uncertainties (may be huge for weakly-identified aniso entries).
|
|
177
|
+
uncert = {}
|
|
178
|
+
try:
|
|
179
|
+
th_leaf = theta.detach().clone().requires_grad_(True)
|
|
180
|
+
H = torch.autograd.functional.hessian(chi2, th_leaf)
|
|
181
|
+
cov = 2.0 * torch.linalg.pinv(H)
|
|
182
|
+
sig = torch.sqrt(torch.diagonal(cov).clamp(min=0.0))
|
|
183
|
+
uncert["a"] = float(sig[0])
|
|
184
|
+
uncert["scale"] = float(sig[1])
|
|
185
|
+
idx = 2
|
|
186
|
+
if refine_aniso:
|
|
187
|
+
uncert["u_aniso"] = sig[idx:idx + n_aniso].reshape(n_uc, 6).tolist()
|
|
188
|
+
idx += n_aniso
|
|
189
|
+
if refine_occupancy:
|
|
190
|
+
uncert["occupancy"] = sig[idx:idx + n_occ].tolist()
|
|
191
|
+
except Exception:
|
|
192
|
+
uncert = {"error": "Hessian failed"}
|
|
193
|
+
|
|
194
|
+
return AnisoRefineResult(
|
|
195
|
+
fitted={
|
|
196
|
+
"a": float(a_val),
|
|
197
|
+
"scale": float(scale_val),
|
|
198
|
+
"u_aniso": u_aniso_val.tolist() if u_aniso_val is not None else None,
|
|
199
|
+
"occupancy": occ_val.tolist() if occ_val is not None else None,
|
|
200
|
+
},
|
|
201
|
+
uncertainty=uncert,
|
|
202
|
+
G_calc=G_fit.detach(),
|
|
203
|
+
chi2_reduced=chi2_red,
|
|
204
|
+
loss=history[-1] if history else float("nan"),
|
|
205
|
+
history=history, loss_history=history,
|
|
206
|
+
)
|