psdi-data-conversion 0.2.0__tar.gz → 0.2.1__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 (153) hide show
  1. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/CHANGELOG.md +13 -0
  2. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/CONTRIBUTING.md +2 -1
  3. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/PKG-INFO +4 -2
  4. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/README.md +1 -1
  5. psdi_data_conversion-0.2.1/doc/conversion_chaining.md +302 -0
  6. psdi_data_conversion-0.2.1/doc/img/all_conversions.png +0 -0
  7. psdi_data_conversion-0.2.1/doc/img/simple_graph.svg +378 -0
  8. psdi_data_conversion-0.2.1/doc/img/simple_graph_with_custom.svg +532 -0
  9. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/constants.py +1 -1
  10. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/database.py +11 -3
  11. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/pyproject.toml +1 -0
  12. psdi_data_conversion-0.2.1/scripts/make_doc_graph_plots.py +94 -0
  13. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/tests/python/cli_test.py +14 -0
  14. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/tests/python/database_test.py +3 -0
  15. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/.gitignore +0 -0
  16. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/LICENSE +0 -0
  17. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/__init__.py +0 -0
  18. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/app.py +0 -0
  19. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/bin/LICENSE_ATOMSK +0 -0
  20. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/bin/LICENSE_C2X +0 -0
  21. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/bin/linux/atomsk +0 -0
  22. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/bin/linux/c2x +0 -0
  23. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/bin/mac/atomsk +0 -0
  24. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/bin/mac/c2x +0 -0
  25. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/converter.py +0 -0
  26. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/converters/__init__.py +0 -0
  27. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/converters/atomsk.py +0 -0
  28. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/converters/base.py +0 -0
  29. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/converters/c2x.py +0 -0
  30. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/converters/openbabel.py +0 -0
  31. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/dist.py +0 -0
  32. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/file_io.py +0 -0
  33. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/gui/__init__.py +0 -0
  34. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/gui/accessibility.py +0 -0
  35. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/gui/env.py +0 -0
  36. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/gui/get.py +0 -0
  37. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/gui/post.py +0 -0
  38. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/gui/setup.py +0 -0
  39. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/log_utility.py +0 -0
  40. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/main.py +0 -0
  41. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/scripts/atomsk.sh +0 -0
  42. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/scripts/c2x.sh +0 -0
  43. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/security.py +0 -0
  44. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/static/content/convert.htm +0 -0
  45. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/static/content/convertato.htm +0 -0
  46. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/static/content/convertc2x.htm +0 -0
  47. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/static/content/download.htm +0 -0
  48. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/static/content/feedback.htm +0 -0
  49. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/static/content/header-links.html +0 -0
  50. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/static/content/index-versions/header-links.html +0 -0
  51. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/static/content/index-versions/psdi-common-footer.html +0 -0
  52. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/static/content/index-versions/psdi-common-header.html +0 -0
  53. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/static/content/psdi-common-footer.html +0 -0
  54. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/static/content/psdi-common-header.html +0 -0
  55. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/static/data/data.json +0 -0
  56. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/static/img/colormode-toggle-dm.svg +0 -0
  57. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/static/img/colormode-toggle-lm.svg +0 -0
  58. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/static/img/psdi-icon-dark.svg +0 -0
  59. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/static/img/psdi-icon-light.svg +0 -0
  60. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/static/img/psdi-logo-darktext-simple.png +0 -0
  61. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/static/img/psdi-logo-darktext.png +0 -0
  62. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/static/img/psdi-logo-lighttext-simple.png +0 -0
  63. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/static/img/psdi-logo-lighttext.png +0 -0
  64. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/static/img/social-logo-bluesky-black.svg +0 -0
  65. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/static/img/social-logo-bluesky-white.svg +0 -0
  66. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/static/img/social-logo-instagram-black.svg +0 -0
  67. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/static/img/social-logo-instagram-white.svg +0 -0
  68. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/static/img/social-logo-linkedin-black.png +0 -0
  69. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/static/img/social-logo-linkedin-white.png +0 -0
  70. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/static/img/social-logo-mastodon-black.svg +0 -0
  71. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/static/img/social-logo-mastodon-white.svg +0 -0
  72. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/static/img/social-logo-x-black.svg +0 -0
  73. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/static/img/social-logo-x-white.svg +0 -0
  74. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/static/img/social-logo-youtube-black.png +0 -0
  75. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/static/img/social-logo-youtube-white.png +0 -0
  76. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/static/img/ukri-epsr-logo-darktext.png +0 -0
  77. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/static/img/ukri-epsr-logo-lighttext.png +0 -0
  78. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/static/img/ukri-logo-darktext.png +0 -0
  79. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/static/img/ukri-logo-lighttext.png +0 -0
  80. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/static/javascript/accessibility.js +0 -0
  81. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/static/javascript/common.js +0 -0
  82. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/static/javascript/convert.js +0 -0
  83. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/static/javascript/convert_common.js +0 -0
  84. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/static/javascript/convertato.js +0 -0
  85. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/static/javascript/convertc2x.js +0 -0
  86. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/static/javascript/data.js +0 -0
  87. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/static/javascript/format.js +0 -0
  88. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/static/javascript/load_accessibility.js +0 -0
  89. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/static/javascript/psdi-common.js +0 -0
  90. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/static/javascript/report.js +0 -0
  91. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/static/styles/format.css +0 -0
  92. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/static/styles/psdi-common.css +0 -0
  93. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/templates/accessibility.htm +0 -0
  94. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/templates/documentation.htm +0 -0
  95. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/templates/index.htm +0 -0
  96. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/templates/report.htm +0 -0
  97. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/testing/__init__.py +0 -0
  98. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/testing/constants.py +0 -0
  99. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/testing/conversion_callbacks.py +0 -0
  100. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/testing/conversion_test_specs.py +0 -0
  101. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/testing/gui.py +0 -0
  102. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/testing/utils.py +0 -0
  103. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/psdi_data_conversion/utils.py +0 -0
  104. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/scripts/setup_bin.py +0 -0
  105. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/test_data/1ARJ.mmcif +0 -0
  106. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/test_data/1NE6.mmcif +0 -0
  107. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/test_data/5a9z-assembly1.cif +0 -0
  108. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/test_data/Fapatite.ins +0 -0
  109. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/test_data/aceticacid.mol +0 -0
  110. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/test_data/benzyne.molden +0 -0
  111. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/test_data/caffeine-smi.tar +0 -0
  112. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/test_data/caffeine-smi.tar.gz +0 -0
  113. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/test_data/caffeine-smi.zip +0 -0
  114. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/test_data/caffeine.inchi +0 -0
  115. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/test_data/ch3cl-esp.cub +0 -0
  116. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/test_data/cyclopropane_err.mol +0 -0
  117. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/test_data/ethanol.xyz +0 -0
  118. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/test_data/fullRhinovirus.pdb +0 -0
  119. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/test_data/hemoglobin.pdb +0 -0
  120. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/test_data/nacl.cif +0 -0
  121. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/test_data/nacl.mol +0 -0
  122. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/test_data/output/aceticacid.log.txt +0 -0
  123. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/test_data/output/aceticacid.mol2 +0 -0
  124. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/test_data/output/caffeine-2D-fastest.xyz +0 -0
  125. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/test_data/output/caffeine-3D-best.xyz +0 -0
  126. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/test_data/output/caffeine.smi +0 -0
  127. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/test_data/output/caffeine.xyz +0 -0
  128. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/test_data/output/caffeine_a_in.smi +0 -0
  129. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/test_data/output/caffeine_a_in_kx_f4_l5_out.smi +0 -0
  130. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/test_data/output/caffeine_a_in_kx_f4_out.smi +0 -0
  131. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/test_data/output/caffeine_a_in_kx_out.smi +0 -0
  132. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/test_data/output/caffeine_a_in_x_out.smi +0 -0
  133. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/test_data/output/hemoglobin_Atomsk.xyz +0 -0
  134. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/test_data/output/hemoglobin_c2x.xyz +0 -0
  135. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/test_data/output/nacl.log +0 -0
  136. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/test_data/output/nacl.mol +0 -0
  137. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/test_data/output/quartz_OB.cif +0 -0
  138. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/test_data/output/quartz_OB.log.txt +0 -0
  139. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/test_data/output/quartz_atomsk.cif +0 -0
  140. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/test_data/output/quartz_atomsk.log.txt +0 -0
  141. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/test_data/output/standard_test.inchi +0 -0
  142. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/test_data/output/xyz_files-mol.zip +0 -0
  143. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/test_data/output/xyz_files.log.txt +0 -0
  144. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/test_data/periodic_dmol3.outmol +0 -0
  145. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/test_data/quartz.xyz +0 -0
  146. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/test_data/quartz_err.xyz +0 -0
  147. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/test_data/standard_test.cdxml +0 -0
  148. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/tests/gui/gui_test.py +0 -0
  149. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/tests/python/converter_test.py +0 -0
  150. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/tests/python/dist_test.py +0 -0
  151. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/tests/python/file_io_test.py +0 -0
  152. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/tests/python/logging_test.py +0 -0
  153. {psdi_data_conversion-0.2.0 → psdi_data_conversion-0.2.1}/tests/python/security_test.py +0 -0
