finalcif 173__tar.gz → 175__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 (199) hide show
  1. {finalcif-173 → finalcif-175}/.gitignore +1 -0
  2. {finalcif-173 → finalcif-175}/.ruff.toml +3 -1
  3. {finalcif-173 → finalcif-175}/.run/compile_ui_files.run.xml +1 -1
  4. {finalcif-173 → finalcif-175}/PKG-INFO +6 -6
  5. finalcif-175/finalcif/__init__.py +3 -0
  6. {finalcif-173 → finalcif-175}/finalcif/appwindow.py +103 -8
  7. {finalcif-173 → finalcif-175}/finalcif/cif/checkcif/checkcif.py +7 -5
  8. {finalcif-173 → finalcif-175}/finalcif/cif/hkl.py +73 -9
  9. {finalcif-173 → finalcif-175}/finalcif/datafiles/bruker_data.py +76 -21
  10. {finalcif-173 → finalcif-175}/finalcif/datafiles/sadabs.py +138 -5
  11. {finalcif-173 → finalcif-175}/finalcif/gui/finalcif_gui_ui.py +63 -54
  12. {finalcif-173 → finalcif-175}/finalcif/gui/finalcif_gui_ui.ui +117 -105
  13. {finalcif-173 → finalcif-175}/finalcif/gui/validators.py +5 -1
  14. {finalcif-173 → finalcif-175}/finalcif/tools/squeeze.py +76 -1
  15. {finalcif-173 → finalcif-175}/finalcif/tools/sumformula.py +27 -3
  16. {finalcif-173 → finalcif-175}/finalcif/tools/z_from_packing.py +701 -281
  17. {finalcif-173 → finalcif-175}/pyproject.toml +4 -4
  18. {finalcif-173 → finalcif-175}/uv.lock +215 -135
  19. finalcif-173/finalcif/__init__.py +0 -3
  20. {finalcif-173 → finalcif-175}/.gitattributes +0 -0
  21. {finalcif-173 → finalcif-175}/.githooks/pre-push +0 -0
  22. {finalcif-173 → finalcif-175}/.python-version +0 -0
  23. {finalcif-173 → finalcif-175}/.run/Make Documentation MAC.run.xml +0 -0
  24. {finalcif-173 → finalcif-175}/.run/make documentation Windows.run.xml +0 -0
  25. {finalcif-173 → finalcif-175}/AGENTS.md +0 -0
  26. {finalcif-173 → finalcif-175}/CODE_OF_CONDUCT.md +0 -0
  27. {finalcif-173 → finalcif-175}/CONTRIBUTING.md +0 -0
  28. {finalcif-173 → finalcif-175}/Finalcif_installer_win.spec +0 -0
  29. {finalcif-173 → finalcif-175}/Finalcif_linux_onefile.spec +0 -0
  30. {finalcif-173 → finalcif-175}/Finalcif_mac.spec +0 -0
  31. {finalcif-173 → finalcif-175}/LICENSE +0 -0
  32. {finalcif-173 → finalcif-175}/README.md +0 -0
  33. {finalcif-173 → finalcif-175}/finalcif/app_path.py +0 -0
  34. {finalcif-173 → finalcif-175}/finalcif/cif/__init__.py +0 -0
  35. {finalcif-173 → finalcif-175}/finalcif/cif/all_cif_dicts.py +0 -0
  36. {finalcif-173 → finalcif-175}/finalcif/cif/atoms.py +0 -0
  37. {finalcif-173 → finalcif-175}/finalcif/cif/checkcif/__init__.py +0 -0
  38. {finalcif-173 → finalcif-175}/finalcif/cif/cif_dict_foo.py +0 -0
  39. {finalcif-173 → finalcif-175}/finalcif/cif/cif_file_io.py +0 -0
  40. {finalcif-173 → finalcif-175}/finalcif/cif/cif_keyword_mapping.py +0 -0
  41. {finalcif-173 → finalcif-175}/finalcif/cif/cif_order.py +0 -0
  42. {finalcif-173 → finalcif-175}/finalcif/cif/cod/__init__.py +0 -0
  43. {finalcif-173 → finalcif-175}/finalcif/cif/cod/deposit.py +0 -0
  44. {finalcif-173 → finalcif-175}/finalcif/cif/cod/deposit_check.py +0 -0
  45. {finalcif-173 → finalcif-175}/finalcif/cif/cod/deposition_list.py +0 -0
  46. {finalcif-173 → finalcif-175}/finalcif/cif/cod/doi.py +0 -0
  47. {finalcif-173 → finalcif-175}/finalcif/cif/cod/upload.py +0 -0
  48. {finalcif-173 → finalcif-175}/finalcif/cif/cod/website_parser.py +0 -0
  49. {finalcif-173 → finalcif-175}/finalcif/cif/core_dict.py +0 -0
  50. {finalcif-173 → finalcif-175}/finalcif/cif/modulation_dict.py +0 -0
  51. {finalcif-173 → finalcif-175}/finalcif/cif/powder_dict.py +0 -0
  52. {finalcif-173 → finalcif-175}/finalcif/cif/reference.py +0 -0
  53. {finalcif-173 → finalcif-175}/finalcif/cif/restraints_dict.py +0 -0
  54. {finalcif-173 → finalcif-175}/finalcif/cif/text.py +0 -0
  55. {finalcif-173 → finalcif-175}/finalcif/cif/twin_dict.py +0 -0
  56. {finalcif-173 → finalcif-175}/finalcif/cif/vrf_entry.py +0 -0
  57. {finalcif-173 → finalcif-175}/finalcif/ciforder/__init__.py +0 -0
  58. {finalcif-173 → finalcif-175}/finalcif/ciforder/order.py +0 -0
  59. {finalcif-173 → finalcif-175}/finalcif/ciforder/order_ui.py +0 -0
  60. {finalcif-173 → finalcif-175}/finalcif/ciforder/order_ui.ui +0 -0
  61. {finalcif-173 → finalcif-175}/finalcif/datafiles/__init__.py +0 -0
  62. {finalcif-173 → finalcif-175}/finalcif/datafiles/bruker_frame.py +0 -0
  63. {finalcif-173 → finalcif-175}/finalcif/datafiles/ccdc_mail.py +0 -0
  64. {finalcif-173 → finalcif-175}/finalcif/datafiles/data.py +0 -0
  65. {finalcif-173 → finalcif-175}/finalcif/datafiles/p4p_reader.py +0 -0
  66. {finalcif-173 → finalcif-175}/finalcif/datafiles/saint.py +0 -0
  67. {finalcif-173 → finalcif-175}/finalcif/datafiles/shelx_lst.py +0 -0
  68. {finalcif-173 → finalcif-175}/finalcif/datafiles/utils.py +0 -0
  69. {finalcif-173 → finalcif-175}/finalcif/equip_property/__init__.py +0 -0
  70. {finalcif-173 → finalcif-175}/finalcif/equip_property/author_loop_templates.py +0 -0
  71. {finalcif-173 → finalcif-175}/finalcif/equip_property/equipment.py +0 -0
  72. {finalcif-173 → finalcif-175}/finalcif/equip_property/properties.py +0 -0
  73. {finalcif-173 → finalcif-175}/finalcif/equip_property/tools.py +0 -0
  74. {finalcif-173 → finalcif-175}/finalcif/finalcif_start.py +0 -0
  75. {finalcif-173 → finalcif-175}/finalcif/gui/__init__.py +0 -0
  76. {finalcif-173 → finalcif-175}/finalcif/gui/block_combobox.py +0 -0
  77. {finalcif-173 → finalcif-175}/finalcif/gui/checkcif_browser.py +0 -0
  78. {finalcif-173 → finalcif-175}/finalcif/gui/cif_table_model.py +0 -0
  79. {finalcif-173 → finalcif-175}/finalcif/gui/cif_table_view.py +0 -0
  80. {finalcif-173 → finalcif-175}/finalcif/gui/combobox.py +0 -0
  81. {finalcif-173 → finalcif-175}/finalcif/gui/custom_classes.py +0 -0
  82. {finalcif-173 → finalcif-175}/finalcif/gui/dialogs.py +0 -0
  83. {finalcif-173 → finalcif-175}/finalcif/gui/edit_button.py +0 -0
  84. {finalcif-173 → finalcif-175}/finalcif/gui/equipmenttable.py +0 -0
  85. {finalcif-173 → finalcif-175}/finalcif/gui/file_editor.py +0 -0
  86. {finalcif-173 → finalcif-175}/finalcif/gui/import_selector.py +0 -0
  87. {finalcif-173 → finalcif-175}/finalcif/gui/import_selector_ui.py +0 -0
  88. {finalcif-173 → finalcif-175}/finalcif/gui/import_selector_ui.ui +0 -0
  89. {finalcif-173 → finalcif-175}/finalcif/gui/loop_creator.py +0 -0
  90. {finalcif-173 → finalcif-175}/finalcif/gui/loop_creator_ui.py +0 -0
  91. {finalcif-173 → finalcif-175}/finalcif/gui/loop_creator_ui.ui +0 -0
  92. {finalcif-173 → finalcif-175}/finalcif/gui/loops.py +0 -0
  93. {finalcif-173 → finalcif-175}/finalcif/gui/mainstackwidget.py +0 -0
  94. {finalcif-173 → finalcif-175}/finalcif/gui/mixins.py +0 -0
  95. {finalcif-173 → finalcif-175}/finalcif/gui/new_key_dialog.py +0 -0
  96. {finalcif-173 → finalcif-175}/finalcif/gui/new_key_dialog_ui.py +0 -0
  97. {finalcif-173 → finalcif-175}/finalcif/gui/new_key_dialog_ui.ui +0 -0
  98. {finalcif-173 → finalcif-175}/finalcif/gui/plaintextedit.py +0 -0
  99. {finalcif-173 → finalcif-175}/finalcif/gui/playground.py +0 -0
  100. {finalcif-173 → finalcif-175}/finalcif/gui/propertytable.py +0 -0
  101. {finalcif-173 → finalcif-175}/finalcif/gui/shelx_navigation.py +0 -0
  102. {finalcif-173 → finalcif-175}/finalcif/gui/single_line_textedit.py +0 -0
  103. {finalcif-173 → finalcif-175}/finalcif/gui/spell_check_edit.py +0 -0
  104. {finalcif-173 → finalcif-175}/finalcif/gui/squeeze_dialog.py +0 -0
  105. {finalcif-173 → finalcif-175}/finalcif/gui/syntax_highlighter.py +0 -0
  106. {finalcif-173 → finalcif-175}/finalcif/gui/text_templates_ui.py +0 -0
  107. {finalcif-173 → finalcif-175}/finalcif/gui/text_templates_ui.ui +0 -0
  108. {finalcif-173 → finalcif-175}/finalcif/gui/text_value_editor.py +0 -0
  109. {finalcif-173 → finalcif-175}/finalcif/gui/timers.py +0 -0
  110. {finalcif-173 → finalcif-175}/finalcif/gui/vrf_classes.py +0 -0
  111. {finalcif-173 → finalcif-175}/finalcif/gui/vzs_viewer.py +0 -0
  112. {finalcif-173 → finalcif-175}/finalcif/icon/__init__.py +0 -0
  113. {finalcif-173 → finalcif-175}/finalcif/icon/finalcif.png +0 -0
  114. {finalcif-173 → finalcif-175}/finalcif/icon/finalcif2.ico +0 -0
  115. {finalcif-173 → finalcif-175}/finalcif/icon/finalcif2.png +0 -0
  116. {finalcif-173 → finalcif-175}/finalcif/icon/multitable.ico +0 -0
  117. {finalcif-173 → finalcif-175}/finalcif/icon/multitable.png +0 -0
  118. {finalcif-173 → finalcif-175}/finalcif/icon/multitable.xcf +0 -0
  119. {finalcif-173 → finalcif-175}/finalcif/report/__init__.py +0 -0
  120. {finalcif-173 → finalcif-175}/finalcif/report/archive_report.py +0 -0
  121. {finalcif-173 → finalcif-175}/finalcif/report/gui/__init__.py +0 -0
  122. {finalcif-173 → finalcif-175}/finalcif/report/gui/mainwindow.py +0 -0
  123. {finalcif-173 → finalcif-175}/finalcif/report/gui/mainwindow.ui +0 -0
  124. {finalcif-173 → finalcif-175}/finalcif/report/mtools.py +0 -0
  125. {finalcif-173 → finalcif-175}/finalcif/report/references.py +0 -0
  126. {finalcif-173 → finalcif-175}/finalcif/report/report_text.py +0 -0
  127. {finalcif-173 → finalcif-175}/finalcif/report/symm.py +0 -0
  128. {finalcif-173 → finalcif-175}/finalcif/report/tables.py +0 -0
  129. {finalcif-173 → finalcif-175}/finalcif/report/templated_report.py +0 -0
  130. {finalcif-173 → finalcif-175}/finalcif/template/__init__.py +0 -0
  131. {finalcif-173 → finalcif-175}/finalcif/template/bootstrap/bootstrap.min.css +0 -0
  132. {finalcif-173 → finalcif-175}/finalcif/template/machines/D8_VENTURE.cif +0 -0
  133. {finalcif-173 → finalcif-175}/finalcif/template/mathml2omml.xsl +0 -0
  134. {finalcif-173 → finalcif-175}/finalcif/template/miew/LICENSES +0 -0
  135. {finalcif-173 → finalcif-175}/finalcif/template/miew/Miew.css +0 -0
  136. {finalcif-173 → finalcif-175}/finalcif/template/miew/Miew.min.js +0 -0
  137. {finalcif-173 → finalcif-175}/finalcif/template/miew/lodash.min.js +0 -0
  138. {finalcif-173 → finalcif-175}/finalcif/template/miew/three.js +0 -0
  139. {finalcif-173 → finalcif-175}/finalcif/template/report.tex +0 -0
  140. {finalcif-173 → finalcif-175}/finalcif/template/report.tmpl +0 -0
  141. {finalcif-173 → finalcif-175}/finalcif/template/report2.tex +0 -0
  142. {finalcif-173 → finalcif-175}/finalcif/template/report_default.docx +0 -0
  143. {finalcif-173 → finalcif-175}/finalcif/template/report_fastmolwidget.tmpl +0 -0
  144. {finalcif-173 → finalcif-175}/finalcif/template/template1.docx +0 -0
  145. {finalcif-173 → finalcif-175}/finalcif/template/template_for_multitable.docx +0 -0
  146. {finalcif-173 → finalcif-175}/finalcif/template/template_text.docx +0 -0
  147. {finalcif-173 → finalcif-175}/finalcif/template/template_without_text.docx +0 -0
  148. {finalcif-173 → finalcif-175}/finalcif/template/templates.py +0 -0
  149. {finalcif-173 → finalcif-175}/finalcif/template/unicode2latex/FakePlasTeX/FakeContext.py +0 -0
  150. {finalcif-173 → finalcif-175}/finalcif/template/unicode2latex/FakePlasTeX/FakeTokenizer.py +0 -0
  151. {finalcif-173 → finalcif-175}/finalcif/template/unicode2latex/FakePlasTeX/README.md +0 -0
  152. {finalcif-173 → finalcif-175}/finalcif/template/unicode2latex/FakePlasTeX/TokenizerPassThru.py +0 -0
  153. {finalcif-173 → finalcif-175}/finalcif/template/unicode2latex/LICENSE +0 -0
  154. {finalcif-173 → finalcif-175}/finalcif/template/unicode2latex/__init__.py +0 -0
  155. {finalcif-173 → finalcif-175}/finalcif/template/unicode2latex/u2l.py +0 -0
  156. {finalcif-173 → finalcif-175}/finalcif/template/unicode2latex/unicode-math-table.tex +0 -0
  157. {finalcif-173 → finalcif-175}/finalcif/template/unicode2latex/unicode-math-xetex.sty +0 -0
  158. {finalcif-173 → finalcif-175}/finalcif/template/xsl/convert.py +0 -0
  159. {finalcif-173 → finalcif-175}/finalcif/template/xsl/xmltohtml.xsl +0 -0
  160. {finalcif-173 → finalcif-175}/finalcif/template/xsl/xmltolatex.xsl +0 -0
  161. {finalcif-173 → finalcif-175}/finalcif/tools/__init__.py +0 -0
  162. {finalcif-173 → finalcif-175}/finalcif/tools/chemparse.py +0 -0
  163. {finalcif-173 → finalcif-175}/finalcif/tools/download.py +0 -0
  164. {finalcif-173 → finalcif-175}/finalcif/tools/dsrmath.py +0 -0
  165. {finalcif-173 → finalcif-175}/finalcif/tools/formal_charge.py +0 -0
  166. {finalcif-173 → finalcif-175}/finalcif/tools/misc.py +0 -0
  167. {finalcif-173 → finalcif-175}/finalcif/tools/mol_file_writer.py +0 -0
  168. {finalcif-173 → finalcif-175}/finalcif/tools/options.py +0 -0
  169. {finalcif-173 → finalcif-175}/finalcif/tools/platon.py +0 -0
  170. {finalcif-173 → finalcif-175}/finalcif/tools/pupdate.py +0 -0
  171. {finalcif-173 → finalcif-175}/finalcif/tools/selfupdate.py +0 -0
  172. {finalcif-173 → finalcif-175}/finalcif/tools/settings.py +0 -0
  173. {finalcif-173 → finalcif-175}/finalcif/tools/shred.py +0 -0
  174. {finalcif-173 → finalcif-175}/finalcif/tools/space_groups.py +0 -0
  175. {finalcif-173 → finalcif-175}/finalcif/tools/spgr_format.py +0 -0
  176. {finalcif-173 → finalcif-175}/finalcif/tools/statusbar.py +0 -0
  177. {finalcif-173 → finalcif-175}/finalcif.exe +0 -0
  178. {finalcif-173 → finalcif-175}/platon/README.txt +0 -0
  179. {finalcif-173 → finalcif-175}/platon/compile_platon.bat +0 -0
  180. {finalcif-173 → finalcif-175}/platon/platon_special.exe +0 -0
  181. {finalcif-173 → finalcif-175}/platon/platon_special.f +0 -0
  182. {finalcif-173 → finalcif-175}/pylock.toml +0 -0
  183. {finalcif-173 → finalcif-175}/run_and_capture.py +0 -0
  184. {finalcif-173 → finalcif-175}/run_finalcif +0 -0
  185. {finalcif-173 → finalcif-175}/run_finalcif.bat +0 -0
  186. {finalcif-173 → finalcif-175}/screenshots/finalcif_checkcif.png +0 -0
  187. {finalcif-173 → finalcif-175}/screenshots/finalcif_details.png +0 -0
  188. {finalcif-173 → finalcif-175}/screenshots/finalcif_loops.png +0 -0
  189. {finalcif-173 → finalcif-175}/screenshots/finalcif_loops2.png +0 -0
  190. {finalcif-173 → finalcif-175}/screenshots/finalcif_main.png +0 -0
  191. {finalcif-173 → finalcif-175}/screenshots/finalcif_report.png +0 -0
  192. {finalcif-173 → finalcif-175}/screenshots/finalcif_responses.png +0 -0
  193. {finalcif-173 → finalcif-175}/screenshots/loops_column_buttons.png +0 -0
  194. {finalcif-173 → finalcif-175}/work/compare_z_platon.py +0 -0
  195. {finalcif-173 → finalcif-175}/work/diagnose_1519506.py +0 -0
  196. {finalcif-173 → finalcif-175}/work/diagnose_z.py +0 -0
  197. {finalcif-173 → finalcif-175}/work/investigate_failures.py +0 -0
  198. {finalcif-173 → finalcif-175}/work/validate_cod.py +0 -0
  199. {finalcif-173 → finalcif-175}/work/validate_zprime.py +0 -0
