xrayscatteringtools 1.1.0__tar.gz → 1.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/PKG-INFO +4 -4
  2. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/README.md +3 -3
  3. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/pyproject.toml +1 -1
  4. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/src/xrayscatteringtools/theory/__init__.py +4 -1
  5. xrayscatteringtools-1.2.0/src/xrayscatteringtools/theory/data/patterns/NBD__CCSD_FC__aug_cc_pVDZ.h5 +0 -0
  6. xrayscatteringtools-1.2.0/src/xrayscatteringtools/theory/data/patterns/NBD__HF__cc_pVTZ.h5 +0 -0
  7. xrayscatteringtools-1.2.0/src/xrayscatteringtools/theory/data/patterns/QC__CCSD_FC__aug_cc_pVDZ.h5 +0 -0
  8. xrayscatteringtools-1.2.0/src/xrayscatteringtools/theory/data/patterns/QC__HF__cc_pVTZ.h5 +0 -0
  9. xrayscatteringtools-1.2.0/src/xrayscatteringtools/theory/data/patterns/SF6__CCSD_FC__aug_cc_pVDZ.h5 +0 -0
  10. xrayscatteringtools-1.2.0/src/xrayscatteringtools/theory/data/patterns/SF6__CCSD_FC__aug_cc_pVTZ.h5 +0 -0
  11. xrayscatteringtools-1.2.0/src/xrayscatteringtools/theory/data/patterns/SF6__HF__cc_pVTZ.h5 +0 -0
  12. xrayscatteringtools-1.2.0/src/xrayscatteringtools/theory/data/patterns/convert_thistle_to_h5.py +67 -0
  13. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/src/xrayscatteringtools/theory/patterns.py +41 -22
  14. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/src/xrayscatteringtools/theory/patterns.pyi +12 -0
  15. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/tests/test_patterns.py +60 -0
  16. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/.gitattributes +0 -0
  17. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/.github/workflows/tests.yml +0 -0
  18. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/.gitignore +0 -0
  19. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/LICENSE +0 -0
  20. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/src/xrayscatteringtools/__init__.py +0 -0
  21. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/src/xrayscatteringtools/azimuthal.py +0 -0
  22. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/src/xrayscatteringtools/calib/__init__.py +0 -0
  23. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/src/xrayscatteringtools/calib/geometry_calibration.py +0 -0
  24. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/src/xrayscatteringtools/calib/masking.py +0 -0
  25. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/src/xrayscatteringtools/calib/scattering_corrections.py +0 -0
  26. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/src/xrayscatteringtools/calib/timetool_calibration.py +0 -0
  27. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/src/xrayscatteringtools/data/Jungfrau4M.h5 +0 -0
  28. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/src/xrayscatteringtools/epicsArch.py +0 -0
  29. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/src/xrayscatteringtools/io.py +0 -0
  30. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/src/xrayscatteringtools/plotting.py +0 -0
  31. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/src/xrayscatteringtools/theory/data/IAM/Compton_Factors.npy +0 -0
  32. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/src/xrayscatteringtools/theory/data/IAM/Scattering_Factors.npy +0 -0
  33. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/src/xrayscatteringtools/theory/data/geometries/SF6__CCSD_T_DHK__aug_cc_pV5Z_DK.h5 +0 -0
  34. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/src/xrayscatteringtools/theory/data/geometries/make_geometry_h5.py +0 -0
  35. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/src/xrayscatteringtools/theory/data/patterns/Ne__CCSD_X2C__aug_cc_pVDZ_DK.h5 +0 -0
  36. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/src/xrayscatteringtools/theory/data/patterns/Ne__CCSD_X2C__aug_cc_pVQZ_DK.h5 +0 -0
  37. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/src/xrayscatteringtools/theory/data/patterns/Ne__CCSD_X2C__aug_cc_pVTZ_DK.h5 +0 -0
  38. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/src/xrayscatteringtools/theory/data/patterns/SF6__CCSD__aug_cc_pVDZ.h5 +0 -0
  39. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/src/xrayscatteringtools/theory/data/patterns/SF6__HF__aug_cc_pVDZ.h5 +0 -0
  40. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/src/xrayscatteringtools/theory/data/patterns/SF6__MP2__aug_cc_pVDZ.h5 +0 -0
  41. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/src/xrayscatteringtools/theory/data/patterns/convert_ab_initio_to_h5.py +0 -0
  42. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/src/xrayscatteringtools/theory/geometries.py +0 -0
  43. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/src/xrayscatteringtools/theory/geometries.pyi +0 -0
  44. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/src/xrayscatteringtools/theory/iam.py +0 -0
  45. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/src/xrayscatteringtools/utils.py +0 -0
  46. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/tests/__init__.py +0 -0
  47. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/tests/old/DMP_D.xyz +0 -0
  48. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/tests/old/DMP_L1.xyz +0 -0
  49. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/tests/old/DMP_L2.xyz +0 -0
  50. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/tests/old/DMP_ground.xyz +0 -0
  51. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/tests/old/azav_test.ipynb +0 -0
  52. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/tests/old/calib_test.ipynb +0 -0
  53. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/tests/old/geometries_test.ipynb +0 -0
  54. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/tests/old/iam_oriented_test.ipynb +0 -0
  55. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/tests/old/iam_pattern_test.ipynb +0 -0
  56. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/tests/old/patterns_test.ipynb +0 -0
  57. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/tests/sf6_test.xyz +0 -0
  58. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/tests/test_azimuthal.py +0 -0
  59. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/tests/test_geometries.py +0 -0
  60. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/tests/test_geometry_calibration.py +0 -0
  61. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/tests/test_iam.py +0 -0
  62. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/tests/test_io.py +0 -0
  63. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/tests/test_masking.py +0 -0
  64. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/tests/test_plotting.py +0 -0
  65. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/tests/test_scattering_corrections.py +0 -0
  66. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/tests/test_timetool_calibration.py +0 -0
  67. {xrayscatteringtools-1.1.0 → xrayscatteringtools-1.2.0}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: xrayscatteringtools