@@ -1,5 +1,18 @@
1
1
  # Changelog for PSDI Data Conversion
2
2
 
3
+ ## v0.2.1
4
+
5
+ ### Bugfixes
6
+
7
+ - Fixed bug where when a conversion pathway is requested which turns out to be impossible, an exception is thrown instead of `None` being returned
8
+ - The logging level in the production deployment will now properly be INFO, while it will be DEBUG in the dev deployment
9
+ - Fixed the label for formats supporting 3D coordinates, which was unintentionally a duplicate of the 2D label
10
+ - Fixed crash when requesting info on a conversion which is impossible even with chained conversions
11
+
12
+ ### Documentation Changes
13
+
14
+ - Added file `doc/conversion_chaining.md`, which explains the thought process behind the algorithm we (intend to) use for finding the best chained conversion
15
+
3
16
  ## v0.2.0
4
17
 
5
18
  ### New and Changed Functionality
@@ -49,7 +49,8 @@ The following tasks should be completed before merging a release candidate branc
49
49
  - Check that the project version is updated to the desired new version in all places it appears:
50
50
 
51
51
  - `CHANGELOG.md` (The top section should reflect the new version)
52
- - `README.md` (The top line should reflect the new version)
52
+
53
+ - Update the release date at the top of `README.md`
53
54
 