@@ -243,3 +243,4 @@ finalcif_keys_order.cif
243
243
  /work/dicts/ddl.dic
244
244
  /test-data/4060314-finalcif.zip
245
245
  /test-data/report_4060314-finalcif.html
246
+ Z-value_examples
@@ -51,7 +51,9 @@ select = ["E", "F", "B", "UP", "ANN", "A", "C4", "DTZ", "NPY", "PL", "FURB", "RU
51
51
  ignore = ["E501", "F401", "E401", "B904", "E402", "C901",
52
52
  "PLR0912", "PLR0915", "PLR0913", "ANN201", "ANN201", "ANN204", "ANN001", "ANN002",
53
53
  "D102", "D103", "D205", "D212", "PLR2004", "PLW2901", "DTZ005", "DTZ006", "UP015",
54
- "RUF012", "DTZ001",
54
+ "RUF012",
55
+ "DTZ001",
56
+ "PLR0911",
55
57
  ]
56
58
 
57
59
  # Avoid trying to fix flake8-bugbear (`B`) violations.
@@ -14,7 +14,7 @@
14
14
  <option name="ADD_SOURCE_ROOTS" value="true" />
15
15
  <option name="DEBUG_JUST_MY_CODE" value="true" />
16
16
  <EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
17
- <option name="RUN_TOOL" value="true" />
17
+ <option name="RUN_TOOL" value="false" />
18
18
  <option name="SCRIPT_NAME" value="scripts/compile_ui_files.py" />
19
19
  <option name="PARAMETERS" value="" />
20
20
  <option name="SHOW_COMMAND_LINE" value="false" />
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: finalcif
3
- Version: 173
3
+ Version: 175
4
4
  Summary: CIF file editor
5
5
  Project-URL: Homepage, https://dkratzert.de/finalcif.html
6
6
  Project-URL: Bug Tracker, https://github.com/dkratzert/FinalCif/issues
@@ -15,14 +15,14 @@ Requires-Python: >=3.12
15
15
  Requires-Dist: certifi==2026.6.17
16
16
  Requires-Dist: chardet
17
17
  Requires-Dist: charset-normalizer
18
- Requires-Dist: docxcompose==2.2.0
18
+ Requires-Dist: docxcompose>=2.2.0
19
19
  Requires-Dist: docxtpl[subdoc]>=0.20.2
20
- Requires-Dist: fastmolwidget>=1.0.0
20
+ Requires-Dist: fastmolwidget>=1.2.0
21
21
  Requires-Dist: gemmi>=0.7.5
22
22
  Requires-Dist: html2text
23
23
  Requires-Dist: lxml>=6.1.1
24
24
  Requires-Dist: numpy==2.4.6
25
- Requires-Dist: packaging==26.2
25
+ Requires-Dist: packaging==26.3
26
26
  Requires-Dist: pefile
27
27
  Requires-Dist: pybind11>=3
28
28
  Requires-Dist: pyenchant
@@ -31,7 +31,7 @@ Requires-Dist: python-docx
31
31
  Requires-Dist: qtawesome
32
32
  Requires-Dist: qtpy
33
33
  Requires-Dist: requests
34
- Requires-Dist: shelxfile==24
34
+ Requires-Dist: shelxfile>=24
35
35
  Requires-Dist: urllib3==2.7.0
36
36
  Requires-Dist: wheel==0.47.0
37
37
  Description-Content-Type: text/markdown
@@ -0,0 +1,3 @@
1
+
2
+ VERSION = 175
3
+ __version__ = VERSION
@@ -26,6 +26,8 @@ if TYPE_CHECKING:
26
26
  from finalcif.cif.checkcif.checkcif import MyHTMLParser
27
27
 
28
28
  from fastmolwidget import Atomtuple
29
+ from fastmolwidget.density_controls import ResidualDensityControls
30
+ from fastmolwidget.hkl_io import find_reflection_file, has_reflections
29
31
  from fastmolwidget.part_combo import PartFilterWidget
30
32
 
31
33
  from finalcif.gui.shelx_navigation import find_shelx_line_for_atom, scroll_and_highlight_shelx_atom
@@ -86,10 +88,12 @@ from finalcif.tools.platon import PlatonRunner
86
88
  from finalcif.tools.settings import FinalCifSettings
87
89
  from finalcif.tools.shred import ShredCIF
88
90
  from finalcif.tools.space_groups import SpaceGroups
91
+ from finalcif.tools.squeeze import append_unknown_solvent, has_unassigned_solvent
89
92
  from finalcif.tools.z_from_packing import count_z_and_zprime, ZResult
90
93
  from finalcif.tools.spgr_format import spgrps
91
94
  from finalcif.tools.statusbar import StatusBar
92
- from finalcif.tools.sumformula import formula_str_to_dict, formula_to_html, sum_formula_to_html
95
+ from finalcif.tools.sumformula import (formula_str_to_dict, formula_to_html, sum_formula_to_html,
96
+ with_solvent_marker)
93
97
 
94
98
  DEBUG = False
95
99
  app = QApplication.instance()
@@ -194,6 +198,14 @@ class AppWindow(QMainWindow):
194
198
  self.parts_combo = PartFilterWidget(parent=self.ui.molGroupBox, label=' ', min_combo_width=90)
195
199
  self.parts_combo.setObjectName('partsFilterWidget')
196
200
  self.ui.horizontalLayout_7.insertWidget(3, self.parts_combo)
201
+ # Residual density button and level spin box, next to the parts selector.
202
+ # The file dialog is switched off: without reflection data the structure
203
+ # is simply shown without density.
204
+ self.density_controls = ResidualDensityControls(render_widget=self.ui.render_widget,
205
+ parent=self.ui.molGroupBox,
206
+ allow_reflection_dialog=False)
207
+ self.density_controls.setObjectName('residualDensityControls')
208
+ self.ui.horizontalLayout_7.insertWidget(4, self.density_controls)
197
209
  # Inject the Author Editor tab (defined in the .ui file) into LoopsPage
198
210
  self.ui.loops_page.set_author_editor_tab(self.ui.author_editor_widget)
199
211
  self.fixfont = QtGui.QFontDatabase.systemFont(QtGui.QFontDatabase.SystemFont.FixedFont)
@@ -1072,7 +1084,6 @@ class AppWindow(QMainWindow):
1072
1084
  source_item = MyTableWidgetItem(s)
1073
1085
  source_item.setUneditable()
1074
1086
  data_item = MyTableWidgetItem(self.sources[s][1])
1075
- data_item.setToolTip(str(self.sources[s][1]))
1076
1087
  data_item.setUneditable()
1077
1088
  table.setItem(rownum, COL_key, source_item)
1078
1089
  table.setItem(rownum, COL_source_data, data_item)
@@ -1306,8 +1317,9 @@ class AppWindow(QMainWindow):
1306
1317
  self.ui.CheckCifLogPlainTextEdit.appendHtml(
1307
1318
  f'<br><b>{n} validation response forms {form_noun} added to the main table.</b>')
1308
1319
  else:
1320
+ levels = 'A, B or C' if self.ui.explainCandGAlertsCheckBox.isChecked() else 'A or B'
1309
1321
  self.ui.CheckCifLogPlainTextEdit.appendHtml(
1310
- '<br><b>No level A, B or C alerts require a response.</b>')
1322
+ f'<br><b>No level {levels} alerts require a response.</b>')
1311
1323
 