3
- Version: 1.1.0
3
+ Version: 1.2.0
4
4
  Summary: A library for gas phase analysis at the CXI endstation
5
5
  Project-URL: Homepage, https://github.com/Weber-Group/xrayscatteringtools
6
6
  Project-URL: Issues, https://github.com/Weber-Group/xrayscatteringtools/issues
@@ -108,13 +108,13 @@ Calibration and correction tools:
108
108
  Theoretical scattering models:
109
109
  - **`iam`** — Independent Atom Model: isotropic and oriented elastic/inelastic scattering patterns (`iam_elastic_pattern`, `iam_inelastic_pattern`, `iam_total_pattern`, and their `_oriented` variants), `iam_compton_spectrum`, and `iam_inelastic_pattern_from_formula` (Compton intensity from a chemical formula alone, no coordinates needed).
110
110
  - **`geometries`** — Lazy-loaded optimized molecular geometries from bundled HDF5 data (accessed as module-level attributes, e.g. `geometries.SF6__CCSD_T_DHK__aug_cc_pV5Z_DK`).
111
- - **`patterns`** — Lazy-loaded _ab initio_ scattering patterns from bundled HDF5 data (accessed as module-level attributes named `molecule__method__basis_set`, e.g. `patterns.SF6__HF__aug_cc_pVDZ`). Available: SF6 at the HF, MP2, and CCSD levels (aug-cc-pVDZ), and Ne at the CCSD+X2C level (aug-cc-pVDZ/TZ/QZ+DK). Some patterns carry a `notes` attribute with details of the calculation.
111
+ - **`patterns`** — Lazy-loaded _ab initio_ scattering patterns from bundled HDF5 data (accessed as module-level attributes named `molecule__method__basis_set`, e.g. `patterns.SF6__HF__aug_cc_pVDZ`). Available: SF6 at the HF, MP2, and CCSD levels (aug-cc-pVDZ), HF (cc-pVTZ), and frozen-core CCSD (aug-cc-pVDZ/TZ); Ne at the CCSD+X2C level (aug-cc-pVDZ/TZ/QZ+DK); and norbornadiene (`NBD`) and quadricyclane (`QC`) at HF (cc-pVTZ) and frozen-core CCSD (aug-cc-pVDZ). Every pattern has the total `I_q`; many also carry `I_q_elastic`/`I_q_inelastic`, and the newer ones add the `I_q_hartree`/`I_q_exchange`/`I_q_cumulant` decomposition. Some patterns carry a `notes` attribute with details of the calculation.
112
112
 
