rowan-python 3.2.1__tar.gz → 3.2.3__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 (273) hide show
  1. {rowan_python-3.2.1 → rowan_python-3.2.3}/PKG-INFO +2 -2
  2. {rowan_python-3.2.1 → rowan_python-3.2.3}/pyproject.toml +2 -2
  3. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/__init__.py +3 -0
  4. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/project.py +8 -1
  5. rowan_python-3.2.3/rowan/spectra.py +68 -0
  6. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/utils.py +63 -14
  7. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/workflows/_molecular_dynamics.py +27 -2
  8. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/workflows/binding_affinity.py +7 -1
  9. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/workflows/conformer_search.py +62 -10
  10. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/workflows/protein_md.py +3 -3
  11. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/workflows/relative_binding_free_energy_perturbation.py +67 -2
  12. {rowan_python-3.2.1 → rowan_python-3.2.3}/skills/computational-chemistry-and-biology/reference/binding_affinity.md +11 -1
  13. {rowan_python-3.2.1 → rowan_python-3.2.3}/skills/computational-chemistry-and-biology/reference/conformer_search.md +19 -0
  14. {rowan_python-3.2.1 → rowan_python-3.2.3}/skills/computational-chemistry-and-biology/reference/protein_md.md +1 -1
  15. {rowan_python-3.2.1 → rowan_python-3.2.3}/tests/test_binding_affinity.py +33 -0
  16. {rowan_python-3.2.1 → rowan_python-3.2.3}/tests/test_conformer_search.py +20 -0
  17. rowan_python-3.2.3/tests/test_download_presigned_files.py +68 -0
  18. {rowan_python-3.2.1 → rowan_python-3.2.3}/tests/test_md_workflow_updates.py +24 -0
  19. rowan_python-3.2.3/tests/test_project.py +35 -0
  20. {rowan_python-3.2.1 → rowan_python-3.2.3}/tests/test_rbfe_trajectory_download.py +24 -0
  21. {rowan_python-3.2.1 → rowan_python-3.2.3}/uv.lock +5 -5
  22. {rowan_python-3.2.1 → rowan_python-3.2.3}/.claude-plugin/marketplace.json +0 -0
  23. {rowan_python-3.2.1 → rowan_python-3.2.3}/.codex-plugin/plugin.json +0 -0
  24. {rowan_python-3.2.1 → rowan_python-3.2.3}/.editorconfig +0 -0
  25. {rowan_python-3.2.1 → rowan_python-3.2.3}/.envrc +0 -0
  26. {rowan_python-3.2.1 → rowan_python-3.2.3}/.github/workflows/build-and-deploy-docs.yml +0 -0
  27. {rowan_python-3.2.1 → rowan_python-3.2.3}/.github/workflows/publish-skill.yml +0 -0
  28. {rowan_python-3.2.1 → rowan_python-3.2.3}/.github/workflows/python-publish.yml +0 -0
  29. {rowan_python-3.2.1 → rowan_python-3.2.3}/.github/workflows/test.yml +0 -0
  30. {rowan_python-3.2.1 → rowan_python-3.2.3}/.gitignore +0 -0
  31. {rowan_python-3.2.1 → rowan_python-3.2.3}/.python-version +0 -0
  32. {rowan_python-3.2.1 → rowan_python-3.2.3}/.rumdl.toml +0 -0
  33. {rowan_python-3.2.1 → rowan_python-3.2.3}/LICENSE +0 -0
  34. {rowan_python-3.2.1 → rowan_python-3.2.3}/README.md +0 -0
  35. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/api/api-keys.md +0 -0
  36. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/api/calculation.md +0 -0
  37. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/api/folder.md +0 -0
  38. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/api/molecule.md +0 -0
  39. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/api/project.md +0 -0
  40. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/api/protein.md +0 -0
  41. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/api/user.md +0 -0
  42. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/api/webhooks.md +0 -0
  43. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/api/workflow.md +0 -0
  44. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/images/RowanLogoLarge.png +0 -0
  45. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/images/RowanSquareLogo.png +0 -0
  46. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/images/favicon.svg +0 -0
  47. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/index.md +0 -0
  48. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/stylesheets/colors.css +0 -0
  49. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/workflows/admet.md +0 -0
  50. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/workflows/analogue-docking.md +0 -0
  51. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/workflows/basic-calculation.md +0 -0
  52. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/workflows/batch-docking.md +0 -0
  53. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/workflows/binding-affinity.md +0 -0
  54. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/workflows/bond-dissociation-energy.md +0 -0
  55. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/workflows/conformer-search-settings.md +0 -0
  56. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/workflows/conformer-search.md +0 -0
  57. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/workflows/covalent-inhibitor-scan.md +0 -0
  58. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/workflows/descriptors.md +0 -0
  59. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/workflows/docking.md +0 -0
  60. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/workflows/double-ended-ts-search.md +0 -0
  61. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/workflows/electronic-properties.md +0 -0
  62. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/workflows/fukui.md +0 -0
  63. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/workflows/hydrogen-bond-donor-acceptor-strength.md +0 -0
  64. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/workflows/interaction-energy-decomposition.md +0 -0
  65. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/workflows/ion-mobility.md +0 -0
  66. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/workflows/irc.md +0 -0
  67. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/workflows/logp.md +0 -0
  68. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/workflows/macropka.md +0 -0
  69. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/workflows/membrane-permeability.md +0 -0
  70. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/workflows/msa.md +0 -0
  71. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/workflows/multistage-optimization.md +0 -0
  72. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/workflows/nmr.md +0 -0
  73. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/workflows/pka.md +0 -0
  74. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/workflows/pocket-detection.md +0 -0
  75. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/workflows/pose-analysis-md.md +0 -0
  76. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/workflows/protein-binder-design.md +0 -0
  77. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/workflows/protein-cofolding.md +0 -0
  78. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/workflows/protein-md.md +0 -0
  79. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/workflows/protein-preparation.md +0 -0
  80. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/workflows/rbfe-graph.md +0 -0
  81. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/workflows/redox-potential.md +0 -0
  82. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/workflows/relative-binding-free-energy-perturbation.md +0 -0
  83. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/workflows/scan.md +0 -0
  84. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/workflows/settings.md +0 -0
  85. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/workflows/solubility.md +0 -0
  86. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/workflows/solvent-dependent-conformers.md +0 -0
  87. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/workflows/spin-states.md +0 -0
  88. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/workflows/strain.md +0 -0
  89. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/workflows/tautomer-search.md +0 -0
  90. {rowan_python-3.2.1 → rowan_python-3.2.3}/docs/workflows/types.md +0 -0
  91. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/PROTAC_solubility.py +0 -0
  92. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/admet.py +0 -0
  93. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/analogue_docking.py +0 -0
  94. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/aqueous_solubility.py +0 -0
  95. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/basic_calculation.py +0 -0
  96. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/basic_calculation_from_json.py +0 -0
  97. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/basic_calculation_with_constraint.py +0 -0
  98. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/basic_calculation_with_solvent.py +0 -0
  99. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/batch_docking.py +0 -0
  100. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/batch_solubility.py +0 -0
  101. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/bde.py +0 -0
  102. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/binding_affinity.py +0 -0
  103. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/boltz_paired_msa.py +0 -0
  104. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/boltz_single_msa.py +0 -0
  105. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/chai_paired_msa.py +0 -0
  106. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/chai_single_msa.py +0 -0
  107. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/cofolding_screen.py +0 -0
  108. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/colabfold_paired_msa.py +0 -0
  109. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/colabfold_single_msa.py +0 -0
  110. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/conformer_dependent_redox.py +0 -0
  111. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/conformers.py +0 -0
  112. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/conformers_screen.py +0 -0
  113. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/covalent_docking.py +0 -0
  114. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/covalent_inhibitor_scan.py +0 -0
  115. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/data/1iep_receptorH.pdb +0 -0
  116. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/data/Al_FCC.xyz +0 -0
  117. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/data/citalopram_1iep.xyz +0 -0
  118. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/data/ibuprofen_conformers.sdf +0 -0
  119. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/data/tyk2_ligands.sdf +0 -0
  120. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/data/tyk2_structure.pdb +0 -0
  121. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/data/workflow_example.json +0 -0
  122. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/dcd_download.py +0 -0
  123. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/descriptors.py +0 -0
  124. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/docking.py +0 -0
  125. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/docking_screen.py +0 -0
  126. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/double_ended_ts_search.py +0 -0
  127. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/electronic_properties.py +0 -0
  128. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/estimate_workflow.py +0 -0
  129. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/fukui_index.py +0 -0
  130. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/hydrogen_bond_basicity.py +0 -0
  131. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/induced_fit_docking.py +0 -0
  132. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/interaction_energy_decomposition.py +0 -0
  133. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/ion_mobility.py +0 -0
  134. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/irc.py +0 -0
  135. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/logp.py +0 -0
  136. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/macropka.py +0 -0
  137. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/membrane_permeability.py +0 -0
  138. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/multistage_optimization.py +0 -0
  139. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/nmr.py +0 -0
  140. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/optimization.py +0 -0
  141. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/periodic_dft.py +0 -0
  142. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/phenol_pka.py +0 -0
  143. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/pka.py +0 -0
  144. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/pocket_detection.py +0 -0
  145. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/pose_analysis_md.py +0 -0
  146. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/project_scoped_api_key.py +0 -0
  147. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/protein_binder_design.py +0 -0
  148. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/protein_cofolding.py +0 -0
  149. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/protein_cofolding_modified_inputs.py +0 -0
  150. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/protein_cofolding_with_constraints.py +0 -0
  151. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/protein_cofolding_with_templates.py +0 -0
  152. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/protein_file_download.py +0 -0
  153. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/protein_md.py +0 -0
  154. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/protein_preparation.py +0 -0
  155. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/rbfe_graph.py +0 -0
  156. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/rbfe_resubmit.py +0 -0
  157. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/redox_potential.py +0 -0
  158. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/relative_binding_free_energy_perturbation.py +0 -0
  159. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/resubmit_with_perturbations.py +0 -0
  160. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/retrieve_workflow.py +0 -0
  161. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/scan.py +0 -0
  162. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/solvent_dependent_conformers.py +0 -0
  163. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/spin_states.py +0 -0
  164. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/strain.py +0 -0
  165. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/tautomer.py +0 -0
  166. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/template.py +0 -0
  167. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/temporary_workflow_sharing.py +0 -0
  168. {rowan_python-3.2.1 → rowan_python-3.2.3}/examples/webhook.py +0 -0
  169. {rowan_python-3.2.1 → rowan_python-3.2.3}/mkdocs.yml +0 -0
  170. {rowan_python-3.2.1 → rowan_python-3.2.3}/prek.toml +0 -0
  171. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/api_keys.py +0 -0
  172. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/calculation.py +0 -0
  173. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/config.py +0 -0
  174. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/constants.py +0 -0
  175. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/folder.py +0 -0
  176. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/molecule.py +0 -0
  177. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/protein.py +0 -0
  178. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/py.typed +0 -0
  179. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/types.py +0 -0
  180. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/user.py +0 -0
  181. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/workflows/__init__.py +0 -0
  182. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/workflows/admet.py +0 -0
  183. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/workflows/analogue_docking.py +0 -0
  184. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/workflows/base.py +0 -0
  185. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/workflows/basic_calculation.py +0 -0
  186. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/workflows/batch_docking.py +0 -0
  187. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/workflows/bde.py +0 -0
  188. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/workflows/constants.py +0 -0
  189. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/workflows/covalent_inhibitor_scan.py +0 -0
  190. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/workflows/descriptors.py +0 -0
  191. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/workflows/docking.py +0 -0
  192. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/workflows/double_ended_ts_search.py +0 -0
  193. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/workflows/electronic_properties.py +0 -0
  194. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/workflows/fukui.py +0 -0
  195. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/workflows/hydrogen_bond_donor_acceptor_strength.py +0 -0
  196. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/workflows/interaction_energy_decomposition.py +0 -0
  197. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/workflows/ion_mobility.py +0 -0
  198. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/workflows/irc.py +0 -0
  199. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/workflows/logp.py +0 -0
  200. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/workflows/macropka.py +0 -0
  201. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/workflows/membrane_permeability.py +0 -0
  202. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/workflows/msa.py +0 -0
  203. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/workflows/multistage_optimization.py +0 -0
  204. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/workflows/nmr.py +0 -0
  205. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/workflows/pka.py +0 -0
  206. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/workflows/pocket_detection.py +0 -0
  207. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/workflows/pose_analysis_md.py +0 -0
  208. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/workflows/protein_binder_design.py +0 -0
  209. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/workflows/protein_cofolding.py +0 -0
  210. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/workflows/protein_preparation.py +0 -0
  211. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/workflows/rbfe_graph.py +0 -0
  212. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/workflows/redox_potential.py +0 -0
  213. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/workflows/scan.py +0 -0
  214. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/workflows/solubility.py +0 -0
  215. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/workflows/solvent_dependent_conformers.py +0 -0
  216. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/workflows/spin_states.py +0 -0
  217. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/workflows/strain.py +0 -0
  218. {rowan_python-3.2.1 → rowan_python-3.2.3}/rowan/workflows/tautomer_search.py +0 -0
  219. {rowan_python-3.2.1 → rowan_python-3.2.3}/skills/computational-chemistry-and-biology/.claude-plugin/plugin.json +0 -0
  220. {rowan_python-3.2.1 → rowan_python-3.2.3}/skills/computational-chemistry-and-biology/SKILL.md +0 -0
  221. {rowan_python-3.2.1 → rowan_python-3.2.3}/skills/computational-chemistry-and-biology/reference/admet.md +0 -0
  222. {rowan_python-3.2.1 → rowan_python-3.2.3}/skills/computational-chemistry-and-biology/reference/analogue_docking.md +0 -0
  223. {rowan_python-3.2.1 → rowan_python-3.2.3}/skills/computational-chemistry-and-biology/reference/basic_calculation.md +0 -0
  224. {rowan_python-3.2.1 → rowan_python-3.2.3}/skills/computational-chemistry-and-biology/reference/batch_docking.md +0 -0
  225. {rowan_python-3.2.1 → rowan_python-3.2.3}/skills/computational-chemistry-and-biology/reference/bde.md +0 -0
  226. {rowan_python-3.2.1 → rowan_python-3.2.3}/skills/computational-chemistry-and-biology/reference/covalent_inhibitor_scan.md +0 -0
  227. {rowan_python-3.2.1 → rowan_python-3.2.3}/skills/computational-chemistry-and-biology/reference/descriptors.md +0 -0
  228. {rowan_python-3.2.1 → rowan_python-3.2.3}/skills/computational-chemistry-and-biology/reference/docking.md +0 -0
  229. {rowan_python-3.2.1 → rowan_python-3.2.3}/skills/computational-chemistry-and-biology/reference/double_ended_ts_search.md +0 -0
  230. {rowan_python-3.2.1 → rowan_python-3.2.3}/skills/computational-chemistry-and-biology/reference/electronic_properties.md +0 -0
  231. {rowan_python-3.2.1 → rowan_python-3.2.3}/skills/computational-chemistry-and-biology/reference/folders_and_projects.md +0 -0
  232. {rowan_python-3.2.1 → rowan_python-3.2.3}/skills/computational-chemistry-and-biology/reference/fukui.md +0 -0
  233. {rowan_python-3.2.1 → rowan_python-3.2.3}/skills/computational-chemistry-and-biology/reference/hydrogen_bond_donor_acceptor_strength.md +0 -0
  234. {rowan_python-3.2.1 → rowan_python-3.2.3}/skills/computational-chemistry-and-biology/reference/interaction_energy_decomposition.md +0 -0
  235. {rowan_python-3.2.1 → rowan_python-3.2.3}/skills/computational-chemistry-and-biology/reference/ion_mobility.md +0 -0
  236. {rowan_python-3.2.1 → rowan_python-3.2.3}/skills/computational-chemistry-and-biology/reference/irc.md +0 -0
  237. {rowan_python-3.2.1 → rowan_python-3.2.3}/skills/computational-chemistry-and-biology/reference/logp.md +0 -0
  238. {rowan_python-3.2.1 → rowan_python-3.2.3}/skills/computational-chemistry-and-biology/reference/macropka.md +0 -0
  239. {rowan_python-3.2.1 → rowan_python-3.2.3}/skills/computational-chemistry-and-biology/reference/mcp_execution.md +0 -0
  240. {rowan_python-3.2.1 → rowan_python-3.2.3}/skills/computational-chemistry-and-biology/reference/membrane_permeability.md +0 -0
  241. {rowan_python-3.2.1 → rowan_python-3.2.3}/skills/computational-chemistry-and-biology/reference/msa.md +0 -0
  242. {rowan_python-3.2.1 → rowan_python-3.2.3}/skills/computational-chemistry-and-biology/reference/multistage_optimization.md +0 -0
  243. {rowan_python-3.2.1 → rowan_python-3.2.3}/skills/computational-chemistry-and-biology/reference/nmr.md +0 -0
  244. {rowan_python-3.2.1 → rowan_python-3.2.3}/skills/computational-chemistry-and-biology/reference/pka.md +0 -0
  245. {rowan_python-3.2.1 → rowan_python-3.2.3}/skills/computational-chemistry-and-biology/reference/pocket_detection.md +0 -0
  246. {rowan_python-3.2.1 → rowan_python-3.2.3}/skills/computational-chemistry-and-biology/reference/pose_analysis_md.md +0 -0
  247. {rowan_python-3.2.1 → rowan_python-3.2.3}/skills/computational-chemistry-and-biology/reference/protein_binder_design.md +0 -0
  248. {rowan_python-3.2.1 → rowan_python-3.2.3}/skills/computational-chemistry-and-biology/reference/protein_cofolding.md +0 -0
  249. {rowan_python-3.2.1 → rowan_python-3.2.3}/skills/computational-chemistry-and-biology/reference/protein_preparation.md +0 -0
  250. {rowan_python-3.2.1 → rowan_python-3.2.3}/skills/computational-chemistry-and-biology/reference/python_sdk.md +0 -0
  251. {rowan_python-3.2.1 → rowan_python-3.2.3}/skills/computational-chemistry-and-biology/reference/rbfe_graph.md +0 -0
  252. {rowan_python-3.2.1 → rowan_python-3.2.3}/skills/computational-chemistry-and-biology/reference/redox_potential.md +0 -0
  253. {rowan_python-3.2.1 → rowan_python-3.2.3}/skills/computational-chemistry-and-biology/reference/relative_binding_free_energy_perturbation.md +0 -0
  254. {rowan_python-3.2.1 → rowan_python-3.2.3}/skills/computational-chemistry-and-biology/reference/scan.md +0 -0
  255. {rowan_python-3.2.1 → rowan_python-3.2.3}/skills/computational-chemistry-and-biology/reference/solubility.md +0 -0
  256. {rowan_python-3.2.1 → rowan_python-3.2.3}/skills/computational-chemistry-and-biology/reference/solvent_dependent_conformers.md +0 -0
  257. {rowan_python-3.2.1 → rowan_python-3.2.3}/skills/computational-chemistry-and-biology/reference/spin_states.md +0 -0
  258. {rowan_python-3.2.1 → rowan_python-3.2.3}/skills/computational-chemistry-and-biology/reference/strain.md +0 -0
  259. {rowan_python-3.2.1 → rowan_python-3.2.3}/skills/computational-chemistry-and-biology/reference/tautomer_search.md +0 -0
  260. {rowan_python-3.2.1 → rowan_python-3.2.3}/skills/computational-chemistry-and-biology/reference/webhooks.md +0 -0
  261. {rowan_python-3.2.1 → rowan_python-3.2.3}/skills/computational-chemistry-and-biology/scripts/check_env.py +0 -0
  262. {rowan_python-3.2.1 → rowan_python-3.2.3}/tests/test_cofolding_workflow_updates.py +0 -0
  263. {rowan_python-3.2.1 → rowan_python-3.2.3}/tests/test_docking_workflow.py +0 -0
  264. {rowan_python-3.2.1 → rowan_python-3.2.3}/tests/test_docking_workflow_updates.py +0 -0
  265. {rowan_python-3.2.1 → rowan_python-3.2.3}/tests/test_macropka_nmr.py +0 -0
  266. {rowan_python-3.2.1 → rowan_python-3.2.3}/tests/test_mango_forcefields.py +0 -0
  267. {rowan_python-3.2.1 → rowan_python-3.2.3}/tests/test_plugin.py +0 -0
  268. {rowan_python-3.2.1 → rowan_python-3.2.3}/tests/test_protein.py +0 -0
  269. {rowan_python-3.2.1 → rowan_python-3.2.3}/tests/test_solvent_dependent_conformers.py +0 -0
  270. {rowan_python-3.2.1 → rowan_python-3.2.3}/tests/test_tautomer_search.py +0 -0
  271. {rowan_python-3.2.1 → rowan_python-3.2.3}/tests/test_utils.py +0 -0
  272. {rowan_python-3.2.1 → rowan_python-3.2.3}/tests/test_workflow_results.py +0 -0
  273. {rowan_python-3.2.1 → rowan_python-3.2.3}/tests/test_workflow_submission.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: rowan-python