1312
1324
  def _vrf_insert_position(self) -> int:
1313
1325
  """Return the row index just after the last existing ``_vrf_*`` row in the main table.
@@ -1359,7 +1371,8 @@ class AppWindow(QMainWindow):
1359
1371
  hkl_upload=(not self.ui.structfactCheckBox.isChecked()), pdf=False,
1360
1372
  url=self.options.checkcif_url,
1361
1373
  full_iucr=self.ui.fullIucrCheckBox.isChecked(),
1362
- check_duplicates=self.ui.checkDuplicatesCheckBox.isChecked())
1374
+ check_duplicates=self.ui.checkDuplicatesCheckBox.isChecked(),
1375
+ explain_c=self.ui.explainCAlertsCheckBox.isChecked())
1363
1376
  self.ckf.progress.connect(self._ckf_progress)
1364
1377
  self.ckf.failed.connect(self._checkcif_failed)
1365
1378
  self.ckf.finished.connect(self._checkcif_finished)
@@ -1507,6 +1520,50 @@ class AppWindow(QMainWindow):
1507
1520
  self.ui.cif_main_table.setText(key='_chemical_formula_moiety', txt=formula_moiety, column=Column.EDIT)
1508
1521
  self.show_moiety_formula_tooltip(moiety if moiety not in ['', '?'] else formula_moiety)
1509
1522
 
1523
+ def _with_squeezed_solvent(self, formula_moiety: str) -> str:
1524
+ """Mark a generated moiety formula with ``[+ solvent]`` for squeezed structures.
1525
+
1526
+ The unmodelled solvent of a PLATON SQUEEZE / Olex2-SMTBX structure is
1527
+ missing from the atom model, so the moiety derived from the bond graph
1528
+ is incomplete unless the void content was assigned a formula.
1529
+ """
1530
+ try:
1531
+ if has_unassigned_solvent(self.cif):
1532
+ return append_unknown_solvent(formula_moiety)
1533
+ except Exception:
1534
+ pass
1535
+ return formula_moiety
1536
+
1537
+ def mark_squeezed_solvent_in_formulas(self) -> None:
1538
+ """Append ``[+ solvent]`` to the sum and moiety formula of a squeezed structure.
1539
+
1540
+ PLATON marks both ``_chemical_formula_sum`` and
1541
+ ``_chemical_formula_moiety`` this way whenever solvent was removed by
1542
+ SQUEEZE or an Olex2/SMTBX mask, because the formulas then describe less
1543
+ than the real content of the unit cell. Nothing is marked when the void
1544
+ content was assigned a formula, or for multi-CIF files.
1545
+ """
1546
+ try:
1547
+ if self.cif.is_multi_cif or not has_unassigned_solvent(self.cif):
1548
+ return
1549
+ except Exception:
1550
+ return
1551
+ for key in ('_chemical_formula_sum', '_chemical_formula_moiety'):
1552
+ self._append_solvent_marker_to(key)
1553
+
1554
+ def _append_solvent_marker_to(self, key: str) -> None:
1555
+ """Write *key* back with the unknown-solvent marker unless it is empty.
1556
+
1557
+ ``append_unknown_solvent`` normalises a marker that a previous save
1558
+ wrapped across two CIF lines, so the comparison has to be made against
1559
+ the normalised value to avoid writing the row over and over.
1560
+ """
1561
+ current = (self.ui.cif_main_table.getTextFromKey(key=key, col=Column.EDIT)
1562
+ or self.ui.cif_main_table.getTextFromKey(key=key, col=Column.CIF))
1563
+ marked = append_unknown_solvent(current)
1564
+ if marked and marked != current.strip():
1565
+ self.ui.cif_main_table.setText(key=key, txt=marked, column=Column.EDIT)
1566
+
1510
1567
  def append_to_ciflog_without_newline(self, text: str = '') -> None:
1511
1568
  self.ui.CheckCifLogPlainTextEdit.moveCursor(QtGui.QTextCursor.MoveOperation.End)
1512
1569
  self.ui.CheckCifLogPlainTextEdit.insertPlainText(text)
@@ -2047,6 +2104,7 @@ class AppWindow(QMainWindow):
2047
2104
  self.enable_buttons()
2048
2105
  self.fill_space_group_lineedit()
2049
2106
  self.fill_sum_formula_lineedit()
2107
+ self.mark_squeezed_solvent_in_formulas()
2050
2108
  self.ui.CCDCNumLineEdit.setText(self.cif['_database_code_depnum_ccdc_archive'])
2051
2109
  self.ui.CheckcifPlaintextEdit.clear()
2052
2110
  self.ui.TemplatesStackedWidget.setCurrentIndex(0)
@@ -2085,11 +2143,12 @@ class AppWindow(QMainWindow):
2085
2143
  return False
2086
2144
 
2087
2145
  def fill_sum_formula_lineedit(self) -> None:
2146
+ formula = self.cif['_chemical_formula_sum'].strip(" '")
2088
2147
  try:
2089
- self.ui.SumFormMainLineEdit.setText(sum_formula_to_html(formula_str_to_dict(
2090
- self.cif['_chemical_formula_sum'].strip(" '"))))
2148
+ html = sum_formula_to_html(formula_str_to_dict(formula))
2149
+ self.ui.SumFormMainLineEdit.setText(with_solvent_marker(html, formula))
2091
2150
  except Exception:
2092
- self.ui.SumFormMainLineEdit.setText(self.cif['_chemical_formula_sum'].strip(" '"))
2151
+ self.ui.SumFormMainLineEdit.setText(formula)
2093
2152
  self.show_moiety_formula_tooltip()
2094
2153
 
2095
2154
  def show_moiety_formula_tooltip(self, formula_moiety: str = '') -> None:
@@ -2256,6 +2315,7 @@ class AppWindow(QMainWindow):
2256
2315
  if peak:
2257
2316
  self.ui.peakLineEdit.setText("{} / {}".format(peak, self.cif['_refine_diff_density_min']))
2258
2317
  self._show_shelx_file()
2318
+ self._update_residual_density_source()
2259
2319
  try:
2260
2320
  single_shot(self, 0, lambda: self.view_molecule(reset_view=True))
2261
2321
  except Exception:
@@ -2317,6 +2377,29 @@ class AppWindow(QMainWindow):
2317
2377
  self.ui.render_widget.grow_molecule(atom_tuples,
2318
2378
  cell=self.cif.cell[:6])
2319
2379
 
2380
+ def _update_residual_density_source(self) -> None:
2381
+ """Tell the viewer which model and reflections the shown atoms come from.
2382
+
2383
+ The block in memory is used, not the file on disk, so that the density
2384
+ follows unsaved edits and the block the user selected in a multi-block
2385
+ CIF. Without any reflection data the density button stays disabled and
2386
+ the structure is simply displayed without density.
2387
+ """
2388
+ with suppress(Exception):
2389
+ self.ui.render_widget.set_model_source(self.cif.block,
2390
+ reflections=self._reflection_source())
2391
+ self.density_controls.update_density_availability()
2392
+
2393
+ def _reflection_source(self) -> gemmi.cif.Block | Path | None:
2394
+ """Where the reflections of the current structure are, if anywhere.
2395
+
2396
+ Self-contained SHELXL CIFs carry the whole ``.hkl`` in the block itself;
2397
+ otherwise a sibling ``.hkl``/``.fcf`` of the CIF on disk is used.
2398
+ """
2399
+ if has_reflections(self.cif.block):
2400
+ return self.cif.block
2401
+ return find_reflection_file(self.cif.fileobj)
2402
+
2320
2403
  def _update_z_label(self) -> None:
2321
2404
  """Start a background computation of Z and Z′ and show a placeholder.