54
55
  - Ensure that all automated tests and checks pass - these should be run automatically on the PR opened above
55
56
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: psdi_data_conversion
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Chemistry file format conversion service, provided by PSDI
5
5
  Project-URL: Homepage, https://data-conversion.psdi.ac.uk/
6
6
  Project-URL: Documentation, https://psdi-uk.github.io/psdi-data-conversion/
@@ -241,6 +241,8 @@ Requires-Dist: pytest; extra == 'gui-test'
241
241
  Requires-Dist: requests; extra == 'gui-test'
242
242
  Requires-Dist: selenium; extra == 'gui-test'
243
243
  Requires-Dist: webdriver-manager; extra == 'gui-test'
244
+ Provides-Extra: plots
245
+ Requires-Dist: matplotlib; extra == 'plots'
244
246
  Provides-Extra: test
245
247
  Requires-Dist: coverage; extra == 'test'
246
248
  Requires-Dist: pytest; extra == 'test'
@@ -251,7 +253,7 @@ Description-Content-Type: text/markdown
251
253
  [![License Badge](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
252
254
  ![Coverage Badge](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/brgillis/dbd938192dc4de9b7779978e515c0e79/raw/covbadge.json)
253
255
 
254
- Release date: 2024-04-29
256
+ Release date: 2024-06-03
255
257
 
256
258
  This is the repository for the PSDI PF2 Chemistry File Format Conversion project. The goal of this project is to provide utilities to assist in converting files between the many different file formats used in chemistry, providing information on what converters are available for a given conversion and the expected quality of it, and providing multiple interfaces to perform these conversions. These interfaces are:
257
259
 
@@ -3,7 +3,7 @@
3
3
  [![License Badge](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
4
4
  ![Coverage Badge](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/brgillis/dbd938192dc4de9b7779978e515c0e79/raw/covbadge.json)
5
5
 
6
- Release date: 2024-04-29
6
+ Release date: 2024-06-03
7
7
 
8
8
  This is the repository for the PSDI PF2 Chemistry File Format Conversion project. The goal of this project is to provide utilities to assist in converting files between the many different file formats used in chemistry, providing information on what converters are available for a given conversion and the expected quality of it, and providing multiple interfaces to perform these conversions. These interfaces are:
9
9
 
@@ -0,0 +1,302 @@
1
+ # Conversion Chaining
2
+
3
+ ## Motivation
4
+
5
+ The goal of this project is to provide a one-stop for conversion between the hundreds of file formats used in chemistry to describe molecular structure. This is done in part by using existing converters as plugins and allowing the user to pick a converter which can perform a desired conversion.
6
+
7
+ However, there are many pairs of formats where no direct conversion is possible, and we aim to handle this through chained conversions using multiple converters. This presents the problem: How do we determine possible chained conversions? And having determined these possible conversions, how do we determine the best one to use?
8
+
9
+ To answer this question, it helps to reframe the problem in terms of the mathematical structure known as the "graph", in particular the [directed graph](https://en.wikipedia.org/wiki/Directed_graph) (since there are some cases where conversion is only allowed in one direction - an example being the [InChIKey format](https://en.wikipedia.org/wiki/International_Chemical_Identifier#InChIKey), which is a hashed representation and thus can only be converted to, but not from), with formats represented as vertices and conversions as edges. Existing packages such as `igraph` allow us to take advantage of pre-existing code to solve problems such as this rather than needing to write our own from scratch.
10
+
11
+ The full graph of formats and allowed conversions looks like the following, with black dots representing different formats and different edge colors representing different converters performing each conversion:
12
+
13
+ ![Graph of all conversions](img/all_conversions.png)
14
+
15
+ We can see from this that most formats are only supported by one converter, with only a handful being supported by more than one. This underlines the necessity of supporting chained conversions, as a random pair of two formats will not be supported by a direct conversion more often than not.
16
+
17
+ ## General pathfinding
18
+
19
+ Rather than tackling this full graph, let's look at a much smaller, more manageable subset it, with just four supported formats:
20
+
21
+ ![Graph of four formats and their conversions, showing two bridging formats and two formats only supported by one converter](img/simple_graph.svg)
22
+
23
+ This graph shows four formats: MOLDY, PDB, CIF, and InCHi. PDB and CIF are supported by both the Atomsk (red) and Open Babel (blue) converters, while MOLDY is supported only by Atomsk and InCHi is supported only by Open Babel.
24
+
25
+ A user who wished to convert from MOLDY to InCHi would thus face a problem here, that no converter can perform this conversion directly. Our goal then is to determine a possible path the user could take. Looking at this graph, it's easy to intuit that there are two possible reasonable paths:
26
+
27
+ 1. Convert from MOLDY to PDB with Atomsk, then from PDB to InCHi with Open Babel
28
+ 2. Convert from MOLDY to CIF with Atomsk, then from CIF to InCHi with Open Babel
29
+
30
+ But with a much larger graph, it won't always be so easy to find a path. And in fact, even in this case, these are just the most obvious paths to a human. An exhaustive search would see many more paths, such as:
31
+
32
+ 3. Convert from MOLDY to PDB with Atomsk, then from PDB to CIF with Atomsk, then from CIF to InCHi with Open Babel
33
+ 4. Convert from MOLDY to CIF with Atomsk, then from CIF to PDB with Open Babel, then from PDB to InCHi with Open Babel
34
+
35
+ If we don't put in a constraint to avoid retracing one's steps, the number of paths is in fact infinite, but even without that constraint we see that a computer will pick up on many paths which are obviously suboptimal.
36
+
37
+ This is a common problem faced when working with graphs, with the most common real-world example of this coming up being pathfinding, i.e. finding directions from one location to another. Let's use this as an analogy to show how we would determine an optimal path.
38
+
39
+ We'll consider here four locations (analogous to the formats): New York, London, Edinburgh, and Oxford. There are flight connections between all of New York, London, and Edinburgh, and it's possible to drive between all of London, Edinburgh, and Oxford.
40
+
41
+ So our analogous pathfinding problem to the format conversion problem above would someone who wishes to travel from New York to Oxford. This can't be done solely by flying or by driving, but can be done with a hybrid of the two. However, which path is best depends on what the goal is. Let's say the goal is purely to minimise travel time. The travel times between each location are:
42
+
43
+ - New York and London (flight): 7 hours
44
+ - New York and Edinburgh (flight): 6 hours
45
+ - London and Edinburgh (flight): 1 hour
46
+ - London and Edinburgh (driving): 8 hours
47
+ - London and Oxford (driving): 2 hours
48
+ - Edinburgh and Oxford (driving): 7 hours
49
+
50
+ An exhaustive search of all paths that don't retrace their steps would find here that the fastest route is New York to London by flight (7 hours), then London to Oxford by driving (2 hours).
51
+
52
+ But an exhaustive search isn't reasonable with larger maps, as the number of possible routes scales exponentially with the number of vertices (`O(e^V)` time). Luckily, this is a solved problem in graph theory, and modern implementations of [Dijkstra's_algorithm](https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm) can find the optimal route in `O(E + V log(V))` time (where E is the number of edges and V is the number of vertices), making the problem easily tractable for a graph of the size we're working with.
53
+
54
+ ## Determining pathfinding weights
55
+
56
+ In the above example, we chose time as the relevant property we wanted to minimise in pathfinding, but of course we could have chosen something else, such as price, and minimised that instead. The chosen quantity is referred to as the "weight" of a path in the general case. Dijkstra's algorithm functions with any set of unbounded non-negative weights, even if they don't follow intuitive physical properties such as the [triangle inequality](https://en.wikipedia.org/wiki/Triangle_inequality), i.e. it's allowable for A->B to have a greater weight than the sum of A->C and C->B.
57
+
58
+ But what if we want multiple factors to be relevant, or what if there are other constraints we need to abide by, or wish to abide by if at all possible?
59
+
60
+ This is in fact the case we're dealing with when it comes to format conversions. Without getting into the details yet of what these mean, the goals we have are, in descending order of priority:
61
+
62
+ 1. Don't follow a path which loses and then re-extrapolates any type of data shared by both the input and output format
63
+
64
+ 2. Minimise loss of numerical accuracy (e.g. floating-point values being truncated in the conversion process)
65
+
66
+ 3. Minimise time (implicitly minimising the number of steps)
67
+
68
+ Dijkstra's algorithm requires only one weight per path, so we have to find some way to combine these aspects.
69
+
70
+ ### Weighted combination
71
+
72
+ The most straightforward way to have only one weight per path is to calculate it a weighted combination of the relevant factors, e.g.:
73
+
74
+ ```math
75
+ \displaystyle W_i = w_{x}x_i + w_{y}y_i + w_{z}z_i
76
+ ```
77
+
78
+ where $W_i$ is the total weight assigned to edge $i$ and $w_x$, $w_y$, and $w_z$ are the relative weights of factors $x$, $y$, and $z$ respectively. So we could for instance assign the highest weight to the most important factor, etc.
79
+
80
+ But the situation here is actually a bit more complicated. We don't simply wish for some of these factors to be weighted more, we wish for them to be strictly more important - no increase in factor $x$ can be compensated for by any decrease in $y$ or $z$. To use the travelling example, we could say that the high-importance factor is whether or not the traveller's luggage is permitted on the route, and the low-importance factor is how jostled the luggage will be in trip. Obviously if the luggage can't make the trip at all, it doesn't matter how smooth a ride it will have.
81
+
82
+ Strictly speaking, this can't be accomplished with a weighted combination, as no matter how different the weights are, there could always be extreme cases where the weight is overcome. This might occur rarely enough that it gives an acceptably low rate of error though, so we'll pin this possibility while we investigate if other solutions are worthwhile.
83
+
84
+ ### Tiered pathfinding
85
+
86
+ In the example here of losing one's luggage, this is a binary event - either the condition of keeping one's luggage through the trip is satisfied or it isn't. With this binary condition, there isn't likely to be a single best path, but rather many paths which fulfill this criterion equally.
87
+
88
+ Pathfinding algorithms are capable of handling cases like this where there are many equally-good paths, so what we can do with this is, we can implement two stages of pathfinding. The first looks only at the high-importance criteria and identifies only the paths which satisfy it. The second stage looks at the second criteria and only the paths determined valid in the first step, and minimises the weight through these paths using the second criteria.
89
+
90
+ The fact that the high-importance criteria isn't binary actually isn't necessary for this solution. Instead of searching for all pathways which satisfy the condition, we can instead search for all pathways which minimise it to the same greatest extent.
91
+
92
+ This has the advantage over the previous approach that it will guarantee that the strict relative importance of the criteria is respective, but it comes with the drawback of greater computational overhead, needing to run the pathfinding algorithm multiple times (or else running some analogous operation on the list of shortest pathways from the first step such as a sort). This will also get more complicated to program if there are more than two importance tiers.
93
+
94
+ ### Custom weight type
95
+
96
+ It's possible to run the pathfinding in a single stage with strict tiering of criteria if we use a custom data type for the weights of paths. The only requirements that Dijkstra's algorithm places on the weights is that they be non-negative, addable, and comparable. It's possible to construct a data type which meets these criteria and also allows for strict importance tiering, and in fact such a type is already in use for version numbering.
97
+
98
+ Version numbers are period-separated integers such as 0.1, 1.245.0, 0.2.40, etc. A difference in a more-major (earlier) number always takes precedence in a comparison over any difference in a less-major number, i.e. (X+1).0 is greater than X.Y for any value of Y, no matter how large, e.g. 1.0.0 is greater than 0.999999.0.
99
+
100
+ A number system such as this could be used for pathfinding with tiered importance simply by setting up appropriate weights, e.g.:
101
+
102
+ - Weight for luggage being allowed at all on the trip: 1.0.0
103
+ - Weight for time in hours of the trip: 0.2.0
104
+ - Weight for price in $100 for the trip: 0.1.0
105
+ - Weight for how jostled the luggage gets in the trip: 0.0.1
106
+
107
+ A single pathfinding algorithm could then be run, which will prioritise trips where luggage is allowed. Among those where it is (or among all if it isn't allowed on any route), it will balance time and price. If there are multiple best paths which tie on this as well, it will then prioritise whichever jostles the luggage the least.
108
+
109
+ This solution keeps the programming of the pathfinding simple (the extra complexity going into the definition of the data type), but will slow it down as comparisons of a custom data type such as this will take longer than native types, as compilers, hardware etc. are optimised for native numerical types. This also has the issue that if a third-party library is used for the pathfinding, it isn't likely to support a custom data type for weights. For instance, the `igraph` library only supports integer and floating-point weights.
110
+
111
+ ## Optimal approach for our task
112
+
113
+ ### Nature of the problem
114
+
115
+ To determine which approach is best for our task, let's now get into the details of what we need to do.
116
+
117
+ A chemical file format can store various types of information, of which we currently keep track of four, listed here in descending order of importance:
118
+
119
+ 1. **Composition**: What elements make up the chemical (there are in fact some formats which don't store this information, such as [InChIKey](https://en.wikipedia.org/wiki/International_Chemical_Identifier#InChIKey))
120
+ 2. **Connections**: Which atoms in the chemical are bonded to which other atoms
121
+ 3. **Coordinates**: The physical locations of the atoms relative to each other. Some formats provide a full 3D information, while others only provide 2D information. To keep track of which is supported, our database lists 2D Coordinates and 3D Coordinates separately
122
+
123
+ At present, our database stores whether or not each of these properties is supported for many, but not all formats, listing the status as unknown for the remainder. While of course ideally this information should be added to the database, in the meantime it's an issue we need to take into account.
124
+
125
+ When a format is converted to another which is capable of storing more information than the source format, this information will either be excluded or extrapolated, depending on the specific scenario.
126
+
127
+ Different formats also store numeric information at different precisions (i.e. the number of digits), so a conversion to a format with lesser precision will result in some data loss. Even between formats with the same precision in theory, data loss may occur if it's represented differently (e.g. a point in 2D space could be described by its $x$ and $y$ coordinates or $r$ and $\theta$, and a conversion between the two will lose a small amount of information due to rounding at the final step), so it's safest to assume a small loss of information with every conversion.
128
+
129
+ All else being equal, the time to perform a conversion is also relevant.
130
+
131
+ For all considerations here aside from time, we also have to keep in mind whether the source and target format support this information or precision. For instance, consider three cases:
132
+
133
+ 1. Format A (9-digit precision) -> Format B (9-digit precision) -> Format C (4-digit precision) -> Format D (9-digit precision)
134
+
135
+ 2. Format E (4-digit precision) -> Format B (9-digit precision) -> Format C (4-digit precision) -> Format D (9-digit precision)
136
+
137
+ 3. Format A (9-digit precision) -> Format B (9-digit precision) -> Format C (4-digit precision) -> Format F (4-digit precision)
138
+
139
+ Consider here the conversion from Format B to Format C, which goes from 9-digit precision to 4-digit precision. What should the weight be for the loss of data precision? In case 1, where the source format has 9-digit precision, this is the first step to lose that precision, and so should be weighted high to reflect this.
140
+
141
+ However, in case 2, the source format only has 4-digit precision, so although it's converted into a format with greater precision, it never actually has this precision to lose in the conversion from B to C, so it shouldn't be given a weight penalty for this. Case 3 shows the opposite issue: Although the source format does lose precision in this step, the target format doesn't support this precision, so the loss is inevitable and will have to happen at some point.
142
+
143
+ This leads to an important conclusion about this problem: Edge weights are not independent, instead being dependent on the source and target formats. In particular, we can say that a weight for losing information should be applied only if both the source and target format share that information (or level of precision).
144
+
145
+ If we want to be particularly careful, we should also say that this weight should only be applied the first time this information is lost along a pathway. This is difficult to implement though as it would mean that an edge would have a different weight depending on the path taken to get to its source vertex, which is not standard and would require a completely different pathfinding algorithm. However, this would require a particularly long chain of conversions - which is unlikely to occur - and the impact if it did would not be significant - an already dispreferred path would be even more dispreferred - so the amount of work needed to implement this would not nearly be justified.
146
+
147
+ ### Constraint summary
148
+
149
+ Putting this all together, we have the following list of properties to weight, in descending order of importance:
150
+
151
+ 1. Composition
152
+ 2. Connections
153
+ 3. 2D Coordinates
154
+ 4. 3D coordinates
155
+ 5. Numerical precision
156
+ 6. Conversion time
157
+
158
+ With the following notes:
159
+
160
+ - We want to apply this importance strictly - e.g. a faster conversion isn't worth losing numerical precision
161
+ - We should always apply a small weight to numerical precision for each conversion to be on the safe side
162
+ - Not all formats currently list what information they support
163
+ - Any loss of information should only be weighted if both the source and target format support this information, and similarly with numerical precision
164
+
165
+ ### Our approach
166
+
167
+ Let's look again at the graph of all conversions:
168
+
169
+ ![Graph of all conversions](img/all_conversions.png)
170
+
171
+ Note that the vast majority of formats are supported by only one converter, with only a small number of formats being supported by more than one and acting as bridges. Converters also tend to support any-to-any conversions of formats they support, with the only exceptions being some formats they can only convert from, and others they can only convert to.
172
+
173
+ This implies that in the vast majority of cases, conversion chains will be short, likely with two intermediate formats at most, and typically just one. And since there are only a relative few formats that can act as bridges, any filter on equally-short pathways (however "short" is defined, as long as there's at least some cost to each conversion) is going to result in a relatively small number of pathways.
174
+
175
+ We also face the key issue here that any weights we assign to conversions (aside from for the conversion time) will depend on what the original source format and final target format are. This means there is going to be computational overhead in updating the edge weights, which will be more costly the more edges we have to deal with.
176
+
177
+ Since any initial filter will reduce the number of potential pathways so much, the additional computational overhead cost of the tiered pathfinding approach will be minimal. Meanwhile, with so many different factors to consider, the weighted combination approach could become unwieldy. Either solution is probably workable, but consideration of the factors involved leads us to the conclusion that a hybrid approach is best.
178
+
179
+ Due to the computational cost of calculating weights for all conversions, we want to do this on the full set of conversions only once, and then use this to reduce the number of formats and edges we need to worry about to a manageable number. After that point, it's less costly to make more complicated weight calculations.
180
+
181
+ Looking at the list the properties to weight, the first four factors (Composition, Connections, 2D Coordinates, and 3D coordinates) are all binary, the fifth (precision) requires a more complicated comparison and calculation for each conversion, and the sixth (time) is independent. This means we'll benefit most by cutting down the graph before dealing with precision. We thus apply a weighted combination approach first to the first four factors, cut down the graph, then use a second weighted combination approach for the final two.
182
+
183
+ ### Weighted combination 1: Types of information
184
+
185
+ Since the first four factors are all binary, it can in fact be quite efficient to calculate weights for all of them at once by using bitwise operations. That is, we can use an unsigned integer where different bits represent whether or not we weight for the loss of a given property. We assign the following bits to these properties:
186
+
187
+ | Property | Bit |
188
+ | -------------- | --- |
189
+ | Composition | 24 |
190
+ | Connections | 18 |
191
+ | 2D Coordinates | 12 |
192
+ | 3D Coordinates | 6 |
193
+ | (always) | 0 |
194
+
195
+ Note here that we also include an always-weight bit at bit 0. This is to impose a minimal cost for any conversion, so that the pathfinding algorithm won't end up including circuitious paths through zero-weight conversions in the list of shortest paths returned from this step.
196
+
197
+ This set of bits was chosen so that they could all fit into a 32-bit integer value and they are spaced out enough that if used as weights there is negligible chance that any path will accrue enough weight from a lower-importance property to overcome the lack of a weight from a higher-importance property (the 6-bit difference would require 64 occurences to be overcome, which is exceedingly unlikely to cause a problem even imaging the future addition of many different specialised converters).
198
+
199
+ The procedure for this step is as follows:
200
+
201
+ 1. For each conversion in the database, determine a bit mask, where 1 is set in the bit for a property if it's supported in the source property and not supported in the target property (if unknown for the source, assume it to be supported, and if unknown for the target, assume it to not be supported). This can be precalculated for every conversion, since this part doesn't depend on the source and target formats. For instance, take the conversion from molreport to InChI:
202
+
203
+ - Molreport supports Composition, Connections, and 2D Coordinates
204
+ - InChI supports Composition and Connections
205
+ - 2D Coordinates is thus supported only by the source format, so we set the bit for it (bit 12) to be 1, as well as the always-on bit (bit 0), getting the bit mask 00000000 00000000 00010000 00000001 (decimal representation 4,097)
206
+
207
+ 2. Determine a bit mask for the specific conversion requested, where 1 is set in the bit for a property if it's supported in both the original source and final target format (if unknown, consider it supported). For instance, for the conversion from MMCIF to MOLDY:
208
+
209
+ - MMCIF supports all properties
210
+ - MOLDY has unknown support for all properties
211
+ - We assume MOLDY has support for all properties to be on the safe side. Thus, both the original source and final target support all properties, so we set 1 for all of them, getting the bit mask 00000001 00000100 00010000 01000001 (decimal representation 17,043,521)
212
+
213
+ 3. Calculate the weights for all conversions by performing a bitwise "and" operation on their bit mask and the bit mask for the specific conversion requeted. This is the weight to be used for this conversion in determining a path for the conversion requested. For this example:
214
+
215
+ - The molreport to InChI conversion has bit mask 00000000 00000000 00010000 00000001
216
+ - The MMCIF to MOLDY conversion requested has bit mask 00000001 00000100 00010000 01000001
217
+ - A bitwise "and" combination of these gives 00000000 00000000 00010000 00000001 (decimal representation 4,097)
218
+
219
+ 4. Run the pathfinding algorithm using these weights, collecting a list of the shortest paths
220
+
221
+ ### Weighted combination 2: Precision and time
222
+
223
+ The first weighted combination will cut the number of possible shortest paths down to a much smaller number. In the current setup, there are likely to be fewer than 10 pathways at this point, so a full pathfinding approach might not even be necessary to determine the shortest based on loss of precision and time, with a simple search through the list potentially being faster given the lower overhead. However, this may not continue to be the case as the project expands and more converters are integrated with it, so a full pathfinding approach is probably still best here.
224
+
225
+ The first step here is to prune down the graph to just the formats and conversions which are on one of the shortest paths. This is best done by constructing sets of each from the list of shortest paths, then reconstructing a smaller graph using only these. Note that this smaller graph won't include all possible conversions between formats within it, but only those which were used in one of the shortest paths determined before.
226
+
227
+ Next, we have to determine a weight for precision and time for each of the edges in this new graph, prioritising the former. We'll start with the latter though, since placing an upper bound on it will let us know how much of a relative weight we need to give to precision so that differences in runtime will never dominate.
228
+
229
+ #### Time weights
230
+
231
+ At present, we lack information on the typical time to perform different conversions, so the following is speculative. While it is in theory possible to test and record times for all conversions, this is very demanding and likely not worth the efforts. Since all the converters incorporated so far support conversion between any pair of supported formats (albeit with some formats only being supported as sources and some only as targets), a rough formula to estimate the time for a given conversion would be:
232
+
233
+ ```math
234
+ t_{\rm tot}(x) = t_{\rm o} + t_{\rm s}(x) + t_{\rm t}(x)
235
+ ```
236
+
237
+ where $t_{\rm o}$ is the overhead time used by the converter for any conversion, $t_{\rm s}$ is the time needed to convert from the source format, $t_{\rm t}$ is the time needed to convert to the target format, and $x$ represents the size of the file to be converted.
238
+
239
+ The overhead time will be the same for all conversions, and we already intend for the precision weight to include a minimum weight for all conversions, so it serves no purpose including it here. The remaining terms will likely share the same time-dependence, so we can reasonably divide it out, getting a simplified time weight equation:
240
+
241
+ ```math
242
+ w_{\rm tot} = w_{\rm s} + w_{\rm t}
243
+ ```
244
+
245
+ where the time weight for a given conversion is simply the sum of time weights for the source and target formats (note that this will still differ for each converter). Reasonable values for these could be determined through the following procedure:
246
+
247
+ 1. Choose a "standard" file. Convert it to every format supported by each converter for direct conversion many times, timing the conversion process and averaging for each target format. The lowest such time will be our zero-point; subtract it from all times. The remaining time in milliseconds can then be used as the target time weight for each format. Also note the average of these weights, which can be used as a default value.
248
+ 2. For each of these converted files, convert it back to the original format of the standard file, again timing, averaging, and subtracting off the zero-point. This will give the source time weight for each format.
249
+ 3. Repeat this procedure for each converter, getting separate source and target weights for each format with each converter.
250
+
251
+ Note that this process won't produce weights for the format of the original standard file. This will have to be determined in a separate step, using comparisons of conversions to/from it with other formats and their conversions to each other.
252
+
253
+ Milliseconds will likely be the most useful unit for time weights, based on preliminary testing.
254
+
255
+ Note that as time is the lowest-priority factor in pathfinding, gathering accurate time weights is a low-priority task. In the meantime, default weights can be used.
256
+
257
+ #### Precision weights
258
+
259
+ From the previous section, we noted that time weights will be measured in milliseconds. From testing, a reasonable upper bound for the time of a conversion is 10 seconds, but the weights will be measured on smaller files and thus likely to be much less than this. Since we only have two factors to worry about for this combination, we can be sure we're well in the clear by using a base precision weight of 65,536 (equivalent to a total conversion time of ~65.5 seconds on small test files, which should never be the case). For brevity, we will omit this factor from the discussion below.
260
+
261
+ When a number is expressed to a given number of digits, e.g. "3.510", the possible true number represented will be one that could round to it, here 3.5095 through 3.5105. With no other information, all values in this range are equally likely, so this can be described as a uniform distribution, which has a standard deviation proportional to the range spanned. In cases where this number is converted to another number with the same precision, this standard deviation can be used as a rough estimate of the loss of precision. It's possible that numbers will simply be copied over without any loss of precision, but it's best to be safest and impose a minimal cost for each conversion.
262
+
263
+ If this value is stored to fewer decimal places, e.g. for "3.51", the range becomes 3.505 through 3.515, increasing the standard deviation by a factor of 10 per decimal place lost. So if we wished to represent this directly in the weight, we could impose weight of $10^N$, where $N$ is the number of decimal places lost, with a minimum value of 1.
264
+
265
+ This level of accurately representing the variance isn't necessary, however. We're free to scale the values as we wish, as long as there isn't a risk of lower-tier changes overwhelming higher-tier changes. If we were to use a similar method to the weights for types of information and use bits for each level of precision lost, we would need to reserve 3 bits (a factor of 8) for each digit to be comfortable.
266
+
267
+ If we account for the values taken up by the time weight, this gives only enough room to represent 5 digits of precision loss in a 32-bit integer, which isn't likely to be enough - formats can range from just a few digits up to 16, or possibly more. It's safest to use a 64-bit integer here, giving us triple the room to store precision weight information, which should be enough for reasonable use cases.
268
+
269
+ #### Putting them together
270
+
271
+ Now, let's put these together into a specific procedure. The weight for this tier will be the sum of a weight for time (capped at a value of 65,535) and a weight for precision (setting bits from 2^16 = 65,536 upwards based on which digits are lost). The time weight is simply the sum of the calculated weights for the source and target formats, with the cap imposed. The precision weight is more complicated, calculated through the following procedure:
272
+
273
+ 1. Determine the lower precision of the original source and final target formats - call this number of digits $D_{\rm 0}$
274
+ 2. For each conversion, compare the precision of the target format $D_i$ to $D_{\rm 0}$, to get the number of digits lost $L_i = D_{\rm 0} - D_i$, constrained to the values $0 \leq L_i \leq 12$ - the minimum of 0 imposes a minimum cost for each conversion, and the maximum of 12 prevents it from overflowing a 64-bit integer.
275
+ 3. The precision weight is then determined by setting bit $16 + 3L_i$ to 1, i.e. setting the value to $2^{16 + 3L_i}$
276
+
277
+ To illustrate this, let's imagine the following scenario, for the pathway A -> B -> C -> D:
278
+
279
+ - Format A: 8-digit precision, source time weight 10, target time weight 11
280
+ - Format B: 9-digit precision, source time weight 20, target time weight 25
281
+ - Format C: 4-digit precision, source time weight 5, target time weight 6
282
+ - Format D: 9-digit precision, source time weight 30, target time weight 35
283
+
284
+ The time weights for each conversion are:
285
+
286
+ - A -> B: Source time weight of A (10) + Target time weight of B (25) = 35
287
+ - B -> C: Source time weight of B (20) + Target time weight of C (6) = 26
288
+ - C -> D: Source time weight of C (5) + Target time weight of D (35) = 40
289
+
290
+ For the precision weight, $D_{\rm 0}$ is set to the lower precision of format A (8) and D (9): 8. The precision weights for each conversion are then calculated to be:
291
+
292
+ - A -> B: Target format's precision is $D_{\rm B}=9$; $D_{\rm 0} - D_{\rm B} = -1$; bounded to $L_{\rm B} = 0$; giving precision weight $2^{16+3*0}=65,536$
293
+ - B -> C: Target format's precision is $D_{\rm C}=4$; $D_{\rm 0} - D_{\rm C} = 4$; already within bounds, so $L_{\rm C} = 4$; giving precision weight $2^{16+3*4}=268,435,456$
294
+ - C -> D: Target format's precision is $D_{\rm D}=9$; $D_{\rm 0} - D_{\rm D} = -1$; bounded to $L_{\rm D} = 0$; giving precision weight $2^{16+3*0}=65,536$
295
+
296
+ The combined precision and time weights for each conversion are then:
297
+
298
+ - A -> B: Time weight (35) + Precision weight (65,536) = 65,571
299
+ - B -> C: Time weight (26) + Precision weight (268,435,456) = 268,435,482
300
+ - C -> D: Time weight (40) + Precision weight (65,536) = 65,576
301
+
302
+ The combined precision and time weights can then be assigned to all conversions in the reduced graph, and the pathfinding algorithm run on it to find all equally-shortest paths. In the case that one shortest path is found, it's returned as the best path. If more than one is found, whichever is the first in the list can be returned.