113
113
  ---
114
114
 
115
115
  ## Testing
116
116
 
117
- The test suite uses [pytest](https://docs.pytest.org/) and covers all modules (460+ tests):
117
+ The test suite uses [pytest](https://docs.pytest.org/) and covers all modules (580+ tests):
118
118
  ```bash
119
119
  pip install -e ".[dev]"
120
120
  python -m pytest tests/
@@ -138,4 +138,4 @@ This project is licensed under the MIT License. See the [LICENSE](LICENSE) file
138
138
 
139
139
  ## Acknowledgments
140
140
 
141
- This library was created by [David J. Romano](https://david.lizaanddavid.com) by compiling and standardizing code from previous data analysis pipelines. Maintained by the [Weber Research Group](https://sites.brown.edu/weber-lab/) and developed by collaborators to facilitate X-ray scattering research.
141
+ This library was created by [David J. Romano](https://davidromano.dev) by compiling and standardizing code from previous data analysis pipelines. Maintained by the [Weber Research Group](https://sites.brown.edu/weber-lab/) and developed by collaborators to facilitate X-ray scattering research.
@@ -80,13 +80,13 @@ Calibration and correction tools:
80
80
  Theoretical scattering models:
81
81
  - **`iam`** — Independent Atom Model: isotropic and oriented elastic/inelastic scattering patterns (`iam_elastic_pattern`, `iam_inelastic_pattern`, `iam_total_pattern`, and their `_oriented` variants), `iam_compton_spectrum`, and `iam_inelastic_pattern_from_formula` (Compton intensity from a chemical formula alone, no coordinates needed).
82
82
  - **`geometries`** — Lazy-loaded optimized molecular geometries from bundled HDF5 data (accessed as module-level attributes, e.g. `geometries.SF6__CCSD_T_DHK__aug_cc_pV5Z_DK`).
83
- - **`patterns`** — Lazy-loaded _ab initio_ scattering patterns from bundled HDF5 data (accessed as module-level attributes named `molecule__method__basis_set`, e.g. `patterns.SF6__HF__aug_cc_pVDZ`). Available: SF6 at the HF, MP2, and CCSD levels (aug-cc-pVDZ), and Ne at the CCSD+X2C level (aug-cc-pVDZ/TZ/QZ+DK). Some patterns carry a `notes` attribute with details of the calculation.
83
+ - **`patterns`** — Lazy-loaded _ab initio_ scattering patterns from bundled HDF5 data (accessed as module-level attributes named `molecule__method__basis_set`, e.g. `patterns.SF6__HF__aug_cc_pVDZ`). Available: SF6 at the HF, MP2, and CCSD levels (aug-cc-pVDZ), HF (cc-pVTZ), and frozen-core CCSD (aug-cc-pVDZ/TZ); Ne at the CCSD+X2C level (aug-cc-pVDZ/TZ/QZ+DK); and norbornadiene (`NBD`) and quadricyclane (`QC`) at HF (cc-pVTZ) and frozen-core CCSD (aug-cc-pVDZ). Every pattern has the total `I_q`; many also carry `I_q_elastic`/`I_q_inelastic`, and the newer ones add the `I_q_hartree`/`I_q_exchange`/`I_q_cumulant` decomposition. Some patterns carry a `notes` attribute with details of the calculation.
84
84
 
85
85
  ---
86
86
 
87
87
  ## Testing
88
88
 
89
- The test suite uses [pytest](https://docs.pytest.org/) and covers all modules (460+ tests):
89
+ The test suite uses [pytest](https://docs.pytest.org/) and covers all modules (580+ tests):
90
90
  ```bash
91
91
  pip install -e ".[dev]"
92
92
  python -m pytest tests/
@@ -110,4 +110,4 @@ This project is licensed under the MIT License. See the [LICENSE](LICENSE) file
110
110
 
111
111
  ## Acknowledgments
112
112
 
113
- This library was created by [David J. Romano](https://david.lizaanddavid.com) by compiling and standardizing code from previous data analysis pipelines. Maintained by the [Weber Research Group](https://sites.brown.edu/weber-lab/) and developed by collaborators to facilitate X-ray scattering research.
113
+ This library was created by [David J. Romano](https://davidromano.dev) by compiling and standardizing code from previous data analysis pipelines. Maintained by the [Weber Research Group](https://sites.brown.edu/weber-lab/) and developed by collaborators to facilitate X-ray scattering research.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "xrayscatteringtools"
7
- version = "1.1.0"
7
+ version = "1.2.0"
8
8
  description = "A library for gas phase analysis at the CXI endstation"
9
9
  authors = [
10
10
  { name = "David Romano", email = "david_j_romano@brown.edu" }
@@ -4,4 +4,7 @@ from .iam import iam_compton_spectrum, iam_inelastic_pattern_from_formula
4
4
 
5
5
  from .geometries import SF6__CCSD_T_DHK__aug_cc_pV5Z_DK
6
6
 
7
- from .patterns import SF6__CCSD__aug_cc_pVDZ, SF6__MP2__aug_cc_pVDZ, SF6__HF__aug_cc_pVDZ
7
+ from .patterns import SF6__CCSD__aug_cc_pVDZ, SF6__MP2__aug_cc_pVDZ, SF6__HF__aug_cc_pVDZ
8
+ from .patterns import SF6__HF__cc_pVTZ, SF6__CCSD_FC__aug_cc_pVDZ, SF6__CCSD_FC__aug_cc_pVTZ
9
+ from .patterns import Ne__CCSD_X2C__aug_cc_pVDZ_DK, Ne__CCSD_X2C__aug_cc_pVTZ_DK, Ne__CCSD_X2C__aug_cc_pVQZ_DK
10
+ from .patterns import NBD__HF__cc_pVTZ, NBD__CCSD_FC__aug_cc_pVDZ, QC__HF__cc_pVTZ, QC__CCSD_FC__aug_cc_pVDZ
@@ -0,0 +1,67 @@
1
+ import json
2
+ import numpy as np
3
+ import h5py
4
+
5
+ # Converts thistle .csv exports into the .h5 format read by xrayscatteringtools.theory.patterns.
6
+ # Expected .csv layout:
7
+ # line 1: "# <label> q in 1/A {json metadata}"
8
+ # line 2: "q (1/A),total,elastic,inelastic,hartree,exchange[,cumulant]"
9
+ # rest: comma separated data, q in inverse angstroms
10
+ # The total satisfies I_q = n_electrons + hartree + exchange + cumulant (cumulant is zero for HF).
11
+ # Paths are relative to the current working directory.
12
+
13
+ ##################### User Set Parameters #####################
14
+ # (csv file, molecule, method, basis set, n_electrons, notes)
15
+ # h5 file naming convention is molecule__method__basisset.h5
16
+ NBD_NOTES = "NBD is norbornadiene (bicyclo[2.2.1]hepta-2,5-diene, C7H8)."
17
+ QC_NOTES = "QC is quadricyclane (tetracyclo[3.2.0.0^{2,7}.0^{4,6}]heptane, C7H8), the valence isomer of norbornadiene."
18
+ FC_NOTES = "CCSD with frozen core electrons."
19
+ conversions = [
20
+ ("nb_hf_cc-pvtz.csv", "NBD", "HF", "cc_pVTZ", 50, NBD_NOTES),
21
+ ("nb_ccsd_aug-cc-pvdz_fc.csv", "NBD", "CCSD_FC", "aug_cc_pVDZ", 50, f"{NBD_NOTES} {FC_NOTES}"),
22
+ ("qd_hf_cc-pvtz.csv", "QC", "HF", "cc_pVTZ", 50, QC_NOTES),
23
+ ("qd_ccsd_aug-cc-pvdz_fc.csv", "QC", "CCSD_FC", "aug_cc_pVDZ", 50, f"{QC_NOTES} {FC_NOTES}"),
24
+ ("sf6_hf_cc-pvtz.csv", "SF6", "HF", "cc_pVTZ", 70, ""),
25
+ ("sf6_ccsd_aug-cc-pvdz_fc.csv", "SF6", "CCSD_FC", "aug_cc_pVDZ", 70, FC_NOTES),
26
+ ("sf6_ccsd_aug-cc-pvtz_fc.csv", "SF6", "CCSD_FC", "aug_cc_pVTZ", 70, FC_NOTES),
27
+ ]
28
+ ##################### End User Set Parameters #####################
29
+
30
+ # csv column -> h5 dataset
31
+ column_map = {
32
+ "q (1/A)": "q",
33
+ "total": "I_q",
34
+ "elastic": "I_q_elastic",
35
+ "inelastic": "I_q_inelastic",
36
+ "hartree": "I_q_hartree",
37
+ "exchange": "I_q_exchange",
38
+ "cumulant": "I_q_cumulant",
39
+ }
40
+
41
+ for csv_file, molecule, method, basis_set, n_electrons, notes in conversions:
42
+ with open(csv_file) as f:
43
+ header = f.readline()
44
+ columns = f.readline().strip().split(",")
45
+ metadata = json.loads(header[header.index("{"):])
46
+ if metadata["q_unit"] != "1/A":
47
+ raise ValueError(f"{csv_file}: expected q in 1/A, got {metadata['q_unit']}")
48
+ data = np.loadtxt(csv_file, delimiter=",", skiprows=2)
49
+ datasets = {column_map[c]: data[:, i] for i, c in enumerate(columns)}
50
+ # Forward scattering is n_electrons**2, catches a wrong electron count
51
+ if not np.isclose(datasets["I_q"][0], n_electrons**2, rtol=1e-6) or datasets["q"][0] != 0:
52
+ raise ValueError(f"{csv_file}: I(q=0) = {datasets['I_q'][0]}, expected {n_electrons**2}")
53
+
54
+ h5_filename = f"{molecule}__{method}__{basis_set}.h5"
55
+ with h5py.File(h5_filename, "w") as f:
56
+ for name, values in datasets.items():
57
+ f.create_dataset(name, data=values)
58
+ # Add metadata as attributes
59
+ f.attrs["molecule"] = molecule
60
+ f.attrs["method"] = method
61
+ f.attrs["basis_set"] = basis_set
62
+ f.attrs["n_electrons"] = n_electrons
63
+ f.attrs["source"] = f"{metadata['from']['source']} via thistle {metadata['thistle']} ({metadata['kind']}, computed {metadata['created']})"
64
+ f.attrs["metadata"] = json.dumps(metadata)
65
+ if notes:
66
+ f.attrs["notes"] = notes
67
+ print(f"{csv_file} -> {h5_filename}")
@@ -10,7 +10,14 @@ __all__ = [
10
10
  'SF6__HF__aug_cc_pVDZ',
11
11
  'Ne__CCSD_X2C__aug_cc_pVDZ_DK',
12
12
  'Ne__CCSD_X2C__aug_cc_pVTZ_DK',
13
- 'Ne__CCSD_X2C__aug_cc_pVQZ_DK'
13
+ 'Ne__CCSD_X2C__aug_cc_pVQZ_DK',
14
+ 'SF6__HF__cc_pVTZ',
15
+ 'SF6__CCSD_FC__aug_cc_pVDZ',
16
+ 'SF6__CCSD_FC__aug_cc_pVTZ',
17
+ 'NBD__HF__cc_pVTZ',
18
+ 'NBD__CCSD_FC__aug_cc_pVDZ',
19
+ 'QC__HF__cc_pVTZ',
20
+ 'QC__CCSD_FC__aug_cc_pVDZ',
14
21
  ]
15
22
  def __dir__():
16
23
  # Tab completion for IPython
@@ -19,6 +26,17 @@ def __dir__():
19
26
 
20
27
  _data_path = pathlib.Path(__file__).parent / "data/patterns"
21
28
 
29
+ # Optional I_q components a pattern file may carry, with their docstring descriptions.
30
+ _COMPONENTS = {
31
+ "I_q_elastic": "Elastic scattering intensity values corresponding to `q`.",
32
+ "I_q_inelastic": "Inelastic scattering intensity values corresponding to `q`.",
33
+ "I_q_hartree": "Hartree (direct) part of the two-electron term, built from the one-electron density alone. Equal to `I_q_elastic`.",
34
+ "I_q_exchange": "Exchange part of the two-electron term.",
35
+ "I_q_cumulant": "Cumulant (electron correlation) part of the two-electron term. Absent for HF, where it is zero.",
36
+ }
37
+ # Provenance for files made before the 'source' attribute existed
38
+ _DEFAULT_SOURCE = "Molpro & PyXSCAT Library. https://github.com/AMC-dyn/PyXSCAT"
39
+
22
40
  def __getattr__(name):
23
41
  if name in __all__:
24
42
  # Load from HDF5 only when first accessed
@@ -41,7 +59,7 @@ def _make_default_obj(f):
41
59
  -------
42
60
  obj : SimpleNamespace
43
61
  A SimpleNamespace object with attributes: q, I_q, molecule, method, basis_set, n_electrons,
44
- and optionally I_q_elastic and I_q_inelastic if present in the file.
62
+ and optionally any of the I_q components in _COMPONENTS, notes, source, and metadata if present in the file.
45
63
  """
46
64
 
47
65
  attrs = dict(
@@ -52,22 +70,22 @@ def _make_default_obj(f):
52
70
  basis_set=f.attrs["basis_set"],
53
71
  n_electrons=f.attrs["n_electrons"],
54
72
  )
55
- if "I_q_elastic" in f:
56
- attrs["I_q_elastic"] = f["I_q_elastic"][:]
57
- if "I_q_inelastic" in f:
58
- attrs["I_q_inelastic"] = f["I_q_inelastic"][:]
59
- if "notes" in f.attrs:
60
- attrs["notes"] = f.attrs["notes"]
73
+ for name in _COMPONENTS:
74
+ if name in f:
75
+ attrs[name] = f[name][:]
76
+ for name in ("notes", "source", "metadata"):
77
+ if name in f.attrs:
78
+ attrs[name] = f.attrs[name]
61
79
  return SimpleNamespace(**attrs)
62
80
 
63
81
  def _make_default_docstring(obj):
64
82
  """
65
- Generate a default docstring for a data object if created by tests\\convert_ab_initio_to_h5.py
83
+ Generate a default docstring for a data object if created by data\\patterns\\convert_ab_initio_to_h5.py or data\\patterns\\convert_thistle_to_h5.py
66
84
 
67
85
  Parameters
68
86
  ----------
69
87
  obj : SimpleNamespace
70
- The data object with attributes: q, I_q, I_q_elastic, I_q_inelastic, molecule, method, basis_set, n_electrons.
88
+ The data object with attributes: q, I_q, molecule, method, basis_set, n_electrons, and any optional attributes from _make_default_obj.
71
89
 
72
90
  Returns
73
91
  -------
@@ -75,17 +93,17 @@ def _make_default_docstring(obj):
75
93
  The generated docstring.
76
94
  """
77
95
 
78
- elastic_doc = ""
79
- if hasattr(obj, "I_q_elastic"):
80
- elastic_doc = f""" I_q_elastic : ndarray of shape ({len(obj.I_q_elastic)},)
81
- Elastic scattering intensity values corresponding to `q`.
82
- """
83
- inelastic_doc = ""
84
- if hasattr(obj, "I_q_inelastic"):
85
- inelastic_doc = f""" I_q_inelastic : ndarray of shape ({len(obj.I_q_inelastic)},)
86
- Inelastic scattering intensity values corresponding to `q`.
96
+ components_doc = ""
97
+ for name, description in _COMPONENTS.items():
98
+ if hasattr(obj, name):
99
+ components_doc += f""" {name} : ndarray of shape ({len(getattr(obj, name))},)
100
+ {description}
87
101
  """
88
102
 
103
+ decomposition_doc = ""
104
+ if hasattr(obj, "I_q_hartree"):
105
+ decomposition_doc = "I_q = n_electrons + I_q_hartree + I_q_exchange + I_q_cumulant, and I_q = I_q_elastic + I_q_inelastic."
106
+
89
107
  notes_doc = ""
90
108
  if hasattr(obj, "notes"):
91
109
  notes_doc = obj.notes
@@ -99,8 +117,8 @@ def _make_default_docstring(obj):
99
117
  Momentum transfer values (q) in inverse angstroms. Ranges from {obj.q[0]} to {obj.q[-1]} Å⁻¹. ({invAngstroms2au(obj.q[0])} to {invAngstroms2au(obj.q[-1])} a.u.).
100
118
  I_q : ndarray of shape ({len(obj.I_q)},)
101
119
  Total scattering intensity values corresponding to `q`.
102
- {elastic_doc}{inelastic_doc} molecule : str
103
- The molecular formula of the species.
120
+ {components_doc} molecule : str
121
+ The molecular formula, or an abbreviation for isomers (e.g. NBD, QC).
104
122
  method : str
105
123
  The method used for the calculation.
106
124
  basis_set : str
@@ -110,8 +128,9 @@ def _make_default_docstring(obj):
110
128
 
111
129
  Notes
112
130
  -----
113
- Calculated using Molpro & PyXSCAT Library. https://github.com/AMC-dyn/PyXSCAT
131
+ Calculated using {getattr(obj, "source", _DEFAULT_SOURCE)}
114
132
  Inverse angstroms to atomic units conversion factor used: {au2invAngstroms(1.0)} Å⁻¹/a.u.
133
+ {decomposition_doc}
115
134
  {notes_doc}
116
135
  """
117
136
 
@@ -9,11 +9,16 @@ class _PatternData(SimpleNamespace):
9
9
  I_q: any # ndarray
10
10
  I_q_elastic: Optional[any] # ndarray, may not be present
11
11
  I_q_inelastic: Optional[any] # ndarray, may not be present
12
+ I_q_hartree: Optional[any] # ndarray, may not be present
13
+ I_q_exchange: Optional[any] # ndarray, may not be present
14
+ I_q_cumulant: Optional[any] # ndarray, may not be present
12
15
  molecule: str
13
16
  method: str
14
17
  basis_set: str
15
18
  n_electrons: int
16
19
  notes: Optional[any]
20
+ source: Optional[str]
21
+ metadata: Optional[str] # JSON string
17
22
 
18
23
  SF6__CCSD__aug_cc_pVDZ: _PatternData
19
24
  SF6__MP2__aug_cc_pVDZ: _PatternData
@@ -21,3 +26,10 @@ SF6__HF__aug_cc_pVDZ: _PatternData
21
26
  Ne__CCSD_X2C__aug_cc_pVDZ_DK: _PatternData
22
27
  Ne__CCSD_X2C__aug_cc_pVTZ_DK: _PatternData
23
28
  Ne__CCSD_X2C__aug_cc_pVQZ_DK: _PatternData
29
+ SF6__HF__cc_pVTZ: _PatternData
30
+ SF6__CCSD_FC__aug_cc_pVDZ: _PatternData
31
+ SF6__CCSD_FC__aug_cc_pVTZ: _PatternData
32
+ NBD__HF__cc_pVTZ: _PatternData
33
+ NBD__CCSD_FC__aug_cc_pVDZ: _PatternData
34
+ QC__HF__cc_pVTZ: _PatternData
35
+ QC__CCSD_FC__aug_cc_pVDZ: _PatternData
@@ -83,6 +83,13 @@ class TestDir:
83
83
  assert "SF6__CCSD__aug_cc_pVDZ" in result
84
84
 
85
85
 
86
+ class TestTheoryExports:
87
+ @pytest.mark.parametrize("name", PATTERNS_ALL)
88
+ def test_pattern_exported_from_theory(self, name):
89
+ from xrayscatteringtools import theory
90
+ assert hasattr(theory, name), f"add {name} to theory/__init__.py"
91
+
92
+
86
93
  # ── _make_default_obj ──────────────────────────────────────────────────
87
94
 
88
95
 
@@ -137,6 +144,20 @@ class TestMakeDefaultObj:
137
144
  assert hasattr(obj, "I_q")
138
145
  assert not hasattr(obj, "I_q_elastic")
139
146
  assert not hasattr(obj, "I_q_inelastic")
147
+ assert not hasattr(obj, "I_q_hartree")
148
+ assert not hasattr(obj, "source")
149
+
150
+ def test_loads_decomposition_and_source(self, h5_with_elastic):
151
+ with h5py.File(h5_with_elastic, "a") as f:
152
+ f.create_dataset("I_q_hartree", data=np.ones(50) * 7)
153
+ f.create_dataset("I_q_exchange", data=np.ones(50) * -7)
154
+ f.attrs["source"] = "PySCF RHF via thistle"
155
+ with h5py.File(h5_with_elastic, "r") as f:
156
+ obj = _make_default_obj(f)
157
+ np.testing.assert_allclose(obj.I_q_hartree, 7)
158
+ np.testing.assert_allclose(obj.I_q_exchange, -7)
159
+ assert not hasattr(obj, "I_q_cumulant")
160
+ assert obj.source == "PySCF RHF via thistle"
140
161
 
141
162
 
142
163
  # ── _make_default_docstring ────────────────────────────────────────────
@@ -191,6 +212,26 @@ class TestMakeDefaultDocstring:
191
212
  # Should contain the a.u. conversion factor
192
213
  assert str(au2invAngstroms(1.0)) in doc
193
214
 
215
+ def test_docstring_source(self):
216
+ obj = SimpleNamespace(
217
+ q=np.linspace(0.5, 8.0, 30),
218
+ I_q=np.ones(30),
219
+ molecule="NBD",
220
+ method="HF",
221
+ basis_set="cc_pVTZ",
222
+ n_electrons=50,
223
+ )
224
+ assert "Molpro" in _make_default_docstring(obj)
225
+ obj.source = "PySCF RHF via thistle"
226
+ obj.I_q_hartree = np.ones(30)
227
+ obj.I_q_exchange = np.ones(30)
228
+ doc = _make_default_docstring(obj)
229
+ assert "PySCF RHF via thistle" in doc
230
+ assert "Molpro" not in doc
231
+ assert "I_q_hartree" in doc
232
+ assert "I_q_exchange" in doc
233
+ assert "I_q_cumulant" not in doc.split("Notes")[0]
234
+
194
235
 
195
236
  # ── Consistency between shipped patterns ───────────────────────────────
196
237
 
@@ -209,6 +250,25 @@ class TestShippedPatternConsistency:
209
250
  err_msg=f"elastic + inelastic != total for {name}",
210
251
  )
211
252
 
253
+ @pytest.mark.parametrize("name", PATTERNS_ALL)
254
+ def test_two_electron_decomposition_equals_total(self, name):
255
+ obj = getattr(patterns, name)
256
+ if not hasattr(obj, "I_q_hartree"):
257
+ pytest.skip(f"{name} does not have the hartree/exchange/cumulant decomposition")
258
+ cumulant = getattr(obj, "I_q_cumulant", 0)
259
+ np.testing.assert_allclose(
260
+ obj.I_q, obj.n_electrons + obj.I_q_hartree + obj.I_q_exchange + cumulant,
261
+ rtol=1e-9,
262
+ err_msg=f"n_electrons + hartree + exchange + cumulant != total for {name}",
263
+ )
264
+
265
+ @pytest.mark.parametrize("name", PATTERNS_ALL)
266
+ def test_forward_scattering_is_n_electrons_squared(self, name):
267
+ obj = getattr(patterns, name)
268
+ if obj.q[0] > 1e-6:
269
+ pytest.skip(f"{name} does not start at q = 0")
270
+ np.testing.assert_allclose(obj.I_q[0], obj.n_electrons**2, rtol=1e-6)
271
+
212
272
  @pytest.mark.parametrize("name", PATTERNS_ALL)
213
273
  def test_n_electrons_positive(self, name):
214
274
  obj = getattr(patterns, name)