2322
2405
 
@@ -2372,7 +2455,7 @@ class AppWindow(QMainWindow):
2372
2455
  return
2373
2456
  # Propagate moiety formula to the CIF table if not already filled.
2374
2457
  if result.moiety_formula:
2375
- self.add_moiety_furmula(result.moiety_formula)
2458
+ self.add_moiety_furmula(self._with_squeezed_solvent(result.moiety_formula))
2376
2459
  confidence_icon = {'high': '✓', 'medium': '~', 'formula': '≡', 'low': '?'}[result.confidence]
2377
2460
  self.ui.zEstimateLabel.setText(f"Z = {result.z} Z′ = {result.z_prime:.3g} {confidence_icon}")
2378
2461
  self.ui.zEstimateLabel2.setText(f"Z = {result.z} Z′ = {result.z_prime:.3g} {confidence_icon}")
@@ -2543,6 +2626,18 @@ class AppWindow(QMainWindow):
2543
2626
  # print(e, '##', miss_key)
2544
2627
  pass
2545
2628
  self._apply_source_overrides(bruker_data.overrides)
2629
+ self._add_source_tooltips()
2630
+
2631
+ def _add_source_tooltips(self) -> None:
2632
+ """
2633
+ Shows the file a value of the data source column came from as a tooltip.
2634
+ """
2635
+ for key, source in self.sources.items():
2636
+ if not source or not source[1] or not self.ui.cif_main_table.has_key(key):
2637
+ continue
2638
+ widget = self.ui.cif_main_table.widget_from_key(key, Column.DATA)
2639
+ if widget:
2640
+ widget.setToolTip(f'Source: {source[1]}')
2546
2641
 