3
- Version: 3.2.1
3
+ Version: 3.2.3
4
4
  Summary: Rowan Python Library
5
5
  Project-URL: Homepage, https://github.com/rowansci/rowan-client
6
6
  Project-URL: Bug Tracker, https://github.com/rowansci/rowan-client/issues
@@ -12,7 +12,7 @@ Requires-Dist: httpx
12
12
  Requires-Dist: nest-asyncio
13
13
  Requires-Dist: rdkit
14
14
  Requires-Dist: setuptools
15
- Requires-Dist: stjames>=0.0.275
15
+ Requires-Dist: stjames>=0.0.289
16
16
  Description-Content-Type: text/markdown
17
17
 
18
18
  # Rowan Python Library
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "rowan-python"
3
- version = "3.2.1"
3
+ version = "3.2.3"
4
4
  description = "Rowan Python Library"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -12,7 +12,7 @@ authors = [
12
12
  ]
13
13
  dependencies = [
14
14
  "httpx",
15
- "stjames>=0.0.275",
15
+ "stjames>=0.0.289",
16
16
  "setuptools",
17
17
  "rdkit",
18
18
  "nest-asyncio",
@@ -15,6 +15,7 @@ from stjames import (
15
15
  Constraint,
16
16
  ConstraintType,
17
17
  ConformerClusteringSettings,
18
+ ConformerProperties,
18
19
  ConformerGenSettingsUnion,
19
20
  Correction,
20
21
  DNASequence,
@@ -67,6 +68,7 @@ from stjames.engine_compatibility import (
67
68
  METHOD_ENGINES,
68
69
  get_supported_corrections,
69
70
  )
71
+ from stjames.pdb import ResidueRef
70
72
  from stjames.optimization.band_method import NEBSettings
71
73
  from stjames.optimization.interpolation import Interpolation
72
74
  from stjames.optimization.string_method import FSMSettings, StringMethodSettings
@@ -82,5 +84,6 @@ from .types import ProteinUUID, RdkitMol, SMILES, StJamesMolecule, StructureInpu
82
84
  from .workflows import *
83
85
  from .project import *
84
86
  from .protein import *
87
+ from .spectra import HARTREE_TO_EV, broaden_spectrum
85
88
  from .user import *
86
89
  from .utils import *
@@ -197,8 +197,15 @@ def default_project() -> Project:
197
197
 
198
198
  Raises:
199
199
  httpx.HTTPStatusError: API request fails
200
+ ValueError: user has no default project in the API key's organization
200
201
  """
201
202
  with api_client() as client:
202
203
  response = client.get("/user/me/default_project")
203
204
  response.raise_for_status()
204
- return Project(**response.json())
205
+ project = response.json()
206
+ if project is None:
207
+ raise ValueError(
208
+ "You have no default project in this organization. "
209
+ "Set a default project, or pass a folder or project."
210
+ )
211
+ return Project(**project)
@@ -0,0 +1,68 @@
1
+ """Broaden stick spectra into continuous line shapes."""
2
+
3
+ import math
4
+ from collections.abc import Sequence
5
+ from typing import Literal
6
+
7
+ HARTREE_TO_EV = 27.211386245988
8
+
9
+ _GAUSSIAN_FWHM_TO_SIGMA = 1 / (2 * math.sqrt(2 * math.log(2)))
10
+
11
+
12
+ def broaden_spectrum(
13
+ positions: Sequence[float],
14
+ intensities: Sequence[float],
15
+ grid: Sequence[float],
16
+ fwhm: float,
17
+ lineshape: Literal["gaussian", "lorentzian"] = "gaussian",
18
+ ) -> list[float]:
19
+ """Sum line shapes centered on each stick, each with area equal to its intensity.
20
+
21
+ Use it on the stick spectra of `ConformerSearchResult.ensemble_properties`, for
22
+ example excitation energies with oscillator or rotatory strengths for UV-vis or ECD,
23
+ or vibrational frequencies with IR intensities. `grid` and `fwhm` share the units of
24
+ `positions`; convert excitation energies from Hartree with `HARTREE_TO_EV` first to
25
+ broaden in eV.
26
+
27
+ Args:
28
+ positions: line positions
29
+ intensities: line intensities, parallel to `positions`
30
+ grid: points at which to evaluate the spectrum
31
+ fwhm: full width at half maximum of each line
32
+ lineshape: line shape of each stick
33
+
34
+ Returns:
35
+ spectrum at each point of `grid`
36
+
37
+ Raises:
38
+ ValueError: `fwhm` is not positive or `intensities` differs in length from `positions`
39
+
40
+ >>> [round(y, 4) for y in broaden_spectrum([0.0], [1.0], [0.0, 0.5], fwhm=1.0)]
41
+ [0.9394, 0.4697]
42
+ >>> [round(y, 4) for y in broaden_spectrum([0.0], [2.0], [0.0], 1.0, "lorentzian")]
43
+ [1.2732]
44
+ """
45
+ if fwhm <= 0:
46
+ raise ValueError(f"fwhm must be positive, got {fwhm}")
47
+ if len(positions) != len(intensities):
48
+ raise ValueError(
49
+ f"positions ({len(positions)}) and intensities ({len(intensities)}) "
50
+ "must have the same length"
51
+ )
52
+
53
+ if lineshape == "gaussian":
54
+ sigma = fwhm * _GAUSSIAN_FWHM_TO_SIGMA
55
+ norm = 1 / (sigma * math.sqrt(2 * math.pi))
56
+
57
+ def shape(dx: float) -> float:
58
+ return norm * math.exp(-0.5 * (dx / sigma) ** 2)
59
+
60
+ else:
61
+ gamma = fwhm / 2
62
+
63
+ def shape(dx: float) -> float:
64
+ return gamma / (math.pi * (dx**2 + gamma**2))
65
+
66
+ return [
67
+ sum(i * shape(x - p) for p, i in zip(positions, intensities, strict=True)) for x in grid
68
+ ]
@@ -4,7 +4,7 @@ from contextvars import ContextVar
4
4
  from dataclasses import dataclass
5
5
  from pathlib import Path
6
6
  from tempfile import NamedTemporaryFile
7
- from typing import Any, Generator, Literal
7
+ from typing import Any, Callable, ContextManager, Generator, Literal
8
8
 
9
9
  import httpx
10
10
  import stjames
@@ -167,24 +167,14 @@ def api_client() -> Generator[httpx.Client, None, None]:
167
167
  yield client
168
168
 
169
169
 
170
- def download_file(
171
- path: Path,
172
- method: Literal["GET", "POST"],
173
- url: str,
174
- *,
175
- params: dict[str, Any] | None = None,
176
- json: Any = None,
177
- ) -> Path:
178
- """Stream an authenticated API response into one local file atomically."""
170
+ def _stream_to_file(path: Path, stream: Callable[[], ContextManager[httpx.Response]]) -> Path:
171
+ """Write a streamed response into one local file atomically."""
179
172
  path.parent.mkdir(parents=True, exist_ok=True)
180
173
  temporary_path: Path | None = None
181
174
  try:
182
175
  with NamedTemporaryFile(dir=path.parent, prefix=f".{path.name}.", delete=False) as output:
183
176
  temporary_path = Path(output.name)
184
- with (
185
- api_client() as client,
186
- client.stream(method, url, params=params, json=json) as response,
187
- ):
177
+ with stream() as response:
188
178
  response.raise_for_status()
189
179
  for chunk in response.iter_bytes():
190
180
  output.write(chunk)
@@ -196,6 +186,65 @@ def download_file(
196
186
  return path
197
187
 
198
188
 
189
+ def download_file(
190
+ path: Path,
191
+ method: Literal["GET", "POST"],
192
+ url: str,
193
+ *,
194
+ params: dict[str, Any] | None = None,
195
+ json: Any = None,
196
+ ) -> Path:
197
+ """Stream an authenticated API response into one local file atomically."""
198
+
199
+ @contextmanager
200
+ def stream() -> Generator[httpx.Response, None, None]:
201
+ with (
202
+ api_client() as client,
203
+ client.stream(method, url, params=params, json=json) as response,
204
+ ):
205
+ yield response
206
+
207
+ return _stream_to_file(path, stream)
208
+
209
+
210
+ def download_presigned_files(
211
+ directory: Path,
212
+ url: str,
213
+ *,
214
+ params: dict[str, Any] | None = None,
215
+ json: Any = None,
216
+ ) -> list[Path]:
217
+ """Fetch presigned download links from the API and stream each file into `directory`.
218
+
219
+ Links are short-lived, so an expired one is refreshed once before giving up.
220
+ """
221
+
222
+ def fetch_links() -> list[dict[str, str]]:
223
+ with api_client() as client:
224
+ return client.post(url, params=params, json=json).json()
225
+
226
+ def download(link: dict[str, str]) -> Path:
227
+ # Plain client: the API key must never be sent to S3.
228
+ @contextmanager
229
+ def stream() -> Generator[httpx.Response, None, None]:
230
+ with httpx.Client(timeout=httpx.Timeout(120, read=None)) as client:
231
+ with client.stream("GET", link["url"]) as response:
232
+ yield response
233
+
234
+ return _stream_to_file(directory / Path(link["filename"]).name, stream)
235
+
236
+ links = fetch_links()
237
+ paths = []
238
+ for i, link in enumerate(links):
239
+ try:
240
+ paths.append(download(link))
241
+ except httpx.HTTPStatusError as error:
242
+ if error.response.status_code != 403:
243
+ raise
244
+ paths.append(download(fetch_links()[i]))
245
+ return paths
246
+
247
+
199
248
  ATOMIC_NUMBER_TO_ATOMIC_SYMBOL = {
200
249
  "1": "H",
201
250
  "2": "He",
@@ -8,7 +8,7 @@ from typing import Literal, Protocol
8
8
  from stjames.pdb import PDB
9
9
 
10
10
  from rowan.protein import Protein, retrieve_protein
11
- from rowan.utils import api_client, download_file
11
+ from rowan.utils import api_client, download_file, download_presigned_files
12
12
 
13
13
  from .base import WorkflowResult
14
14
 
@@ -222,7 +222,9 @@ class _MolecularDynamicsResult(WorkflowResult):
222
222
  name: str | None = None,
223
223
  path: Path | str | None = None,
224
224
  ) -> Path:
225
- """Download DCD trajectory files for selected replicates.
225
+ """Download DCD trajectory files for selected replicates as one `.tar.gz` archive.
226
+
227
+ Long trajectories can time out; use `download_trajectory_files` for those.
226
228
 
227
229
  Args:
228
230
  replicates: zero-based replicate indices
@@ -244,6 +246,29 @@ class _MolecularDynamicsResult(WorkflowResult):
244
246
  json=replicates,
245
247
  )
246
248
 
249
+ def download_trajectory_files(
250
+ self,
251
+ replicates: list[int],
252
+ path: Path | str | None = None,
253
+ ) -> list[Path]:
254
+ """Download each selected replicate's DCD file directly from storage.
255
+
256
+ Args:
257
+ replicates: zero-based replicate indices
258
+ path: output directory; defaults to the current directory
259
+
260
+ Returns:
261
+ paths to the downloaded `.dcd` files, one per replicate
262
+
263
+ Raises:
264
+ httpx.HTTPStatusError: API or storage request fails
265
+ """
266
+ return download_presigned_files(
267
+ Path(path) if path is not None else Path.cwd(),
268
+ f"/trajectory/{self.workflow_uuid}/trajectory_dcd_urls",
269
+ json=replicates,
270
+ )
271
+
247
272
  def _trajectory(self, replicate: int) -> _MolecularDynamicsTrajectory:
248
273
  """Return one trajectory with a consistent bounds error."""
249
274
  trajectories = self.trajectories
@@ -153,9 +153,15 @@ def submit_binding_affinity_workflow(
153
153
  binding_affinity_settings=binding_affinity_settings or SinglePointEnergySettings(),
154
154
  )
155
155
 
156
+ workflow_data = workflow.model_dump(serialize_as_any=True, mode="json")
157
+ if isinstance(workflow.binding_affinity_settings, SinglePointEnergySettings):
158
+ workflow_data["binding_affinity_settings"]["truncation_radius"] = (
159
+ workflow.binding_affinity_settings.truncation_radius
160
+ )
161
+
156
162
  data = {
157
163
  "workflow_type": "binding_affinity",
158
- "workflow_data": workflow.model_dump(serialize_as_any=True, mode="json"),
164
+ "workflow_data": workflow_data,
159
165
  "name": name,
160
166
  "folder_uuid": folder_uuid,
161
167
  "max_credits": max_credits,
@@ -31,6 +31,8 @@ from .base import (
31
31
  )
32
32
  from .constants import to_relative_kcal
33
33
 
34
+ ConformerProperties = stjames.ConformerProperties
35
+
34
36
 
35
37
  @register_result("conformer_search")
36
38
  class ConformerSearchResult(WorkflowResult):
@@ -72,22 +74,59 @@ class ConformerSearchResult(WorkflowResult):
72
74
  return to_relative_kcal(energies) if relative else energies
73
75
 
74
76
  @property
75
- def radii_of_gyration(self) -> list[float]:
76
- """Radius of gyration for each conformer (A)."""
77
+ def radii_of_gyration(self) -> list[float | None]:
78
+ """Radius of gyration for each conformer (A), None where it failed."""
77
79
  return [p.radius_of_gyration for p in self._workflow.conformer_properties]
78
80
 
79
81
  @property
80
- def sasa(self) -> list[float]:
81
- """Solvent accessible surface area for each conformer (A^2)."""
82
+ def sasa(self) -> list[float | None]:
83
+ """Solvent accessible surface area for each conformer (A^2), None where it failed."""
82
84
  return [p.solvent_accessible_surface_area for p in self._workflow.conformer_properties]
83
85
 
84
86
  @property
85
- def polar_sasa(self) -> list[float]:
86
- """Polar solvent accessible surface area for each conformer (A^2)."""
87
+ def polar_sasa(self) -> list[float | None]:
88
+ """Polar solvent accessible surface area for each conformer (A^2), None where it failed."""
87
89
  return [
88
90
  p.polar_solvent_accessible_surface_area for p in self._workflow.conformer_properties
89
91
  ]
90
92
 
93
+ @property
94
+ def dipoles(self) -> list[float | None]:
95
+ """Dipole moment magnitude about the center of mass for each conformer (Debye).
96
+
97
+ None where the method reports no dipole.
98
+ """
99
+ return [p.dipole for p in self._workflow.conformer_properties]
100
+
101
+ @property
102
+ def ensemble_properties(self) -> ConformerProperties | None:
103
+ """Ensemble properties weighted by `boltzmann_weights`.
104
+
105
+ Each property is averaged over the conformers that have it: SASA, polar SASA, Rg,
106
+ the root-mean-square dipole (Debye), and per-atom Mulliken charges. When the run
107
+ computed them, it also holds stick spectra with every conformer's lines, each
108
+ intensity scaled by its conformer's population: `excitation_energies` (Hartree) with
109
+ `oscillator_strengths` (UV-vis) and `rotatory_strengths` (ECD), and
110
+ `vibrational_frequencies` (cm^-1) with `ir_intensities` (km/mol). Pass them to
111
+ `rowan.broaden_spectrum` for the spectrum. Rotatory strengths are omitted when the
112
+ conformers differ in stereochemistry, since mixing enantiomers cancels the ECD signal.
113
+ """
114
+ return self._workflow.ensemble_properties
115
+
116
+ @property
117
+ def boltzmann_weights(self) -> list[float]:
118
+ """Population of each conformer at the weighting temperature (sum to 1.0).
119
+
120
+ From free energies when every conformer has frequencies, else from ground-state
121
+ energies. Empty for workflows run before weights were reported.
122
+ """
123
+ return list(self._workflow.boltzmann_weights)
124
+
125
+ @property
126
+ def weighting_temperature(self) -> float:
127
+ """Temperature of `boltzmann_weights` and `ensemble_properties` (K)."""
128
+ return self._workflow.weighting_temperature
129
+
91
130
  def get_conformers(self, n: int | None = None) -> list[Molecule]:
92
131
  """Fetch conformer molecules.
93
132
 
@@ -156,6 +195,7 @@ def _mso_for_final_method(
156
195
  final_method: Method,
157
196
  solvent: SolventInput = None,
158
197
  transition_state: bool = False,
198
+ frequencies: bool = False,
159
199
  ) -> MultiStageOptSettings:
160
200
  """Build optimization settings for the final method.
161
201
 
@@ -163,9 +203,9 @@ def _mso_for_final_method(
163
203
  without a named preset.
164
204
 
165
205
  `solvent` and `transition_state` are merged into each stage's `Settings`
166
- when supplied; the named preset itself is independent of them (mirrors how
167
- tinbergen's form keeps the preset dropdown separate from the solvent / TS
168
- toggles).
206
+ when supplied, and `frequencies` runs on the optimization stage; the named
207
+ preset itself is independent of them (mirrors how tinbergen's form keeps the
208
+ preset dropdown separate from the solvent / TS toggles).
169
209
  """
170
210
  opt_method, sp_method = _FINAL_METHOD_TO_MSO_RECIPE.get(final_method, (final_method, None))
171
211
 
@@ -196,6 +236,7 @@ def _mso_for_final_method(
196
236
  return MultiStageOptSettings(
197
237
  optimization_settings=[opt_stage],
198
238
  singlepoint_settings=sp_stage,
239
+ frequencies=frequencies,
199
240
  )
200
241
 
201
242
 
@@ -224,6 +265,8 @@ def submit_conformer_search_workflow(
224
265
  final_method: Method | str = "aimnet2_wb97md3",
225
266
  solvent: SolventInput = None,
226
267
  transition_state: bool = False,
268
+ frequencies: bool = False,
269
+ weighting_temperature: float = 298.15,
227
270
  multistage_opt_settings: MultiStageOptSettings | None = None,
228
271
  conformer_clustering_settings: ConformerClusteringSettings | None = None,
229
272
  initial_conformers: Sequence[StructureInput] | None = None,
@@ -264,6 +307,11 @@ def submit_conformer_search_workflow(
264
307
  `multistage_opt_settings` is provided
265
308
  transition_state: whether to optimize the transition state. Ignored
266
309
  if `multistage_opt_settings` is provided
310
+ frequencies: compute vibrational frequencies of each conformer at the optimization
311
+ level, for the ensemble IR spectrum. Ignored if `multistage_opt_settings` is
312
+ provided; set its `frequencies` instead
313
+ weighting_temperature: temperature for the conformer populations, in Kelvin. With
314
+ frequencies, populations use free energies at this temperature
267
315
  multistage_opt_settings: optimization stages and singlepoint settings
268
316
  for ranking conformers. When provided, takes precedence over
269
317
  `final_method` / `solvent` / `transition_state`. When omitted, an MSO is
@@ -359,7 +407,10 @@ def submit_conformer_search_workflow(
359
407
  if isinstance(final_method, str):
360
408
  final_method = Method(final_method)
361
409
  multistage_opt_settings = _mso_for_final_method(
362
- final_method, solvent=solvent, transition_state=transition_state
410
+ final_method,
411
+ solvent=solvent,
412
+ transition_state=transition_state,
413
+ frequencies=frequencies,
363
414
  )
364
415
  multistage_opt_settings = _release_ts_optimization_constraints(multistage_opt_settings)
365
416
 
@@ -370,6 +421,7 @@ def submit_conformer_search_workflow(
370
421
  multistage_opt_settings=multistage_opt_settings,
371
422
  conf_gen_settings=conf_gen_settings,
372
423
  conformer_clustering_settings=conformer_clustering_settings,
424
+ weighting_temperature=weighting_temperature,
373
425
  )
374
426
 
375
427
  workflow_data = workflow.model_dump(serialize_as_any=True, mode="json")
@@ -149,7 +149,7 @@ def submit_protein_md_workflow(
149
149
  binder: Binder | None = None,
150
150
  protein_restraint_cutoff: float | None = None,
151
151
  protein_restraint_constant: float = 100,
152
- analysis_interval_ps: float | None = None,
152
+ analysis_interval_ps: float | None = 100,
153
153
  clustering: KMeansClusteringSettings | GreedyClusteringSettings | None = None,
154
154
  validate_forcefield: bool = True,
155
155
  name: str = "Protein MD Workflow",
@@ -194,8 +194,8 @@ def submit_protein_md_workflow(
194
194
  protein_restraint_cutoff: cutoff distance from the binder past which Cα atoms are
195
195
  harmonically restrained, in Å; None disables restraints
196
196
  protein_restraint_constant: force constant for Cα backbone restraints, in kcal/mol/Ų
197
- analysis_interval_ps: interval at which to compute per-frame SASA and polar SASA, in ps.
198
- None disables those analyses
197
+ analysis_interval_ps: interval at which to compute per-frame SASA and polar SASA
198
+ (default 100), in ps. None disables those analyses
199
199
  clustering: how to cluster trajectory frames. None disables clustering; pass a
200
200
  KMeansClusteringSettings (num_clusters) or GreedyClusteringSettings (cutoff_angstrom)
201
201
  validate_forcefield: validate the protein forcefield
@@ -12,7 +12,7 @@ from rowan.folder import Folder
12
12
  from rowan.molecule import Molecule
13
13
  from rowan.protein import Protein
14
14
  from rowan.types import ProteinUUID
15
- from rowan.utils import api_client, download_file
15
+ from rowan.utils import api_client, download_file, download_presigned_files
16
16
 
17
17
  from .base import Workflow, WorkflowResult, molecule_to_dict, register_result
18
18
  from .rbfe_graph import RelativeBindingFreeEnergyGraphEdge, RelativeBindingFreeEnergyGraphResult
@@ -143,7 +143,9 @@ class RelativeBindingFreeEnergyPerturbationResult(WorkflowResult):
143
143
  *,
144
144
  leg: Literal["complex", "solvent"] = "complex",
145
145
  ) -> Path:
146
- """Download DCD trajectory files for a specific perturbation edge.
146
+ """Download DCD trajectory files for a specific perturbation edge as one `.tar.gz` archive.
147
+
148
+ Long trajectories can time out; use `download_edge_trajectory_files` for those.
147
149
 
148
150
  Args:
149
151
  edge_index: index of the edge (0-based, matching `edges` order)
@@ -183,6 +185,40 @@ class RelativeBindingFreeEnergyPerturbationResult(WorkflowResult):
183
185
  json=lambda_vals,
184
186
  )
185
187
 
188
+ def download_edge_trajectory_files(
189
+ self,
190
+ edge_index: int,
191
+ lambda_vals: list[float] | None = None,
192
+ path: Path | str | None = None,
193
+ *,
194
+ leg: Literal["complex", "solvent"] = "complex",
195
+ ) -> list[Path]:
196
+ """Download each lambda window's DCD file for one edge directly from storage.
197
+
198
+ Args:
199
+ edge_index: index of the edge (0-based, matching `edges` order)
200
+ lambda_vals: lambda values to download. Defaults to all windows
201
+ path: directory to save the files to. Defaults to current directory
202
+ leg: thermodynamic leg whose trajectories should be downloaded
203
+
204
+ Returns:
205
+ paths to the downloaded `.dcd` files, one per lambda window
206
+
207
+ Raises:
208
+ IndexError: edge_index is out of range
209
+ httpx.HTTPStatusError: API or storage request fails
210
+ """
211
+ edges = self.edges
212
+ if edge_index < 0 or edge_index >= len(edges):
213
+ raise IndexError(f"Edge index {edge_index} out of range (0-{len(edges) - 1})")
214
+
215
+ return download_presigned_files(
216
+ Path(path) if path is not None else Path.cwd(),
217
+ f"/trajectory/{self.workflow_uuid}/rbfe_trajectory_dcd_urls",
218
+ params={"edge_index": edge_index, "leg": leg},
219
+ json=lambda_vals,
220
+ )
221
+
186
222
  def download_all_trajectories(
187
223
  self,
188
224
  path: Path | str | None = None,
@@ -205,6 +241,35 @@ class RelativeBindingFreeEnergyPerturbationResult(WorkflowResult):
205
241
  self.download_edge_trajectories(i, path=path, leg=leg) for i in range(len(self.edges))
206
242
  ]
207
243
 
244
+ def download_all_trajectory_files(
245
+ self,
246
+ path: Path | str | None = None,
247
+ *,
248
+ leg: Literal["complex", "solvent"] = "complex",
249
+ ) -> list[Path]:
250
+ """Download every edge's DCD files directly from storage.
251
+
252
+ Files for each edge go in an `edge_<index>` subdirectory of `path`.
253
+
254
+ Args:
255
+ path: directory to save the files to. Defaults to current directory
256
+ leg: thermodynamic leg whose trajectories should be downloaded
257
+
258
+ Returns:
259
+ paths to all downloaded `.dcd` files, in edge order
260
+
261
+ Raises:
262
+ httpx.HTTPStatusError: any API or storage request fails
263
+ """
264
+ directory = Path(path) if path is not None else Path.cwd()
265
+ return [
266
+ file
267
+ for i in range(len(self.edges))
268
+ for file in self.download_edge_trajectory_files(
269
+ i, path=directory / f"edge_{i}", leg=leg
270
+ )
271
+ ]
272
+
208
273
  @property
209
274
  def ligand_dg_results(self) -> dict[str, RelativeBindingFreeEnergyResult] | None:
210
275
  """Per-ligand binding free energy results, or None if not yet computed."""
@@ -57,9 +57,19 @@ for name, score in zip(ligands.keys(), result.scores):
57
57
 
58
58
  - `rowan.SinglePointEnergySettings` (default):
59
59
  - `multistage_opt_settings` (default PM6-D3H4X/COSMO optimization + PM6-D3H4X/COSMO2 single-point in water): a `rowan.MultiStageOptSettings` controlling ligand geometry optimization and energy evaluation.
60
- - `truncation_radius` (default `6.0` Å): protein residues beyond this distance from the ligand are excluded from the calculation.
60
+ - `truncation_radius` (default `6.0` Å): selects whole protein residues with any atom within this distance of any submitted ligand atom.
61
+ - `truncation_residues`: nonempty list of `rowan.ResidueRef` objects identifying polymer residues by chain and residue keys in the submitted protein. Requires `truncation_radius=None`; a radius and explicit residues cannot be supplied together.
61
62
  - `rowan.GninaAffinitySettings`, `rowan.AEVPLIGAffinitySettings`, `rowan.NessoAffinitySettings`: no tunable parameters.
62
63
 
64
+ For an explicit pocket, pass settings like these, using keys from your protein:
65
+
66
+ ```python
67
+ rowan.SinglePointEnergySettings(
68
+ truncation_radius=None,
69
+ truncation_residues=[rowan.ResidueRef("polymer", "A", "A.182")],
70
+ )
71
+ ```
72
+
63
73
  ```python
64
74
  # Replace the path, residue name, and SMILES with those of your holo complex.
65
75
  holo_protein = rowan.upload_protein("Holo complex", "PATH_TO_HOLO_PDB")
@@ -27,10 +27,29 @@ best = result.get_conformers(1)[0] # lowest-energy conformer, a rowan.Molecule
27
27
 
28
28
  `get_conformers(n)` returns the `n` lowest-energy conformers as `rowan.Molecule` objects (omit `n` for all), and `get_energies(relative=True)` gives their relative energies in kcal/mol. Both are energy-ordered, so the lowest-energy conformer is index 0, ready to feed into a downstream 3D workflow. `get_conformers` makes one API call per conformer, so request only as many as you need.
29
29
 
30
+ ## Ensemble properties
31
+
32
+ `sasa`, `polar_sasa`, `radii_of_gyration`, and `dipoles` give per-conformer values in energy order (`None` where a value could not be computed); `dipoles` are magnitudes about the center of mass, in Debye. `boltzmann_weights` are the conformer populations at `weighting_temperature`, from free energies when every conformer has frequencies and from ground-state energies otherwise.
33
+
34
+ `ensemble_properties` is a `rowan.ConformerProperties` weighted by those populations over the conformers that have each value: SASA, polar SASA, Rg, the root-mean-square dipole, and per-atom Mulliken charges. When the run computed them, it also holds stick spectra with every conformer's lines, each intensity scaled by its conformer's population: `excitation_energies` (Hartree) with `oscillator_strengths` (UV-vis) and `rotatory_strengths` (ECD), and `vibrational_frequencies` (cm⁻¹) with `ir_intensities` (km/mol).
35
+
36
+ ```python
37
+ import rowan
38
+
39
+ props = result.ensemble_properties
40
+ energies_ev = [e * rowan.HARTREE_TO_EV for e in props.excitation_energies]
41
+ grid = [2 + 0.01 * i for i in range(600)] # eV
42
+ uv_vis = rowan.broaden_spectrum(energies_ev, props.oscillator_strengths, grid, fwhm=0.3)
43
+ ```
44
+
45
+ Spectra need the matching calculations: set `frequencies=True` for IR, and give `multistage_opt_settings` a TDDFT `singlepoint_settings` (with `excited_state_settings=rowan.TDDFTSettings(...)`) for UV-vis and ECD. ECD is omitted when the conformers differ in stereochemistry, so give a SMILES with every stereocenter assigned. A TDDFT single point does not report dipoles or charges. The populations are fixed when the workflow runs; resubmit with another `weighting_temperature` for a different temperature.
46
+
30
47
  ## Settings
31
48
 
32
49
  - `final_method` (default `aimnet2_wb97md3`): method used to rank the final conformers.
33
50
  - `solvent` (default gas phase): implicit solvent; introspect `rowan.Solvent` for the available solvents.
51
+ - `frequencies` (default `False`): compute vibrational frequencies of each conformer at the optimization level, for the ensemble IR spectrum and free-energy populations.
52
+ - `weighting_temperature` (default 298.15 K): temperature for the conformer populations and their free-energy corrections.
34
53
  - `transition_state` (default `False`): optimize each conformer toward a transition state rather than a minimum. Start from a fully optimized transition state, not a guess and not a SMILES. Conformer generation will otherwise relax the transition state to a local minimum, so pass constraints in `conf_gen_settings` to hold the reacting atoms in place during generation; the post-generation TS optimizations then run without constraints.
35
54
  - `conf_gen_settings` (default none, currently resolves to OpenConf): override conformer generation. Pick and tune a generator by passing its settings object: `rowan.OpenConfSettings`, `rowan.ETKDGSettings`, `rowan.MonteCarloMultipleMinimumSettings` (MCMM), or `rowan.iMTDGCSettings` (iMTDGC). OpenConf (the default) is Rowan's conformer generator; ETKDG is a fast RDKit knowledge-based generator; iMTDGC runs CREST metadynamics for more thorough sampling of flexible molecules, at higher cost. Each has its own tuning fields; run `help()` on the one you want. `max_confs` is common to every generator and caps how many conformers are kept after generation.
36
55
  - `multistage_opt_settings` (default none): override the full optimization-and-ranking stack as a `MultiStageOptSettings`. When supplied, it takes precedence over `final_method`, `solvent`, and `transition_state`, so those are ignored. Omit it and set `final_method`/`solvent`/`transition_state` instead unless you need a stack they cannot express.
@@ -61,7 +61,7 @@ result.download_trajectories([0], path=".") # save DCD trajectory files
61
61
  - `binder` (default `None`): a `rowan.Binder` specifying the binder within the complex — protein/peptide chains (`chain_ids`), small molecules (`small_molecule_residues`, identified by residue-name string or 0-based non-polymer residue index), or both. Enables per-frame MM/GBSA and binder RMSD analyses (see result fields).
62
62
  - `protein_restraint_cutoff` (default `None`): distance from the binder past which Cα atoms are harmonically restrained, in angstrom; `None` disables restraints. Useful for keeping the binding site mobile while stabilizing the rest of the protein.
63
63
  - `protein_restraint_constant` (default `100`): force constant for the Cα backbone restraints, in kcal/mol/Ų.
64
- - `analysis_interval_ps` (default `None`): interval at which to compute per-frame SASA and polar SASA, in ps. `None` (the default) disables those analyses.
64
+ - `analysis_interval_ps` (default `100`): interval at which to compute per-frame SASA and polar SASA, in ps. `None` disables those analyses.
65
65
  - `clustering` (default `None`): cluster the trajectory frames. `None` disables it; pass `rowan.KMeansClusteringSettings(num_clusters=10)` or `rowan.GreedyClusteringSettings(cutoff_angstrom=2.0)`.
66
66
  - `validate_forcefield` (default `True`): validate the protein forcefield before submitting; raises early if the protein cannot be parameterized or has clashing residues. When a `binder` is set, its small molecules are excluded from validation, whether keyed by residue name or by non-polymer index, since they are parameterized from their SMILES. Cofactors, metals, and glycans outside the binder are still validated — set `False` to skip the pre-check entirely.
67
67