2547
2642
  def _apply_source_overrides(self, overrides: set[str]) -> None:
2548
2643
  """
@@ -215,7 +215,8 @@ class CheckCif(QThread):
215
215
  failed = Signal(str)
216
216
 
217
217
  def __init__(self, parent, cif: CifContainer, outfile: Path, hkl_upload: bool = True,
218
- pdf: bool = False, url: str = '', full_iucr: bool = False, check_duplicates: bool = True):
218
+ pdf: bool = False, url: str = '', full_iucr: bool = False, check_duplicates: bool = True,
219
+ explain_c: bool = False):
219
220
  # hkl == False means no hkl upload
220
221
  super().__init__(parent=parent)
221
222
  self.hkl_upload = hkl_upload
@@ -225,6 +226,7 @@ class CheckCif(QThread):
225
226
  self.checkcif_url = url
226
227
  self.full_iucr = full_iucr
227
228
  self.check_duplicates = check_duplicates
229
+ self.explain_c = explain_c
228
230
 
229
231
  def _html_check(self) -> None:
230
232
  if self.hkl_upload:
@@ -232,12 +234,12 @@ class CheckCif(QThread):
232
234
  else:
233
235
  self.progress.emit('Running Checkcif with no hkl data')
234
236
 
235
- def get_vrf(self):
237
+ def get_vrf(self) -> str:
238
+ """The 'valout' request value defining which alert levels get a response form."""
236
239
  if self.pdf:
237
240
  return 'vrfno'
238
- else:
239
- # Currently, the vrfabc option misses some validation response forms. Only vrfab gives correct results.
240
- return 'vrfabc'
241
+ # The IUCr server offers no response forms for level G alerts.
242
+ return 'vrfabc' if self.explain_c else 'vrfab'
241
243
 
242
244
  def run(self) -> None:
243
245
  """
@@ -90,28 +90,42 @@ class HKL:
90
90
  return Limit(h_max=h_max, h_min=h_min, k_max=k_max, k_min=k_min, l_max=l_max, l_min=l_min)
91
91
 
92
92
 
93
- def calculate_rint(hkl_file: str, space_group: str, cell: Sequence[float] | None = None,
94
- resolution: tuple[float, float] | None = None) -> float | None:
93
+ def calculate_rint(hkl_file: str, space_group: str, *, cell: Sequence[float] | None = None,
94
+ resolution: tuple[float, float] | None = None, hklf: int = 4,
95
+ twst: int | None = None) -> float | None:
95
96
  """
96
- R(int) of an unmerged SHELX HKLF 4 file, calculated the way SHELXL does it:
97
+ R(int) of an unmerged SHELX hkl file, calculated the way SHELXL and Olex2 do it:
97
98
 
98
99
  R(int) = sum|Fo^2 - Fo^2(mean)| / sum[Fo^2]
99
100
 
100
101
  Systematically absent reflections are rejected and Friedel opposites are only merged in
101
102
  centrosymmetric space groups. Fo^2(mean) is the mean weighted with 1/sigma^2.
102
103
 
104
+ For HKLF 5 data only the singly indexed reflections of the reference domain are used,
105
+ because composite (overlapped) reflections have no symmetry equivalents to compare with.
106
+
103
107
  SADABS writes no overall R(int) into its listing file, only the wR2(int) of the parameter
104
- refinement and the R(int) values of the individual runs.
108
+ refinement and the R(int) values of the individual runs. The R(int) of a TWINABS listing
109
+ file describes a data treatment that a HKLF 5 refinement does not use.
105
110
 
106
111
  Args:
107
- hkl_file: Content of a SHELX HKLF 4 file.
112
+ hkl_file: Content of a SHELX hkl file.
108
113
  space_group: Space group name, e.g. 'P n a 21'.
109
114
  cell: Unit cell parameters, needed to apply a resolution limit.
110
115
  resolution: Lowest and highest resolution in Angstrom (a SHELX SHEL instruction).
116
+ hklf: The SHELX HKLF file type. Data with negative batch numbers are always treated as
117
+ HKLF 5 data.
118
+ twst: The domain of a SHELX TWST instruction, only used for HKLF 5 data.
111
119
  """
112
- miller, intensities, sigmas = _reflections_from_shelx_hkl(hkl_file)
120
+ miller, intensities, sigmas, batches = _reflections_from_shelx_hkl(hkl_file)
113
121
  if not len(miller) or not space_group:
114
122
  return None
123
+ if hklf >= 5 or bool((batches < 0).any()):
124
+ # Only an HKLF 5 file has negative batch numbers:
125
+ singles = _singles_of_reference_domain(batches, twst)
126
+ miller, intensities, sigmas = miller[singles], intensities[singles], sigmas[singles]
127
+ if not len(miller):
128
+ return None
115
129
  try:
116
130
  operations = gemmi.SpaceGroup(space_group).operations()
117
131
  except (RuntimeError, ValueError):
@@ -128,6 +142,45 @@ def calculate_rint(hkl_file: str, space_group: str, cell: Sequence[float] | None
128
142
  return _rint_of_groups(groups, counts, intensities, sigmas)
129
143
 
130
144
 
145
+ def reference_domain(hkl_file: str, twst: int | None = None) -> int | None:
146
+ """
147
+ The twin domain whose singly indexed reflections are used for the R(int) of HKLF 5 data.
148
+ """
149
+ *_, batches = _reflections_from_shelx_hkl(hkl_file)
150
+ if not len(batches):
151
+ return None
152
+ return _largest_domain(batches[_singles(batches)]) or None
153
+
154
+
155
+ def _singles_of_reference_domain(batches: np.ndarray, twst: int | None) -> np.ndarray:
156
+ """
157
+ The singly indexed reflections of the reference domain of an HKLF 5 file.
158
+
159
+ The reference domain is the domain of a SHELX TWST instruction or, like in Olex2, the
160
+ domain with the most singly indexed reflections.
161
+ """
162
+ singles = _singles(batches)
163
+ domain = twst if twst and twst > 0 else _largest_domain(batches[singles])
164
+ return singles & (batches == domain)
165
+
166
+
167
+ def _singles(batches: np.ndarray) -> np.ndarray:
168
+ """
169
+ In an HKLF 5 file all members of a composite reflection except the last one have a negative
170
+ batch number, thus a reflection is a single if its own batch number and that of its
171
+ predecessor are positive.
172
+ """
173
+ previous = np.concatenate(([1], batches[:-1]))
174
+ return (batches > 0) & (previous > 0)
175
+
176
+
177
+ def _largest_domain(domains: np.ndarray) -> int:
178
+ if not len(domains):
179
+ return 0
180
+ values, counts = np.unique(domains, return_counts=True)
181
+ return int(values[np.argmax(counts)])
182
+
183
+
131
184
  def _within_resolution(miller: np.ndarray, cell: Sequence[float] | None,
132
185
  resolution: tuple[float, float] | None) -> np.ndarray:
133
186
  """
@@ -197,8 +250,8 @@ def _encode(miller: np.ndarray) -> np.ndarray:
197
250
  return (shifted[:, 0] << 42) | (shifted[:, 1] << 21) | shifted[:, 2]
198
251
 
199
252
 
200
- def _reflections_from_shelx_hkl(hkl_file: str) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
201
- miller, intensities, sigmas = [], [], []
253
+ def _reflections_from_shelx_hkl(hkl_file: str) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
254
+ miller, intensities, sigmas, batches = [], [], [], []
202
255
  for line in hkl_file.splitlines(keepends=False):
203
256
  if len(line) < 28:
204
257
  continue
@@ -212,9 +265,20 @@ def _reflections_from_shelx_hkl(hkl_file: str) -> tuple[np.ndarray, np.ndarray,
212
265
  miller.append(index)
213
266
  intensities.append(intensity)
214
267
  sigmas.append(sigma)
268
+ batches.append(_batch_number(line))
215
269
  return (np.array(miller, dtype=np.int32).reshape(-1, 3),
216
270
  np.array(intensities, dtype=np.float64),
217
- np.array(sigmas, dtype=np.float64))
271
+ np.array(sigmas, dtype=np.float64),
272
+ np.array(batches, dtype=np.int32))
273
+
274
+
275
+ def _batch_number(line: str) -> int:
276
+ """The batch number of an HKLF 5 reflection, negative for all but the last member of a
277
+ composite reflection."""
278
+ try:
279
+ return int(line[28:32])
280
+ except ValueError:
281
+ return 1
218
282
 
219
283
 
220
284
  if __name__ == '__main__':
@@ -5,6 +5,8 @@
5
5
  # and you think this stuff is worth it, you can buy me a beer in return.
6
6
  # Dr. Daniel Kratzert
7
7
  # ----------------------------------------------------------------------------
8
+ from __future__ import annotations
9
+
8
10
  import re
9
11
  from contextlib import suppress
10
12
  from pathlib import Path
@@ -12,11 +14,11 @@ from pathlib import Path
12
14
  from gemmi import cif as gcif
13
15
 
14
16
  from finalcif.cif.cif_file_io import CifContainer
15
- from finalcif.cif.hkl import calculate_rint
17
+ from finalcif.cif.hkl import calculate_rint, reference_domain
16
18
  from finalcif.datafiles.bruker_frame import BrukerFrameHeader
17
19
  from finalcif.datafiles.data import WorkDataMixin
18
20
  from finalcif.datafiles.p4p_reader import P4PFile
19
- from finalcif.datafiles.sadabs import Sadabs
21
+ from finalcif.datafiles.sadabs import Dataset, Sadabs
20
22
  from finalcif.datafiles.saint import SaintListFile
21
23
  from finalcif.datafiles.shelx_lst import SolutionProgram
22
24
  from finalcif.gui.dialogs import show_general_warning
@@ -231,38 +233,91 @@ class BrukerData(WorkDataMixin):
231
233
  """
232
234
  Adds the number of measured reflections and R(int) of the data set that belongs to the
233
235
  hkl file of this refinement. SHELXL can not determine these values from an HKLF 5 file,
234
- thus the values of a TWINABS file take precedence over the values in the CIF.
236
+ thus the number of reflections of a TWINABS file and a calculated R(int) take precedence
237
+ over the values in the CIF.
235
238
  """
236
239
  dataset = sadabs.select_dataset(hkl_basename=self._hkl_basename,
237
240
  reflections=self._reflections_in_hkl_file(),
238
241
  hklf=self.cif.hklf_number)
239
- if not dataset:
240
- return
241
- rint = dataset.rint or self._rint_from_hkl_data()
242
- if rint:
243
- self.sources['_diffrn_reflns_av_R_equivalents'] = (rint, self._rint_source(dataset, sadabs))
244
- if dataset.reflections_number:
242
+ self._add_rint(dataset, sadabs)
243
+ if dataset and dataset.reflections_number:
245
244
  self.sources['_diffrn_reflns_number'] = (dataset.reflections_number, source_path(sadabs.filename))
246
- if sadabs.is_twinabs and dataset.filetype == 5:
247
- self.overrides.update({'_diffrn_reflns_av_R_equivalents', '_diffrn_reflns_number'})
245
+ if sadabs.is_twinabs and dataset.filetype == 5:
246
+ self.overrides.add('_diffrn_reflns_number')
248
247
 
249
- def _rint_source(self, dataset, sadabs: Sadabs) -> str:
250
- if dataset.rint:
251
- return source_path(sadabs.filename)
248
+ def _add_rint(self, dataset: Dataset | None, sadabs: Sadabs) -> None:
249
+ """
250
+ The R(int) of an HKLF 5 refinement is calculated from the reflection data, because the
251
+ R(int) of a TWINABS listing file is a residual of the TWINABS twin fraction refinement
252
+ and not the agreement of symmetry equivalent reflections.
253
+ """
254
+ calculated = self._rint_from_hkl_data()
255
+ listed = dataset.rint if dataset else None
256
+ twinned = self._is_twinned(dataset, sadabs)
257
+ if twinned:
258
+ rint, source = calculated, self._calculated_rint_source(twinned)
259
+ if not rint:
260
+ rint, source = listed, self._listed_rint_source(dataset, sadabs)
261
+ else:
262
+ rint, source = listed, source_path(sadabs.filename)
263
+ if not rint:
264
+ rint, source = calculated, self._calculated_rint_source(twinned)
265
+ if not rint:
266
+ return
267
+ self.sources['_diffrn_reflns_av_R_equivalents'] = (rint, source)
268
+ if twinned:
269
+ self.overrides.add('_diffrn_reflns_av_R_equivalents')
270
+
271
+ def _is_twinned(self, dataset: Dataset | None, sadabs: Sadabs) -> bool:
272
+ """
273
+ SHELXL can not determine R(int) of HKLF 5 data, thus FinalCif has to do it.
274
+ """
275
+ return self.cif.hklf_number >= 5 or bool(
276
+ sadabs.is_twinabs and dataset and dataset.filetype == 5)
277
+
278
+ def _calculated_rint_source(self, twinned: bool) -> str:
279
+ if twinned:
280
+ return (f'calculated from {source_path(self.cif.fileobj)} '
281
+ f'(singles of twin component {self._reference_domain()}, '
282
+ f'merged in {self.cif.space_group})')
252
283
  return f'calculated from {source_path(self.cif.fileobj)}'
253
284
 
285
+ def _reference_domain(self) -> int | None:
286
+ return self._twst() or reference_domain(self.cif.hkl_file)
287
+
288
+ def _listed_rint_source(self, dataset: Dataset | None, sadabs: Sadabs) -> str:
289
+ """
290
+ The R(int) of the TWINABS statistics is only valid for the point group the equivalent
291
+ reflections were defined with.
292
+ """
293
+ source = source_path(sadabs.filename)
294
+ statistics = dataset.singles_statistics if dataset else None
295
+ if statistics and dataset.equivalents_point_group:
296
+ return (f'{source} (singles of twin component {statistics.component}, '
297
+ f'point group {dataset.equivalents_point_group})')
298
+ return source
299
+
254
300
  def _rint_from_hkl_data(self) -> float | None:
255
301
  """
256
- SADABS writes no overall R(int) into its listing file, therefore it is calculated from
257
- the reflection data, but only if the CIF has no R(int) yet. SHELXL can not calculate it
258
- for the composite reflections of HKLF 5 data.
302
+ R(int) calculated from the reflection data of the CIF.
303
+
304
+ SADABS writes no overall R(int) into its listing file and SHELXL can not determine it
305
+ from an HKLF 5 file, therefore it is calculated here. For HKLF 4 data this only happens
306
+ if the CIF has no R(int) yet, while for HKLF 5 data the calculated value is always
307
+ preferred over the R(int) of a TWINABS listing file, which is a model residual of the
308
+ TWINABS twin fraction refinement instead of a merging R(int).
259
309
  """
260
- if self.cif.hklf_number != 4:
261
- return None
262
- if gcif.as_string(self.cif['_diffrn_reflns_av_R_equivalents']).strip(' ?'):
310
+ if self.cif.hklf_number == 4 and gcif.as_string(
311
+ self.cif['_diffrn_reflns_av_R_equivalents']).strip(' ?'):
263
312
  return None
264
313
  return calculate_rint(self.cif.hkl_file, self.cif.space_group,
265
- cell=self.cif.cell, resolution=self._resolution_limits())
314
+ cell=self.cif.cell, resolution=self._resolution_limits(),
315
+ hklf=self.cif.hklf_number, twst=self._twst())
316
+
317
+ def _twst(self) -> int | None:
318
+ """The reference domain of a SHELX TWST instruction."""
319
+ match = re.search(r'^TWST\s+(-?\d+)', self.cif.res_file_data or '', re.MULTILINE)
320
+ return int(match.group(1)) if match else None
266
321
 
267
322
  def _resolution_limits(self) -> tuple[float, float] | None:
268
323
  """The resolution limits of a SHELX SHEL instruction, if there is one."""